Compare commits

..

169 Commits

Author SHA1 Message Date
Dan Poltawski 1533fca4b5 Moodle release 3.0rc2 2015-11-06 11:57:10 +00:00
Rajesh Taneja 7b4933db3a MDL-51685 unittest: Test functionality individually 2015-11-06 11:14:17 +01:00
Dan Poltawski 5208168e0c Merge branch 'MDL-51861' of https://github.com/stronk7/moodle 2015-11-06 10:04:02 +00:00
Eloy Lafuente (stronk7) ed4e707577 MDL-51861 enrol: added missing external cleanup for formal compliance 2015-11-06 10:58:28 +01:00
Dan Poltawski 265ddbf30c MDL-51569 mod_choice: Fix use of hardcoded ids
(Previous approach of making up ids longer works now we are validating
them..)
2015-11-06 08:09:47 +00:00
Dani Palou b036215acc MDL-51861 enrol: Don't get all parts in get_enrolled_users with groups 2015-11-06 15:07:24 +08:00
Andrew Nicols af8472a91f Merge branch 'MDL-51861' of git://github.com/stronk7/moodle 2015-11-06 15:01:20 +08:00
John Okely 531bcb13f6 MDL-51569 mod_choice: Prevent users from updating choices with curl
And fix unit tests.
2015-11-06 14:52:26 +08:00
Andrew Nicols 1d1f08a045 MDL-51685-master Fix unit tests following change
These unit tests were modifying the PAGE and other globals in the middle of
tests and were failing as a result.

Broke the tests up into separate tests where the functionality is now
tested individually.
2015-11-06 10:36:06 +08:00
Eloy Lafuente (stronk7) f076ce2834 MDL-51861 enrol: new unit test to verify all conditions and filters
This unit test does not verify contents, details, but only
that the returned users for every group mode, for every
filtering (active, groupid and withcapability) together
with permissions are correct.
2015-11-06 02:26:02 +01:00
Damyon Wiese 0bca129775 MDL-51569 mod_choice: Validate the submitted optionid
Make sure any submitted choice options actually belong to the current choice
module.
2015-11-05 10:48:40 +00:00
Dan Poltawski 2759c1218d Merge branch 'MDL-52036-master-resettable' of git://github.com/mudrd8mz/moodle 2015-11-05 09:03:20 +00:00
Ryan Wyllie ceb689b9fc MDL-50426 message: post_message capability tests
Behat and unit tests to confirm the behaviour of the
messaging lib after the capability checks were added
to the post message function.
2015-11-05 08:20:39 +00:00
Ryan Wyllie cd0c9ac87d MDL-50426 message: optional permission check on message
Changed the messaging API to allow you to provide a parameter
that will cause the code to check if the sender of the message
has the correct permissions before the message is sent.

The ajax messaging (message button on profile page) has been
updated to perform these permission checks. This brings it
inline with the existing private message page.
2015-11-05 08:20:39 +00:00
Ryan Wyllie e24188c881 MDL-50426 behat: fix behat field manager
Thanks to Raj for the patch.
2015-11-05 08:20:39 +00:00
Andrew Nicols 19f14de362 Merge branch 'MDL-51395-master' of git://github.com/junpataleta/moodle 2015-11-05 13:57:22 +08:00
Jun Pataleta cefa583e71 MDL-51395 blog: Fix output of strings for blog comments 2015-11-04 23:46:31 -06:00
Andrew Nicols c73acaa7ef Merge branch 'MDL-26429-master' of git://github.com/cameron1729/moodle 2015-11-05 12:12:43 +08:00
Andrew Nicols 8acf1ccd22 MDL-51899 mod_forum: Add test for allowing 0 grades 2015-11-05 12:07:27 +08:00
Cameron Ball 7062e7c6b3 MDL-26429 usability: Add course completion icons.
This patch adds a method to the course completion criteria classes
which allows them to specify an icon for display in the course
completion report table.

There is a default implementation which simply looks for an icon
in pix/i with a name matching the completion criteria type.
2015-11-05 11:40:58 +08:00
Andrew Nicols da737c7201 Merge branch 'MDL-51899_master' of git://github.com/jacac/moodle 2015-11-05 11:24:43 +08:00
Andrew Nicols f8e685af44 Merge branch 'MDL-51685-master' of git://github.com/dpalou/moodle 2015-11-05 10:41:43 +08:00
rajesh Taneja 03b1f63d40 MDL-50837 mod_scorm: include locallib to access function 2015-11-05 10:23:31 +08:00
rajesh Taneja 0db240c1dd MDL-51918 behat: Key press should be down-press-up
As we simulate real user key press event, the
event should down followed by press and then up key
2015-11-05 09:15:41 +08:00
Jakob 90e815390a MDL-51899 mod/assign: Allow 0.00 grades while editing a grade.
The function unformat_float can return 0 and false.
We need to do strict comparison for the return value on line 92 to accept grades 0.00.
2015-11-04 11:11:42 -08:00
David Mudrák 19168d0934 MDL-52036 tablelib: Fix the behaviour of resetting table preferences
The problem with the previous implementation was that the table's
$this->prefs can contain valid non-empty value in its default state -
the default column to sort by. On resetting, we must not throw away
these default prefs.

This patch simplifies the TABLE_VAR_RESET interpretation. If such an
HTTP parameter is passed via the request, the table simply behaves as if
there were no previously stored preferences (does not matter if coming
from the current session, or from the persistent cross-session storage).

The logic that decides on whether or not the reset widget should be
displayed is put into a new method can_be_reset() with unit tests
attached.

Finally, the previously private method render_reset_button() is now
protected and the reset widget is given a new semantic CSS class.
2015-11-04 15:07:17 +01:00
David Monllao 47d5c29202 MDL-51684 badges: Make sure 'moodle/badges:viewbadges' is respected 2015-11-04 13:01:11 +00:00
Yuliya Bozhko 4bd4d4eee8 MDL-51684 badges: Make sure 'moodle/badges:viewbadges' is respected
Signed-off-by: Yuliya Bozhko <yuliya.bozhko@totaralms.com>
2015-11-04 13:01:11 +00:00
Juan Leyva 02c256c941 MDL-50837 mod_scorm: Fix availability checks 2015-11-04 12:54:55 +01:00
Juan Leyva 3dd0ac559f MDL-51569 mod_choice: Check choice availability prior to do any action 2015-11-04 11:08:32 +00:00
Cameron Ball 4bb9e1ad8a MDL-51091 core_registration: session key check in registration. 2015-11-04 18:46:27 +08:00
Simey Lameze 31d0bf81af MDL-49940 mod_survey: Fix XSS on survey module 2015-11-04 18:46:27 +08:00
Hugh Davenport 45ae1da263 MDL-49940 mod_survey: Escape user comments
This patch fixes a XSS vulnerability with surveys where a student user
    could inject arbitrary HTML in a comment on the survey. The fix is to
    escape the comment before displaying it to the teacher.
2015-11-04 18:46:27 +08:00
David Monllao 3f08e9b98a MDL-51000 editor_atto: No autosave for guests 2015-11-04 18:46:27 +08:00
Simey Lameze 541c5b8552 MDL-48109 mod_lesson: prevent CSRF on password protected lesson
This commit add a new session key hidden field on the lesson password form
and confirm if the session key is valid on related pages to prevent CSRF on
password protected lessons.
2015-11-04 18:46:27 +08:00
Petr Skoda c73f6d03e5 MDL-48085 flowplayer: prevent flash parameters via url 2015-11-04 10:31:12 +00:00
Damyon Wiese 861c1dea98 MDL-51970 autocomplete: Fix for a regression in MDL-51964
This was causing the suggestions list to never open.
Picked up in testing for MDL-51970.
2015-11-04 16:29:47 +08:00
Dani Palou 1f7273af6a MDL-51685 user: Support gravatar images in user_get_user_details 2015-11-04 08:40:19 +01:00
Andrew Nicols 9fe1cb0421 Merge branch 'MDL-51983-master' of git://github.com/ryanwyllie/moodle 2015-11-04 15:21:37 +08:00
Ryan Wyllie 77b0811021 MDL-51983 actionmenu: behat tests 2015-11-04 06:36:32 +00:00
Andrew Nicols 6da79391ca Merge branch 'MDL-45814-master' of git://github.com/danpoltawski/moodle 2015-11-04 14:05:01 +08:00
Andrew Nicols 6c57f48034 Merge branch 'MDL-52012-master' of https://github.com/lucisgit/moodle 2015-11-04 13:49:01 +08:00
Andrew Nicols 01451bbc5a Merge branch 'MDL-52004-master' of git://github.com/damyon/moodle 2015-11-04 13:38:07 +08:00
Ryan Wyllie 5d2c5ba3e4 MDL-51983 actionmenu: fix hideIfOutside
The hideIfOutside function was being too restrictive with the
selector for checking if the event occurred within a menu child
(anchor tag) rather than the menu itself.
2015-11-04 03:42:09 +00:00
Ryan Wyllie 4456c306df MDL-51983 actionmenu: stop event propagation
Stop event propagation on keyboard events that have been
successfully handled by the action menu code.
2015-11-04 03:41:34 +00:00
Andrew Nicols 99b9751f0d Merge branch 'wip-MDL-51486-master' of git://github.com/abgreeve/moodle 2015-11-04 11:35:38 +08:00
Andrew Nicols 4d95d3f0e5 Merge branch 'MDL-51918-master-navigable' of https://github.com/pauln/moodle 2015-11-04 11:25:34 +08:00
Adrian Greeve 4a9a9bad73 MDL-51486 mod_data: Unit test update for item ratings.
Additional checks that access is available for item ratings
on records that have a group id of zero (all participants).
2015-11-04 10:12:39 +08:00
Adrian Greeve ca3e8022d1 MDL-51486 mod_data: Can view rating information.
When students are given the appropriate permissions
to view ratings, in the situation where there are
separate groups. The students can view the ratings
of an entry that is in the all participants group.
2015-11-04 10:12:39 +08:00
Andrew Nicols 36e323645a Merge branch 'MDL-51970-master' of git://github.com/damyon/moodle 2015-11-04 08:28:57 +08:00
Paul Nicholls 245f45f4d6 MDL-51918 gradereport: Fix AJAX mode keyboard navigation
Keyboard navigation (ctrl+up/down/left/right) with AJAX enabled ceased
functioning with editing mode enabled after MDL-36606, as it was looking
for cells with a "clickable" class - but was only applying that class when
editing mode is NOT enabled.  This patch uses a new "gbnavigable" class to
control keyboard navigation, whether editing mode is on or not.  It also
addresses some browser compatibility and minor behavioural issues.
2015-11-04 11:27:35 +13:00
Eloy Lafuente (stronk7) 4cef723c22 Moodle release 3.0rc1 2015-11-03 19:00:58 +01:00
Eloy Lafuente (stronk7) 561413695d Merge branch 'install_master' of https://git.in.moodle.com/amosbot/moodle-install 2015-11-03 19:00:41 +01:00
Eloy Lafuente (stronk7) 217c3e0fc9 Merge branch 'wip-mdl-52007' of https://github.com/rajeshtaneja/moodle 2015-11-03 17:56:23 +01:00
Eloy Lafuente (stronk7) 836c6d7883 Merge branch 'wip-mdl-52020' of https://github.com/rajeshtaneja/moodle 2015-11-03 17:56:22 +01:00
Dan Poltawski 728754de6b Merge branch 'MDL-52018-master-enfix' of git://github.com/mudrd8mz/moodle 2015-11-03 17:56:21 +01:00
Andrew Nicols 40a44cdd52 Merge branch 'MDL-51993-master' of git://github.com/cameron1729/moodle 2015-11-03 17:56:21 +01:00
AMOS bot 52617af3c9 Automatically generated installer lang files 2015-11-04 00:04:21 +08:00
Dan Poltawski 7a89772cfb MDL-45814 login: don't prompt guest user to change password 2015-11-03 14:48:58 +00:00
rajesh Taneja 180fdb2f7c MDL-52007 behat: Use extended timeout for site admin expansion 2015-11-03 16:08:27 +08:00
Barbara Ramiro 08dc6b095e MDL-26429 Course completion icons for self, duration, and role. 2015-11-03 14:54:15 +08:00
Cameron Ball c47b6f97d7 MDL-51993 course: Set valid default for section_edit_control_menu
Thank you to Gareth Barnard for originally providing this patch.
2015-11-03 10:29:38 +08:00
Andrew Nicols 80ad34e720 Merge branch 'MDL-51914' of git://github.com/timhunt/moodle 2015-11-03 09:42:04 +08:00
rajesh Taneja f497fdcc21 MDL-52020 behat: Removed text checking to ensure window switch
Comment question window closes after 2 sec.
which crashes win-FF as it's slow to check for
"Changes saved" text. As after switching it is
checked the changes are saved, this step can be
avoided to ensure robustness of the scenario
2015-11-03 09:40:33 +08:00
Tim Hunt 0b4b0a7eea MDL-51914 qtype_ddmarker: fix checking points on the boundary
All points on the boundary are now considered in the shape, on the
grounds that it is better to grade someone right for an edge case.
2015-11-02 22:06:23 +00:00
David Mudrák e8b75e6292 MDL-52018 lang: Fix behat tests to use the new strings 2015-11-02 22:30:46 +01:00
Helen Foster da6ecfd8c1 MDL-52018 lang: Merge English strings from the en_fix language pack 2015-11-02 22:12:06 +01:00
Eloy Lafuente (stronk7) 2cc040c6c0 Merge branch 'MDL-52014-master' of git://github.com/danpoltawski/moodle 2015-11-02 20:02:16 +01:00
Eloy Lafuente (stronk7) e88100f673 Merge branch 'wip-MDL-51999-master' of https://github.com/marinaglancy/moodle 2015-11-02 19:59:01 +01:00
Eloy Lafuente (stronk7) 373748277a Merge branch 'MDL-51829-master' of git://github.com/dpalou/moodle 2015-11-02 19:40:16 +01:00
Dan Poltawski 5294052875 MDL-52014 quiz: do not log preview attempt deletions
This happens automatically by the system.
2015-11-02 16:17:22 +00:00
Dan Poltawski 28aadc2b12 Merge branch 'MDL-51128-master' of git://github.com/junpataleta/moodle 2015-11-02 14:07:10 +00:00
Dan Poltawski 2088c22e41 Merge branch 'wip-MDL-50633-master2' of https://github.com/marinaglancy/moodle 2015-11-02 14:04:44 +00:00
Marina Glancy 57ee89569a MDL-50633 dml: deprecated var replaced in mysql 5.7
Thanks to Charles Verge for providing a patch
2015-11-02 21:05:20 +08:00
Tony Butler d3069afd27 MDL-52012 core_lib: Add namespace to $OUTPUT->notify_message definition 2015-11-02 11:12:47 +00:00
Dan Poltawski deec056650 Merge branch 'MDL-51854-master' of https://github.com/pauln/moodle 2015-11-02 09:08:01 +00:00
Damyon Wiese cfca338d7a MDL-52004 assign: Set unique urls when grading so autosaves work
The Atto autosave feature uses the $PAGE->url to generate unique hashes for
each page. Assignment uses a MUC cache to persist ordering of lists - the result
is that the urls are not unique (they depend on the cached filter).

The fix is to spoof a unique url that will grade only that student with no
active grading list (no next/prev buttons).
2015-11-02 16:41:55 +08:00
Andrew Nicols 693ed49c10 Merge branch 'MDL-51707-m' of https://github.com/micaherne/moodle 2015-11-02 14:36:10 +08:00
Andrew Nicols df8095156e Merge branch 'MDL-47073-master' of git://github.com/junpataleta/moodle 2015-11-02 11:54:21 +08:00
Marina Glancy bb0ac1fdc2 MDL-51999 user: remove unnecessary continue 2015-11-02 10:53:43 +08:00
Andrew Nicols 117dc470bc Merge branch 'MDL-51964-master' of git://github.com/damyon/moodle 2015-11-02 10:52:37 +08:00
Marina Glancy 9dfe870449 MDL-51888 mod_resource: cache file details for performance
If file details (size, type, date) are configured to be displayed we cache them
in course cache raw and build the display string in user language/timezone when
displaying. Also changed behat test not to fail in 2016
2015-11-02 10:38:26 +08:00
Damyon Wiese 273b255673 MDL-51970 autocomplete: Small refactor because too many options
Each function was taking 8-10 args - combine them all in state and options
objects and pass those around instead.
2015-11-02 10:22:53 +08:00
Damyon Wiese 97d2ea7f87 MDL-51970 autocomplete: Add an option to hide the suggestions
This is used by tags when official tags are disabled.
2015-11-02 10:21:57 +08:00
Damyon Wiese be9b036a33 MDL-51964 autocomplete: IE11 fires spurious "input" events
We need to check for "real" changes to the input value because
ie11 is just firing events willy nilly.
2015-11-02 10:11:13 +08:00
AMOS bot e0b2ad1f35 Automatically generated installer lang files 2015-11-01 00:07:45 +08:00
Jun Pataleta bbbdfc8610 MDL-47073 lib: Replace backslashes with slashes in jquery plugin urls
This patch replaces backslash characters in the generated jQuery
plugin URL to forward slashes when slasharguments is disabled and
 the Moodle server is running on IIS.
Thanks to Ryan Panning <Ryan.Panning@district196.org> for
reporting this issue and for suggesting a fix.
2015-10-30 17:05:23 -05:00
Dan Poltawski 821ab27c19 on-demand release 3.0beta+ 2015-10-30 21:09:21 +00:00
Dan Poltawski 999aa8ca45 Merge branch 'install_master' of https://git.in.moodle.com/amosbot/moodle-install 2015-10-30 21:09:20 +00:00
Eloy Lafuente (stronk7) d708bf59e4 Merge branch 'wip-mdl-51981' of https://github.com/rajeshtaneja/moodle 2015-10-30 10:23:52 +01:00
rajesh Taneja 4b274a10c2 MDL-51981 behat: No need for JS tag
This scenario crashes win-chrome and probably
other comninations. Looking at this, it seems
js tag is not needed, so removing js tag to make
it pass on all OS
2015-10-30 11:29:52 +08:00
Andrew Nicols ee0546291a Merge branch 'wip-MDL-51552-master' of git://github.com/abgreeve/moodle 2015-10-30 11:01:00 +08:00
Adrian Greeve 42b3a58c47 MDL-51552 gradebook: Update to singleview behat test. 2015-10-30 10:08:02 +08:00
Andrew Nicols 22d0a8f11e MDL-51966 forms: Typo correction 2015-10-30 07:56:25 +08:00
Andrew Nicols 63a03a8b05 Merge branch 'wip-MDL-51966-master' of https://github.com/marinaglancy/moodle 2015-10-30 07:31:28 +08:00
AMOS bot 1a9d1593e4 Automatically generated installer lang files 2015-10-30 00:04:09 +08:00
Dan Poltawski ca9daf0fb0 Merge branch 'MDL-51955' of git://github.com/stronk7/moodle 2015-10-29 14:22:28 +00:00
Dan Poltawski 02315eb2bb Merge branch 'MDL-51939-master' of git://github.com/andrewnicols/moodle 2015-10-29 13:54:56 +00:00
Eloy Lafuente (stronk7) 1f0f002e1d MDL-51939 mod_forum: all participants discussions are viewed by students
Added scenario to verify that "all participants" discussions, under
separate-groups mode are, by default, visible for students.
2015-10-29 21:10:20 +08:00
Andrew Nicols 7774cf96f8 MDL-51939 mod_forum: Fix groupid data on upgrade
This upgrade step addresses issues identified in MDL-51939 where the
groupid was incorrectly set. The issue itself is not present in 2.9, but
this upgrade step is required to correct any incorrect data.
2015-10-29 21:10:11 +08:00
Eloy Lafuente (stronk7) aa4a2716ef MDL-51955 testing: Exported files may be smaller than expected
Open a bit the allowed size expectation to accomodate oracle
producing smaller files when outputing decimal numbers.
2015-10-29 13:19:36 +01:00
Dan Poltawski 3adcb91d55 Merge branch 'MDL-50811-master' of git://github.com/cameron1729/moodle 2015-10-29 10:53:40 +00:00
Dan Poltawski ab0ad70a83 Merge branch 'MDL-51921-master' of git://github.com/junpataleta/moodle 2015-10-29 10:35:03 +00:00
Adrian Greeve 81e3251512 MDL-51552 gradebook: Single view bulk insert fix.
Single view now doesn't overwrite non-empty grades.

A thank you to Sam Anderson and Robert Russo for providing a patch.
2015-10-29 16:00:46 +08:00
Marina Glancy ecbc2a2f25 MDL-51966 forms: case-insensitive search in autocomplete 2015-10-29 15:10:20 +08:00
Andrew Nicols b5df2c1db0 Merge branch 'MDL-51923-master-2' of git://github.com/xow/moodle 2015-10-29 14:48:25 +08:00
Andrew Nicols 37d0021369 Merge branch 'MDL-51915' of git://github.com/timhunt/moodle 2015-10-29 14:41:45 +08:00
Andrew Nicols a72f3add06 Merge branch 'MDL-51799-master' of git://github.com/ryanwyllie/moodle 2015-10-29 13:16:15 +08:00
Ryan Wyllie f8f477ece5 MDL-51799 javascript: dialogue accessibility
Moved setting the aria visiblity of the dialogues from the show
and hide functions into the visibility change handler to stop
multiple calls.

Also made the visibility checker just continue to buffer the elements
it hides, rather than clearning them, on multiple calls.
2015-10-29 05:10:10 +00:00
Andrew Nicols 68881fb620 Merge branch 'MDL-51956-master' of git://github.com/ryanwyllie/moodle 2015-10-29 11:06:18 +08:00
Ryan Wyllie 1c53bc813e MDL-51956 atto_table: fix bg colour bug
The table editor will now set the backgorund colour on the
table in Firefox version 34 and below.

Also expanded the dialogue slightly because the colour choices
were wrapping and I added some styling to put a gap in for the
metric markers for border width and table width.
2015-10-29 02:51:06 +00:00
Dan Poltawski de342562cc Merge branch 'wip-MDL-51919-master' of git://github.com/abgreeve/moodle 2015-10-28 12:48:18 +00:00
Dan Poltawski bb63c18443 Merge branch 'MDL-51924-master' of git://github.com/barbararamiro/moodle 2015-10-28 12:04:20 +00:00
Dan Poltawski b2aaab60f3 Merge branch 'MDL-51838-master' of git://github.com/damyon/moodle 2015-10-28 11:53:53 +00:00
Dan Poltawski 413c321a18 Merge branch 'MDL-51914' of git://github.com/timhunt/moodle 2015-10-28 11:52:03 +00:00
Tim Hunt 2d391d1b10 MDL-51914 qtype_ddmarker: precise grading round the edges of zones
There was a previous change to the CSS (7px -> 3px) to make things
look prettier, which acutally broke the grading by a few pixes.
I fixed this by:

* Changing the offset of the cross hairs back to the correct value,
  and adding a comment to point out the imporance of not changing
  that CSS.

* Achieved the nice layout of the label relative to the cross-hairs
  in a different way.

* Added a similar clafirying comment in the JavaScript.

* Improved how the grid on the editing form is aligned with the
  background image.

* Added rounding to the grading code, to cope better now that
  browsers do sub-pixel positioning.
2015-10-28 10:53:00 +00:00
Jun Pataleta 9f555a7e77 MDL-51921 files: Fixes for file manager issues
Fixed the issue where the file path select box goes beyond the
container with very long folder names. Instead of setting to
"width: auto", I set it the select box's "width: 100%" so that it will
just fill up its container's width.
Fixed the issue where markups are being shown on file aliases.
2015-10-28 04:48:25 -05:00
Rajneel Totaram 494fec0071 MDL-51923 enrol: Enrolment list filter 'No Group' shows incorrect count
With contributions from John Okely
2015-10-28 17:34:16 +08:00
Adrian Greeve 527bde6e0f MDL-51919 forms lib: Autocomplete doesn't hide prematurely.
Quickly navigating via the keyboard to an autocomplete
element and hitting down would result in the selections
being displayed and then quickly removed. This has now
been fixed.
2015-10-28 14:15:26 +08:00
Andrew Nicols d412f8552d on-demand release 3.0beta+ 2015-10-28 12:59:31 +08:00
Andrew Nicols 6fb6859945 Merge branch 'install_master' of https://git.in.moodle.com/amosbot/moodle-install 2015-10-28 12:59:25 +08:00
Andrew Nicols af39ce0f8d MDL-51880 atto_table: Coding style fix 2015-10-28 11:39:17 +08:00
Andrew Nicols 7dea2c1707 Merge branch 'MDL-51880-master' of git://github.com/ryanwyllie/moodle 2015-10-28 11:37:17 +08:00
Ryan Wyllie f45937e17d MDL-51880 atto/plugins/table: improve table borders
* Removed the "No borders" option from general borders setting
  since it's a style choice.
* Added inherit, initial and unset as border style options.
* Made the border settings disabled if "Theme default" is selected.
* Changed the transparent colour to be "Theme default" which means
  apply no colour at all.
2015-10-28 03:28:36 +00:00
Dan Poltawski e360ff5db6 Merge branch 'MDL-51893' of git://github.com/stronk7/moodle 2015-10-27 14:43:00 +00:00
Dan Poltawski 61257ae417 Merge branch 'MDL-51863' of git://github.com/stronk7/moodle 2015-10-27 11:57:20 +00:00
Dan Poltawski 101df9a634 Merge branch 'MDL-51906-master-fixes' of git://github.com/andrewnicols/moodle 2015-10-27 10:49:10 +00:00
Andrew Nicols 68b35af353 MDL-51906 testing: Hardcode branch name 2015-10-27 18:31:23 +08:00
Eloy Lafuente (stronk7) 7c549a17f4 MDL-51893 core_date: Stop using Pacific/Norfolk as 11.5 replacement
Norfolk Island's standard time (NFT) was on UTC+11:30 until 4 October
2015, when it was changed to UTC+11:00. So it's not suitable for 11.5
bad mapping replacement anymore.

It seems that there isn't any other location using such 11.5 timezone
so completely getting rid of it for conversions.
2015-10-27 10:17:05 +01:00
Barbara Ramiro 39f360ae38 MDL-51924 Using box-sizing mixin to prevent blocks overlap on profile page 2015-10-27 16:13:43 +08:00
Corey Wallis 346ba4f443 MDL-50811 mod_forum: Add completion tracking for emailed replies 2015-10-27 15:53:42 +08:00
Andrew Nicols 10a8d040dc Merge branch 'MDL-51905-master' of git://github.com/xow/moodle 2015-10-27 14:50:55 +08:00
John Okely 161ffc5605 MDL-51905 block_activity_results: Default to the current activity
Due to an incorrect condition, the activity results block would
incorrectly identify some blocks as not being gradeable.
This meant that edit form could not determine what activity the block
was added to. This commit fixes the condition, allowing the current
block to be determined correctly
2015-10-27 13:47:58 +08:00
Andrew Nicols 53759a0c91 Merge branch 'wip-MDL-51840-master' of git://github.com/abgreeve/moodle 2015-10-27 11:13:44 +08:00
Andrew Nicols f5e587c1db Merge branch 'wip-MDL-51841-master' of git://github.com/abgreeve/moodle 2015-10-27 11:03:43 +08:00
Andrew Nicols 4727a4a207 Merge branch 'MDL-51864-master' of git://github.com/junpataleta/moodle 2015-10-27 10:57:45 +08:00
Andrew Nicols d891a6dcb7 Merge branch 'MDL-51127-master' of git://github.com/lazydaisy/moodle 2015-10-27 10:47:48 +08:00
Eloy Lafuente (stronk7) 70ae75674d MDL-51863 packer: ensure empty zip files behavior remains consistent
With PHP bug #70322 fixed, ZipArchive::close() did start returning false
and throwing PHP Warnings with recent PHP versions (5.6.14 and up).
Previously (5.6.13 verified) it was returning true, and false in older
versions (5.4.x verified).

This change does silent the 2 "hacky" calls to close() that we perform
in core leaving the 3rd one (used for files having files) unmodified.

A new unit test has been created to cover the close() behavior, ideally
supporting both old and new PHP versions without harcoding any PHP
version.

Note that we don't use to rely much on results coming from close(), and
that's a good thing given the buggy behavior commented above. This just
keeps empty zips working like they were before.
2015-10-26 21:16:54 +01:00
Tim Hunt 42406bc413 MDL-51915 lib tests: Extra unit test for html_to_text. 2015-10-26 18:30:21 +00:00
Dan Poltawski d3cd5f1f8f Merge branch 'wip-MDL-51834-master' of https://github.com/kwiliarty/moodle 2015-10-26 13:54:41 +00:00
Dan Poltawski 3ec49c6de3 Merge branch 'MDL-51906-master' of git://github.com/andrewnicols/moodle 2015-10-26 13:49:32 +00:00
Dan Poltawski ff1543e0df Merge branch 'MDL-51894-master' of git://github.com/andrewnicols/moodle 2015-10-26 12:40:15 +00:00
Dan Poltawski eb154d9c14 Merge branch 'MDL-51855-master' of git://github.com/lameze/moodle 2015-10-26 10:20:02 +00:00
Eloy Lafuente (stronk7) f7d8c61ae7 Merge branch 'MDL-51824-master' of git://github.com/lameze/moodle 2015-10-26 09:59:21 +01:00
Simey Lameze 0bc41cfcec MDL-51824 auth: include required CLI library 2015-10-26 15:52:11 +08:00
Adrian Greeve d304952b43 MDL-51840 forms lib: keyboard navigation with ajax works.
Ajax autocomplete forms can now be navigated properly with
the keyboard.
2015-10-26 15:03:42 +08:00
Adrian Greeve adebc069bd MDL-51841 forms lib: Autocomplete fields work with spaces. 2015-10-26 15:02:52 +08:00
Adrian Greeve abfaf50217 MDL-51841 forms lib: Autocomplete fields work with spaces. 2015-10-26 14:55:19 +08:00
Damyon Wiese 81c471e2a9 MDL-51821 forms: Be more explicit in autocomplete if no suggestions 2015-10-26 14:20:25 +08:00
Damyon Wiese bdd60287e5 MDL-51821 forms: Better handling for deselecting items in autocomplete
Fixes 3 problems with deselecting items in an autocomplete field:

1. Keep track of whether items were in the offical suggestion list, or are newly created
   tags and remove the "newly created tags" from the suggestion list when they are
   deselected
2. Change the aria-role for the selected items list when items cannot be deselected (and
   do not treat it like a multiselect list).
3. When leaving and returning focus to the selected items list, remember the last
   active-descendant.
2015-10-26 14:20:25 +08:00
Andrew Nicols 3365244fda Merge branch 'MDL-51824-master' of git://github.com/lameze/moodle 2015-10-26 13:56:49 +08:00
Simey Lameze 37e51a5daa MDL-51824 core: initial deprecation of auth sync CLI scripts
This commit does the initial deprecation of the CLI scripts of CAS and LDAP authentication plugins.
2015-10-26 13:36:27 +08:00
Damyon Wiese 32f3de567f MDL-51838 autocomplete: Scroll the suggestions list
Keep the active item "visible" when using keyboard nav on a long list of suggestions.
2015-10-26 12:20:33 +08:00
Andrew Nicols fd424b99f2 MDL-51894 inboundmessage: Ensure that all mailboxes exist 2015-10-26 12:17:15 +08:00
Andrew Nicols 5459e754c6 MDL-51906 testing: Fix travis builds for beta/rc
These were previously checking the release string for presence of the 'dev'
string. On friday we moved to beta, and will shortly move to RC.

We should be checking the version maturity instead.

This change switches to checking for MATURITY_STABLE in the maturity
string. If it is found, the branch behaviour follows the $branch version.
If it is not found, we use master.

This also adds a check to ensure that if the branch is not found, we exit
early instead of performing CI Tests over the entirety of Moodle.
2015-10-26 12:03:51 +08:00
Simey Lameze abc2491211 MDL-51855 mod_question: fix offset error during upgrade 2015-10-26 11:51:18 +08:00
Andrew Nicols 3953d0b7a7 Merge branch 'MDL-51842-master' of git://github.com/xow/moodle 2015-10-26 11:29:02 +08:00
John Okely 0fc2bccd60 MDL-51842 theme_bootstrapbase: Fix alignment of parallel buttons 2015-10-26 10:49:26 +08:00
Andrew Nicols 3363cbe55b Merge branch 'MDL-51779_master' of https://github.com/Syxton/moodle 2015-10-26 09:46:38 +08:00
Eloy Lafuente (stronk7) 8b3090c13b MDL-51903 behat: move guest enrolment to new UI
With MDL-30674 we missed to change this use from the
old (course edition) UI to the new (enrolment methods) one.
2015-10-25 21:13:08 +01:00
Jun Pataleta 3dbdeb5ccb MDL-51864 blocks: Fixed title display for block_course_summary 2015-10-24 00:03:39 -05:00
lazydaisy b395f7b599 MDL-51127 theme_bootstrapbase: Re-aligned RTL context header button. 2015-10-23 18:28:20 +01:00
AMOS bot 1bc3f19a5b Automatically generated installer lang files 2015-10-24 00:04:00 +08:00
Syxton 626d833543 MDL-51779 assign: PDF annotate quality
Fixes the quality of pdf content when annotating.
2015-10-23 09:22:38 -04:00
Kevin Wiliarty b71890687a MDL-51834 auth,profile: locks custom fields based on auth settings 2015-10-22 23:39:49 -04:00
Paul Nicholls ca50279d4e MDL-51854 atto_image: Allow other plugins to handle drop events
The drop handler should only return false if it has actually handled the
drop event (i.e. it was an image) - otherwise, Atto plugins loaded after
this one don't get an opportunity to handle other drop events.
2015-10-22 11:58:57 +13:00
Dani Palou dbcade703b MDL-51829 admin: Set userquota setting as PARAM_INT 2015-10-20 10:01:11 +02:00
Jun Pataleta cef93f97ae MDL-51128 course: Add startdate for generated course
Added course start date for the course generated by the course
generator in order to prevent incorrect date calculations
(i.e. in assignment due dates) when the generated course is reset.
2015-10-12 21:48:27 -05:00
Michael Aherne 1dc462baa5 MDL-51707 cron: Prevent finished tasks being returned 2015-10-08 14:43:56 +01:00
201 changed files with 3605 additions and 1466 deletions
+2 -12
View File
@@ -165,20 +165,10 @@ script:
# The local_ci repository does the actual checking.
git clone https://github.com/moodlehq/moodle-local_ci.git local/ci
# Determine the branch
grep '^$release.*dev' version.php > /dev/null
if [ $? -eq 0 ];
then
# This is master
export branchname='master';
else
# This is a stable branch. Use the version from version.php to determine which one.
export branchname="MOODLE_`grep '^$branch' version.php | sed "s/^.*'\([0-9]*\)'.*$/\1/"`_STABLE";
fi
# We need the official upstream for comparison
git remote add upstream https://github.com/moodle/moodle.git;
git fetch upstream "$branchname";
git fetch upstream master;
export GIT_PREVIOUS_COMMIT="`git merge-base FETCH_HEAD $TRAVIS_COMMIT`";
export GIT_COMMIT="$TRAVIS_COMMIT";
export UPSTREAM_FETCH_HEAD=`git rev-parse FETCH_HEAD`
+2
View File
@@ -39,6 +39,8 @@ require_once($CFG->dirroot . '/' . $CFG->admin . '/registration/forms.php');
require_once($CFG->dirroot . '/webservice/lib.php');
require_once($CFG->dirroot . '/' . $CFG->admin . '/registration/lib.php');
require_sesskey();
$huburl = required_param('huburl', PARAM_URL);
$huburl = rtrim($huburl, "/");
+2 -1
View File
@@ -35,7 +35,8 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
$params = new stdClass();
$params->bytes = $defaultuserquota;
$params->displaysize = display_size($defaultuserquota);
$temp->add(new admin_setting_configtext('userquota', new lang_string('userquota', 'admin'), new lang_string('configuserquota', 'admin', $params), $defaultuserquota));
$temp->add(new admin_setting_configtext('userquota', new lang_string('userquota', 'admin'),
new lang_string('configuserquota', 'admin', $params), $defaultuserquota, PARAM_INT, 30));
$temp->add(new admin_setting_configcheckbox('allowobjectembed', new lang_string('allowobjectembed', 'admin'), new lang_string('configallowobjectembed', 'admin'), 0));
$temp->add(new admin_setting_configcheckbox('enabletrusttext', new lang_string('enabletrusttext', 'admin'), new lang_string('configenabletrusttext', 'admin'), 0));
+1 -1
View File
@@ -11,7 +11,7 @@ $hassiteconfig = has_capability('moodle/site:config', $systemcontext);
$ADMIN->add('root', new admin_externalpage('adminnotifications', new lang_string('notifications'), "$CFG->wwwroot/$CFG->admin/index.php"));
$ADMIN->add('root', new admin_externalpage('registrationmoodleorg', new lang_string('registration', 'admin'),
"$CFG->wwwroot/$CFG->admin/registration/register.php?huburl=" . HUB_MOODLEORGHUBURL . "&hubname=Moodle.org"));
"$CFG->wwwroot/$CFG->admin/registration/register.php?huburl=" . HUB_MOODLEORGHUBURL . "&hubname=Moodle.org&sesskey=" . sesskey()));
$ADMIN->add('root', new admin_externalpage('registrationhub', new lang_string('registerwith', 'hub'),
"$CFG->wwwroot/$CFG->admin/registration/register.php", 'moodle/site:config', true));
$ADMIN->add('root', new admin_externalpage('registrationhubs', new lang_string('hubs', 'admin'),
@@ -253,7 +253,8 @@ class tool_generator_course_backend extends tool_generator_backend {
$courserecord = array(
'shortname' => $this->shortname,
'fullname' => $this->fullname,
'numsections' => self::$paramsections[$this->size]
'numsections' => self::$paramsections[$this->size],
'startdate' => usergetmidnight(time())
);
if (strlen($this->summary) > 0) {
$courserecord['summary'] = $this->summary;
@@ -37,7 +37,7 @@ $string['installfromzip'] = 'Install plugin from ZIP file';
$string['installfromzip_help'] = 'An alternative to installing a plugin directly from the Moodle plugins directory is to upload a ZIP package of the plugin. The ZIP package should have the same structure as a package downloaded from the Moodle plugins directory.';
$string['installfromzipfile'] = 'ZIP package';
$string['installfromzipfile_help'] = 'The plugin ZIP package must contain just one directory, named to match the plugin name. The ZIP will be extracted into an appropriate location for the plugin type. If the package has been downloaded from the Moodle plugins directory then it will have this structure.';
$string['installfromzipinvalid'] = 'The plugin ZIP package must contain just one directory, named to match the plugin name. Provided file is not a valid plugin ZIP package.';
$string['installfromzipinvalid'] = 'The plugin ZIP package must contain just one directory, named to match the plugin name. The file provided is not a valid plugin ZIP package.';
$string['installfromziprootdir'] = 'Rename the root directory';
$string['installfromziprootdir_help'] = 'Some ZIP packages, such as those generated by Github, may contain an incorrect root directory name. If so, the correct name may be entered here.';
$string['installfromzipsubmit'] = 'Install plugin from the ZIP file';
@@ -39,12 +39,12 @@ $string['langpackupdatedevent'] = 'Language pack updated';
$string['langupdatecomplete'] = 'Language pack update completed';
$string['missingcfglangotherroot'] = 'Missing configuration value $CFG->langotherroot';
$string['missinglangparent'] = 'Missing parent language <em>{$a->parent}</em> of <em>{$a->lang}</em>.';
$string['noenglishuninstall'] = 'English language pack can not be uninstalled';
$string['noenglishuninstall'] = 'The English language pack cannot be uninstalled.';
$string['nolangupdateneeded'] = 'All your language packs are up to date, no update is needed';
$string['pluginname'] = 'Language packs';
$string['purgestringcaches'] = 'Purge string caches';
$string['remotelangnotavailable'] = 'Because Moodle cannot connect to download.moodle.org, it is not possible for language packs to be installed automatically. Please download the appropriate ZIP file(s) from <a href="https://download.moodle.org/langpack/">download.moodle.org/langpack</a>, copy them to your {$a} directory and unzip them manually.';
$string['selectlangs'] = 'Select languages to unistall!';
$string['selectlangs'] = 'Select languages to uninstall';
$string['uninstall'] = 'Uninstall selected language pack(s)';
$string['uninstallconfirm'] = 'You are about to completely uninstall these language packs: <strong>{$a}</strong>. Are you sure?';
$string['updatelangs'] = 'Update all installed language packs';
@@ -53,7 +53,7 @@ Feature: Manage language packs
And I navigate to "Language packs" node in "Site administration > Language"
When I set the field "Installed language packs" to "English (en)"
And I press "Uninstall selected language pack(s)"
Then I should see "English language pack can not be uninstalled"
Then I should see "The English language pack cannot be uninstalled."
And I navigate to "Live logs" node in "Site administration > Reports"
And I should not see "Language pack uninstalled"
And I log out
@@ -102,6 +102,10 @@ class manager {
try {
$this->client->login();
mtrace("Connection established.");
// Ensure that mailboxes exist.
$this->ensure_mailboxes_exist();
return true;
} catch (\Horde_Imap_Client_Exception $e) {
@@ -741,6 +745,27 @@ class manager {
return in_array($flag, $flags);
}
/**
* Ensure that all mailboxes exist.
*/
private function ensure_mailboxes_exist() {
$requiredmailboxes = array(
self::MAILBOX,
self::CONFIRMATIONFOLDER,
);
$existingmailboxes = $this->client->listMailboxes($requiredmailboxes);
foreach ($requiredmailboxes as $mailbox) {
if (isset($existingmailboxes[$mailbox])) {
// This mailbox was found.
continue;
}
mtrace("Unable to find the '{$mailbox}' mailbox - creating it.");
$this->client->createMailbox($mailbox);
}
}
/**
* Attempt to determine whether this message is a bulk message (e.g. automated reply).
*
+11 -4
View File
@@ -20,10 +20,6 @@
* This script is meant to be called from a cronjob to sync moodle with the CAS
* backend in those setups where the CAS backend acts as 'master'.
*
* Sample cron entry:
* # 5 minutes past 4am
* 5 4 * * * $sudo -u www-data /usr/bin/php /var/www/moodle/auth/cas/cli/sync_users.php
*
* Notes:
* - it is required to use the web server account when executing PHP CLI scripts
* - you need to change the "www-data" to match the apache user account
@@ -40,12 +36,15 @@
* @package auth_cas
* @copyright 2007 Jerome Gutierrez - based on code by Martin Langhoff
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @deprecated since Moodle 3.0 MDL-51824 - please do not use this CLI script any more, use scheduled task instead.
* @todo MDL-50264 This will be deleted in Moodle 3.2.
*/
define('CLI_SCRIPT', true);
require(dirname(dirname(dirname(dirname(__FILE__)))).'/config.php');
require_once($CFG->dirroot.'/course/lib.php');
require_once($CFG->libdir.'/clilib.php');
// Ensure errors are well explained
set_debugging(DEBUG_DEVELOPER, true);
@@ -55,6 +54,14 @@ if (!is_enabled_auth('cas')) {
die;
}
cli_problem('[AUTH CAS] The sync users cron has been deprecated. Please use the scheduled task instead.');
// Abort execution of the CLI script if the auth_cas\task\sync_task is enabled.
$task = \core\task\manager::get_scheduled_task('auth_cas\task\sync_task');
if (!$task->get_disabled()) {
cli_error('[AUTH CAS] The scheduled task sync_task is enabled, the cron execution has been aborted.');
}
$casauth = get_auth_plugin('cas');
$casauth->sync_users(true);
+11 -4
View File
@@ -20,10 +20,6 @@
* This script is meant to be called from a cronjob to sync moodle with the LDAP
* backend in those setups where the LDAP backend acts as 'master'.
*
* Sample cron entry:
* # 5 minutes past 4am
* 5 4 * * * $sudo -u www-data /usr/bin/php /var/www/moodle/auth/ldap/cli/sync_users.php
*
* Notes:
* - it is required to use the web server account when executing PHP CLI scripts
* - you need to change the "www-data" to match the apache user account
@@ -44,12 +40,15 @@
* @package auth_ldap
* @copyright 2004 Martin Langhoff
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @deprecated since Moodle 3.0 MDL-51824 - please do not use this CLI script any more, use scheduled task instead.
* @todo MDL-50264 This will be deleted in Moodle 3.2.
*/
define('CLI_SCRIPT', true);
require(dirname(dirname(dirname(dirname(__FILE__)))).'/config.php'); // global moodle config file.
require_once($CFG->dirroot.'/course/lib.php');
require_once($CFG->libdir.'/clilib.php');
// Ensure errors are well explained
set_debugging(DEBUG_DEVELOPER, true);
@@ -59,6 +58,14 @@ if (!is_enabled_auth('ldap')) {
die;
}
cli_problem('[AUTH LDAP] The users sync cron has been deprecated. Please use the scheduled task instead.');
// Abort execution of the CLI script if the auth_ldap\task\sync_task is enabled.
$taskdisabled = \core\task\manager::get_scheduled_task('auth_ldap\task\sync_task');
if (!$taskdisabled->get_disabled()) {
cli_error('[AUTH LDAP] The scheduled task sync_task is enabled, the cron execution has been aborted.');
}
$ldapauth = get_auth_plugin('ldap');
$ldapauth->sync_users(true);
+2 -2
View File
@@ -47,7 +47,7 @@ $string['auth_ldap_expiration_desc'] = 'Select No to disable expired password ch
$string['auth_ldap_expiration_key'] = 'Expiration';
$string['auth_ldap_expiration_warning_desc'] = 'Number of days before password expiration warning is issued.';
$string['auth_ldap_expiration_warning_key'] = 'Expiration warning';
$string['auth_ldap_expireattr_desc'] = 'Optional: overrides ldap-attribute that stores password expiration time';
$string['auth_ldap_expireattr_desc'] = 'Optional: Overrides the LDAP attribute that stores password expiration time.';
$string['auth_ldap_expireattr_key'] = 'Expiration attribute';
$string['auth_ldapextrafields'] = 'These fields are optional. You can choose to pre-fill some Moodle user fields with information from the <b>LDAP fields</b> that you specify here. <p>If you leave these fields blank, then nothing will be transferred from LDAP and Moodle defaults will be used instead.</p><p>In either case, the user will be able to edit all of these fields after they log in.</p>';
$string['auth_ldap_graceattr_desc'] = 'Optional: Overrides gracelogin attribute';
@@ -71,7 +71,7 @@ $string['auth_ldap_noconnect_all'] = 'LDAP-module cannot connect to any servers:
$string['auth_ldap_noextension'] = '<em>The PHP LDAP module does not seem to be present. Please ensure it is installed and enabled if you want to use this authentication plugin.</em>';
$string['auth_ldap_no_mbstring'] = 'You need the mbstring extension to create users in Active Directory.';
$string['auth_ldapnotinstalled'] = 'Cannot use LDAP authentication. The PHP LDAP module is not installed.';
$string['auth_ldap_objectclass'] = 'Optional: Overrides objectClass used to name/search users on ldap_user_type. Usually you dont need to chage this.';
$string['auth_ldap_objectclass'] = 'Optional: Overrides objectClass used to name/search users on ldap_user_type. Usually you don\'t need to change this.';
$string['auth_ldap_objectclass_key'] = 'Object class';
$string['auth_ldap_opt_deref'] = 'Determines how aliases are handled during search. Select one of the following values: "No" (LDAP_DEREF_NEVER) or "Yes" (LDAP_DEREF_ALWAYS)';
$string['auth_ldap_opt_deref_key'] = 'Dereference aliases';
+3 -1
View File
@@ -61,6 +61,8 @@ $PAGE->set_heading($badge->name);
$PAGE->set_title($badge->name);
$PAGE->navbar->add($badge->name);
require_capability('moodle/badges:viewbadges', $context);
echo $OUTPUT->header();
echo $OUTPUT->heading(print_badge_image($badge, $context, 'small') . ' ' . $badge->name);
@@ -75,4 +77,4 @@ echo $output->print_badge_status_box($badge);
$output->print_badge_tabs($badgeid, $context, 'overview');
echo $output->print_badge_overview($badge, $context);
echo $OUTPUT->footer();
echo $OUTPUT->footer();
+2
View File
@@ -75,6 +75,8 @@ if ($type == BADGE_TYPE_SITE) {
$PAGE->set_heading($coursename);
}
require_capability('moodle/badges:viewbadges', $PAGE->context);
$PAGE->set_title($title);
$output = $PAGE->get_renderer('core', 'badges');
@@ -92,7 +92,7 @@ class block_activity_results extends block_base {
return $result;
}
// See if it is a gradable activity.
if (($rec->gradetype != GRADE_TYPE_VALUE) || ($rec->gradetype != GRADE_TYPE_SCALE)) {
if (($rec->gradetype != GRADE_TYPE_VALUE) && ($rec->gradetype != GRADE_TYPE_SCALE)) {
return $result;
}
return $rec;
@@ -28,31 +28,44 @@ Feature: The activity results block displays student scores
And I follow "Course 1"
And I turn editing mode on
And I add a "Assignment" to section "1" and I fill the form with:
| Assignment name | Test assignment |
| Assignment name | Test assignment 1 |
| Description | Offline text |
| assignsubmission_file_enabled | 0 |
And I follow "Course 1"
And I add a "Assignment" to section "1" and I fill the form with:
| Assignment name | Test assignment 2 |
| Description | Offline text |
| assignsubmission_file_enabled | 0 |
And I follow "Course 1"
And I add a "Assignment" to section "1" and I fill the form with:
| Assignment name | Test assignment 3 |
| Description | Offline text |
| assignsubmission_file_enabled | 0 |
And I follow "Course 1"
And I add a "Page" to section "1"
And I set the following fields to these values:
| Name | Test page name |
| Description | Test page description |
| Page content | This is a page |
And I press "Save and return to course"
And I follow "Course 1"
And I should see "Test page name"
And I navigate to "Grades" node in "Course administration"
And I turn editing mode on
And I give the grade "90.00" to the user "Student 1" for the grade item "Test assignment"
And I give the grade "80.00" to the user "Student 2" for the grade item "Test assignment"
And I give the grade "70.00" to the user "Student 3" for the grade item "Test assignment"
And I give the grade "60.00" to the user "Student 4" for the grade item "Test assignment"
And I give the grade "50.00" to the user "Student 5" for the grade item "Test assignment"
And I give the grade "90.00" to the user "Student 1" for the grade item "Test assignment 1"
And I give the grade "80.00" to the user "Student 2" for the grade item "Test assignment 1"
And I give the grade "70.00" to the user "Student 3" for the grade item "Test assignment 1"
And I give the grade "60.00" to the user "Student 4" for the grade item "Test assignment 1"
And I give the grade "50.00" to the user "Student 5" for the grade item "Test assignment 1"
And I press "Save changes"
And I follow "Course 1"
Scenario: Configure the block on a non-graded activity to show 3 high scores
Given I add a "Page" to section "1"
Given I follow "Test page name"
And I add the "Activity results" block
When I configure the "Activity results" block
And I set the following fields to these values:
| Name | Test page name |
| Description | Test page description |
| page | This is a page |
And I press "Save and display"
When I add the "Activity results" block
And I configure the "Activity results" block
And I set the following fields to these values:
| id_config_activitygradeitemid | Test assignment |
| id_config_activitygradeitemid | Test assignment 1 |
| id_config_showbest | 3 |
| id_config_showworst | 0 |
| id_config_gradeformat | Absolute numbers |
@@ -63,4 +76,25 @@ Scenario: Configure the block on a non-graded activity to show 3 high scores
And I should see "Student 2" in the "Activity results" "block"
And I should see "80.00" in the "Activity results" "block"
And I should see "Student 3" in the "Activity results" "block"
And I should see "70.00" in the "Activity results" "block"
And I should see "70.00" in the "Activity results" "block"
Scenario: Block should select current activity by default
Given I follow "Test assignment 1"
When I add the "Activity results" block
And I configure the "Activity results" block
Then the field "id_config_activitygradeitemid" matches value "Test assignment 1"
And I follow "Course 1"
And I follow "Test assignment 2"
And I add the "Activity results" block
And I configure the "Activity results" block
And the field "id_config_activitygradeitemid" matches value "Test assignment 2"
And I follow "Course 1"
And I follow "Test assignment 3"
And I add the "Activity results" block
And I configure the "Activity results" block
And the field "id_config_activitygradeitemid" matches value "Test assignment 3"
And I follow "Course 1"
And I follow "Test page name"
And I add the "Activity results" block
And I configure the "Activity results" block
And the field "id_config_activitygradeitemid" does not match value "Test page name"
+10 -2
View File
@@ -23,6 +23,12 @@
*/
class block_course_summary extends block_base {
/**
* @var bool Flag to indicate whether the header should be hidden or not.
*/
private $headerhidden = true;
function init() {
$this->title = get_string('pluginname', 'block_course_summary');
}
@@ -32,8 +38,10 @@ class block_course_summary extends block_base {
}
function specialization() {
if($this->page->pagetype == PAGE_COURSE_VIEW && $this->page->course->id != SITEID) {
// Page type starts with 'course-view' and the page's course ID is not equal to the site ID.
if (strpos($this->page->pagetype, PAGE_COURSE_VIEW) === 0 && $this->page->course->id != SITEID) {
$this->title = get_string('coursesummary', 'block_course_summary');
$this->headerhidden = false;
}
}
@@ -71,7 +79,7 @@ class block_course_summary extends block_base {
}
function hide_header() {
return true;
return $this->headerhidden;
}
}
@@ -25,19 +25,22 @@ Feature: Course summary block used in a course
Scenario: Student can view course summary
When I log in as "student1"
And I follow "Course 1"
Then "Course/site summary" "block" should exist
And I should see "Proved the course summary block works!" in the "Course/site summary" "block"
Then "Course summary" "block" should exist
And I should see "Course summary" in the "Course summary" "block"
And I should see "Proved the course summary block works!" in the "Course summary" "block"
Scenario: Teacher can see an edit icon when edit mode is on and follow it to the course edit page
When I log in as "teacher1"
And I follow "Course 1"
And I turn editing mode on
Then I should see "Proved the course summary block works!" in the "Course/site summary" "block"
And I click on "Edit" "link" in the "Course/site summary" "block"
Then I should see "Proved the course summary block works!" in the "Course summary" "block"
And I should see "Course summary" in the "Course summary" "block"
And I click on "Edit" "link" in the "Course summary" "block"
Then I should see "Edit course settings" in the "h2" "css_element"
Scenario: Teacher can not see edit icon when edit mode is off
When I log in as "teacher1"
And I follow "Course 1"
Then I should see "Proved the course summary block works!" in the "Course/site summary" "block"
And "Edit" "link" should not exist in the "Course/site summary" "block"
Then I should see "Proved the course summary block works!" in the "Course summary" "block"
And I should see "Course summary" in the "Course summary" "block"
And "Edit" "link" should not exist in the "Course summary" "block"
@@ -17,6 +17,7 @@ Feature: Course summary block used on the frontpage
Scenario: Guest can view site summary
When I am on site homepage
Then "Course/site summary" "block" should exist
And I should not see "Course summary" in the "Course/site summary" "block"
And I should see "Proved the summary block works!" in the "Course/site summary" "block"
Scenario: Admin can see an edit icon when edit mode is on and follow it to the front page settings
@@ -24,6 +25,7 @@ Feature: Course summary block used on the frontpage
And I am on site homepage
And I follow "Turn editing on"
Then I should see "Proved the summary block works!" in the "Course/site summary" "block"
And I should not see "Course summary" in the "Course/site summary" "block"
And I click on "Edit" "link" in the "Course/site summary" "block"
Then I should see "Front page settings" in the "h2" "css_element"
@@ -31,4 +33,5 @@ Feature: Course summary block used on the frontpage
When I log in as "admin"
And I am on site homepage
Then I should see "Proved the summary block works!" in the "Course/site summary" "block"
And I should not see "Course summary" in the "Course/site summary" "block"
And "Edit" "link" should not exist in the "Course/site summary" "block"
@@ -39,11 +39,11 @@ Feature: Expand the courses nodes within the navigation block
And I log in as "admin"
And I am on site homepage
And I follow "Course 2"
And I turn editing mode on
And I click on "Edit settings" "link" in the "Administration" "block"
And I navigate to "Enrolment methods" node in "Course administration > Users"
And I click on "Edit" "link" in the "Guest access" "table_row"
And I set the following fields to these values:
| Allow guest access | Yes |
And I press "Save and display"
And I press "Save changes"
And I log out
@javascript
@@ -23,7 +23,7 @@
*/
$string['anycategory'] = 'Any category';
$string['apierror'] = 'The Youtube API key is not set. Contact your administrator.';
$string['apierror'] = 'The YouTube API key is not set. Contact your administrator.';
$string['apikey'] = 'API key';
$string['apikeyinfo'] = 'Get a <a href="https://developers.google.com/youtube/v3/getting-started">Google API key</a> for your Moodle site.';
$string['autosvehicles'] = 'Autos &amp; Vehicles';
@@ -42,7 +42,7 @@ $string['numberofvideos'] = 'Number of videos';
$string['peopleblogs'] = 'People &amp; Blogs';
$string['petsanimals'] = 'Pets &amp; Animals';
$string['pluginname'] = 'YouTube';
$string['requesterror'] = 'Data could not be obtained from the server. Contact your administrator if the problem persist.';
$string['requesterror'] = 'Data could not be obtained from the server. Contact your administrator if the problem persists.';
$string['scienceandtech'] = 'Science &amp; Tech';
$string['sports'] = 'Sports';
$string['tag_youtube:addinstance'] = 'Add a new YouTube block';
+4
View File
@@ -26,6 +26,7 @@
require_once(dirname(dirname(__FILE__)).'/config.php');
require_once('lib.php');
require_once('locallib.php');
require_once($CFG->dirroot .'/comment/lib.php');
$action = required_param('action', PARAM_ALPHA);
$id = optional_param('entryid', 0, PARAM_INT);
@@ -115,6 +116,9 @@ $output = $PAGE->get_renderer('blog');
$strblogs = get_string('blogs', 'blog');
if ($action === 'delete') {
// Init comment JS strings.
comment::init();
if (empty($entry->id)) {
print_error('wrongentryid', 'blog');
}
@@ -232,6 +232,20 @@ abstract class completion_criteria extends data_object {
*/
abstract public function get_details($completion);
/**
* Return pix_icon for display in reports.
*
* @param string $alt The alt text to use for the icon
* @param array $attributes html attributes
* @return pix_icon
*/
public function get_icon($alt, array $attributes = null) {
global $COMPLETION_CRITERIA_TYPES;
$criteriatype = $COMPLETION_CRITERIA_TYPES[$this->criteriatype];
return new pix_icon('i/'.$criteriatype, $alt, 'moodle', $attributes);
}
/**
* Return criteria status text for display in reports
*
@@ -288,4 +288,15 @@ class completion_criteria_activity extends completion_criteria {
return $details;
}
/**
* Return pix_icon for display in reports.
*
* @param string $alt The alt text to use for the icon
* @param array $attributes html attributes
* @return pix_icon
*/
public function get_icon($alt, array $attributes = null) {
return new pix_icon('icon', $alt, 'mod_'.$this->module, $attributes);
}
}
@@ -202,4 +202,15 @@ class completion_criteria_date extends completion_criteria {
return $details;
}
/**
* Return pix_icon for display in reports.
*
* @param string $alt The alt text to use for the icon
* @param array $attributes html attributes
* @return pix_icon
*/
public function get_icon($alt, array $attributes = null) {
return new pix_icon('i/calendar', $alt, 'moodle', $attributes);
}
}
@@ -248,4 +248,15 @@ class completion_criteria_grade extends completion_criteria {
return $details;
}
/**
* Return pix_icon for display in reports.
*
* @param string $alt The alt text to use for the icon
* @param array $attributes html attributes
* @return pix_icon
*/
public function get_icon($alt, array $attributes = null) {
return new pix_icon('i/grades', $alt, 'moodle', $attributes);
}
}
@@ -149,4 +149,15 @@ class completion_criteria_self extends completion_criteria {
return $details;
}
/**
* Return pix_icon for display in reports.
*
* @param string $alt The alt text to use for the icon
* @param array $attributes html attributes
* @return pix_icon
*/
public function get_icon($alt, array $attributes = null) {
return new pix_icon('i/completion_self', $alt, 'moodle', $attributes);
}
}
@@ -131,4 +131,15 @@ class completion_criteria_unenrol extends completion_criteria {
$details['status'] = '';
return $details;
}
/**
* Return pix_icon for display in reports.
*
* @param string $alt The alt text to use for the icon
* @param array $attributes html attributes
* @return pix_icon
*/
public function get_icon($alt, array $attributes = null) {
return new pix_icon('i/user', $alt, 'moodle', $attributes);
}
}
+1 -1
View File
@@ -104,7 +104,7 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
$url = empty($value['url']) ? '' : $value['url'];
$icon = empty($value['icon']) ? '' : $value['icon'];
$name = empty($value['name']) ? '' : $value['name'];
$attr = empty($value['attr']) ? '' : $value['attr'];
$attr = empty($value['attr']) ? array() : $value['attr'];
$class = empty($item['pixattr']['class']) ? '' : $item['pixattr']['class'];
$alt = empty($item['pixattr']['alt']) ? '' : $item['pixattr']['alt'];
$al = new action_menu_link_secondary(
+28 -6
View File
@@ -378,7 +378,10 @@ class core_enrol_external extends external_api {
)
), 'Option names:
* withcapability (string) return only users with this capability. This option requires \'moodle/role:review\' on the course context.
* groupid (integer) return only users in this group id. This option requires \'moodle/site:accessallgroups\' on the course context.
* groupid (integer) return only users in this group id. If the course has groups enabled and this param
isn\'t defined, returns all the viewable users.
This option requires \'moodle/site:accessallgroups\' on the course context if the
user doesn\'t belong to the group.
* onlyactive (integer) return only users with active enrolments and matching time restrictions. This option requires \'moodle/course:enrolreview\' on the course context.
* userfields (\'string, string, ...\') return only the values of these user fields.
* limitfrom (integer) sql limit from.
@@ -466,7 +469,7 @@ class core_enrol_external extends external_api {
require_capability('moodle/role:review', $coursecontext);
}
// need accessallgroups capability if you want to overwrite this option
if (!empty($groupid) && groups_is_member($groupid)) {
if (!empty($groupid) && !groups_is_member($groupid)) {
require_capability('moodle/site:accessallgroups', $coursecontext);
}
// to overwrite this option, you need course:enrolereview permission
@@ -478,10 +481,29 @@ class core_enrol_external extends external_api {
$ctxselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
$ctxjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = u.id AND ctx.contextlevel = :contextlevel)";
$enrolledparams['contextlevel'] = CONTEXT_USER;
$sql = "SELECT u.* $ctxselect
FROM {user} u $ctxjoin
WHERE u.id IN ($enrolledsql)
ORDER BY u.id ASC";
$groupjoin = '';
if (empty($groupid) && groups_get_course_groupmode($course) == SEPARATEGROUPS &&
!has_capability('moodle/site:accessallgroups', $coursecontext)) {
// Filter by groups the user can view.
$usergroups = groups_get_user_groups($course->id);
if (!empty($usergroups['0'])) {
list($groupsql, $groupparams) = $DB->get_in_or_equal($usergroups['0'], SQL_PARAMS_NAMED);
$groupjoin = "JOIN {groups_members} gm ON (u.id = gm.userid AND gm.groupid $groupsql)";
$enrolledparams = array_merge($enrolledparams, $groupparams);
} else {
// User doesn't belong to any group, so he can't see any user. Return an empty array.
return array();
}
}
$sql = "SELECT us.*
FROM {user} us
JOIN (
SELECT DISTINCT u.id $ctxselect
FROM {user} u $ctxjoin $groupjoin
WHERE u.id IN ($enrolledsql)
) q ON q.id = us.id
ORDER BY us.id ASC";
$enrolledusers = $DB->get_recordset_sql($sql, $enrolledparams, $limitfrom, $limitnumber);
$users = array();
foreach ($enrolledusers as $user) {
+5 -1
View File
@@ -172,7 +172,11 @@ class course_enrolment_manager {
FROM {user} u
JOIN {user_enrolments} ue ON (ue.userid = u.id AND ue.enrolid $instancessql)
JOIN {enrol} e ON (e.id = ue.enrolid)
LEFT JOIN {groups_members} gm ON u.id = gm.userid
LEFT JOIN {groups_members} gm ON u.id = gm.userid AND gm.groupid IN (
SELECT g.id
FROM {groups} g
WHERE g.courseid = e.courseid
)
WHERE $filtersql";
$this->totalusers = (int)$DB->count_records_sql($sqltotal, $params);
}
+2 -2
View File
@@ -101,9 +101,9 @@ $string['sendcoursewelcomemessage'] = 'Send course welcome message';
$string['sendcoursewelcomemessage_help'] = 'If enabled, users receive a welcome message via email when they self-enrol in a course.';
$string['showhint'] = 'Show hint';
$string['showhint_desc'] = 'Show first letter of the guest access key.';
$string['status'] = 'Enable existing enrolments';
$string['status'] = 'Allow existing enrolments';
$string['status_desc'] = 'Enable self enrolment method in new courses.';
$string['status_help'] = 'If disabled all existing self enrolments are suspended and new users can not enrol.';
$string['status_help'] = 'If enabled together with \'Allow new enrolments\' disabled, only users who self enrolled previously can access the course. If disabled, this self enrolment method is effectively disabled, since all existing self enrolments are suspended and new users cannot self enrol.';
$string['unenrol'] = 'Unenrol user';
$string['unenrolselfconfirm'] = 'Do you really want to unenrol yourself from course "{$a}"?';
$string['unenroluser'] = 'Do you really want to unenrol "{$a->user}" from course "{$a->course}"?';
@@ -27,7 +27,7 @@ Feature: Users can auto-enrol themself in courses where self enrolment is allowe
And I log out
When I follow "Course 1"
And I press "Log in as a guest"
Then I should see "Guests can not access this course, please try to log in."
Then I should see "Guests cannot access this course. Please log in."
And I press "Continue"
And I should see "Log in"
@@ -50,11 +50,12 @@ Feature: Enrolled users can be filtered by group
And I should see "<expected3>"
And I should not see "<notexpected1>"
And I should not see "<notexpected2>"
And I should see "<expected4>"
# Note the 'XX-IGNORE-XX' elements are for when there is less than 2 'not expected' items.
Examples:
| group | expected1 | expected2 | expected3 | notexpected1 | notexpected2 |
| All participants | Student 1 | Student 2 | Student 3 | XX-IGNORE-XX | XX-IGNORE-XX |
| No group | Student 1 | | | Student 2 | Student 3 |
| Group 1 | Student 2 | | | Student 1 | Student 3 |
| Group 2 | Student 2 | Student 3 | | Student 1 | XX-IGNORE-XX |
| group | expected1 | expected2 | expected3 | expected4 | notexpected1 | notexpected2 |
| All participants | Student 1 | Student 2 | Student 3 | 4 enrolled users | XX-IGNORE-XX | XX-IGNORE-XX |
| No group | Student 1 | | | 2 enrolled users | Student 2 | Student 3 |
| Group 1 | Student 2 | | | 1 enrolled users | Student 1 | Student 3 |
| Group 2 | Student 2 | Student 3 | | 2 enrolled users | Student 1 | XX-IGNORE-XX |
+490 -129
View File
@@ -33,59 +33,325 @@ require_once($CFG->dirroot . '/enrol/externallib.php');
class core_enrol_externallib_testcase extends externallib_advanced_testcase {
/**
* Test get_enrolled_users
* dataProvider for test_get_enrolled_users_visibility().
*/
public function test_get_enrolled_users() {
public function get_enrolled_users_visibility_provider() {
return array(
'Course without groups, default behavior (not filtering by cap, group, active)' =>
array(
'settings' => array(
'coursegroupmode' => NOGROUPS,
'withcapability' => null,
'groupid' => null,
'onlyactive' => false,
'allowedcaps' => array(),
),
'results' => array( // Everybody can view everybody.
'user0' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
'user1' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
'user2' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
'user31' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
'userall' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
),
),
'Course with visible groups, default behavior (not filtering by cap, group, active)' =>
array(
'settings' => array(
'coursegroupmode' => VISIBLEGROUPS,
'withcapability' => null,
'groupid' => null,
'onlyactive' => false,
'allowedcaps' => array(),
),
'results' => array( // Everybody can view everybody.
'user0' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
'user1' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
'user2' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
'user31' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
'userall' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
),
),
'Course with separate groups, default behavior (not filtering by cap, group, active)' =>
array(
'settings' => array(
'coursegroupmode' => SEPARATEGROUPS,
'withcapability' => null,
'groupid' => null,
'onlyactive' => false,
'allowedcaps' => array(),
),
'results' => array( // Only users from own groups are visible.
'user0' => array('canview' => array()), // Poor guy, cannot see anybody, himself included.
'user1' => array('canview' => array('user1', 'userall')),
'user2' => array('canview' => array('user2', 'user2su', 'userall')),
'user31' => array('canview' => array('user31', 'user32', 'userall')),
'userall' => array('canview' => array('user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
),
),
'Course with separate groups, default behavior (not filtering but having moodle/site:accessallgroups)' =>
array(
'settings' => array(
'coursegroupmode' => VISIBLEGROUPS,
'withcapability' => null,
'groupid' => null,
'onlyactive' => false,
'allowedcaps' => array('moodle/site:accessallgroups'),
),
'results' => array( // Everybody can view everybody.
'user0' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
'user1' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
'user2' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
'user31' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
'userall' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
),
),
'Course with separate groups, filtering onlyactive (missing moodle/course:enrolreview)' =>
array(
'settings' => array(
'coursegroupmode' => SEPARATEGROUPS,
'withcapability' => null,
'groupid' => null,
'onlyactive' => true,
'allowedcaps' => array(),
),
'results' => array( // returns exception, cannot view anybody without the cap.
'user2' => array('exception' => array(
'type' => 'required_capability_exception',
'message' => 'Review course enrolments')),
'userall' => array('exception' => array(
'type' => 'required_capability_exception',
'message' => 'Review course enrolments')),
),
),
'Course with separate groups, filtering onlyactive (having moodle/course:enrolreview)' =>
array(
'settings' => array(
'coursegroupmode' => SEPARATEGROUPS,
'withcapability' => null,
'groupid' => null,
'onlyactive' => true,
'allowedcaps' => array('moodle/course:enrolreview'),
),
'results' => array( // Suspended are not returned.
'user2' => array('canview' => array('user2', 'userall')),
'user31' => array('canview' => array('user31', 'user32', 'userall')),
'userall' => array('canview' => array('user1', 'user2', 'user31', 'user32', 'userall')),
),
),
'Course with separate groups, filtering by groupid (not having moodle/site:accessallgroups)' =>
array(
'settings' => array(
'coursegroupmode' => SEPARATEGROUPS,
'withcapability' => null,
'groupid' => 'group2',
'onlyactive' => false,
'allowedcaps' => array(),
),
'results' => array( // Only group 2 members and only for members. Exception for non-members.
'user0' => array('exception' => array(
'type' => 'required_capability_exception',
'message' => 'Access all groups')),
'user1' => array('exception' => array(
'type' => 'required_capability_exception',
'message' => 'Access all groups')),
'user2' => array('canview' => array('user2', 'user2su', 'userall')),
'userall' => array('canview' => array('user2', 'user2su', 'userall')),
),
),
'Course with separate groups, filtering by groupid (having moodle/site:accessallgroups)' =>
array(
'settings' => array(
'coursegroupmode' => SEPARATEGROUPS,
'withcapability' => null,
'groupid' => 'group2',
'onlyactive' => false,
'allowedcaps' => array('moodle/site:accessallgroups'),
),
'results' => array( // All users with 'moodle/site:accessallgroups' can view group 2
'user0' => array('canview' => array('user2', 'user2su', 'userall')),
'user1' => array('canview' => array('user2', 'user2su', 'userall')),
'user2' => array('canview' => array('user2', 'user2su', 'userall')),
'userall' => array('canview' => array('user2', 'user2su', 'userall')),
),
),
'Course with separate groups, filtering by withcapability (not having moodle/role:review)' =>
array(
'settings' => array(
'coursegroupmode' => SEPARATEGROUPS,
'withcapability' => 'moodle/course:bulkmessaging',
'groupid' => null,
'onlyactive' => false,
'allowedcaps' => array(),
),
'results' => array( // No user has 'moodle/role:review' so exception.
'user0' => array('exception' => array(
'type' => 'required_capability_exception',
'message' => 'Review permissions for others')),
'user1' => array('exception' => array(
'type' => 'required_capability_exception',
'message' => 'Review permissions for others')),
'user2' => array('exception' => array(
'type' => 'required_capability_exception',
'message' => 'Review permissions for others')),
'userall' => array('exception' => array(
'type' => 'required_capability_exception',
'message' => 'Review permissions for others')),
),
),
'Course with separate groups, filtering by withcapability (having moodle/role:review)' =>
array(
'settings' => array(
'coursegroupmode' => SEPARATEGROUPS,
'withcapability' => 'moodle/course:bulkmessaging',
'groupid' => null,
'onlyactive' => false,
'allowedcaps' => array('moodle/role:review'),
),
'results' => array( // No user has withcapability, but all have 'moodle/role:review'. Empties.
'user0' => array('canview' => array()),
'user1' => array('canview' => array()),
'user2' => array('canview' => array()),
'userall' => array('canview' => array()),
),
),
'Course with separate groups, filtering by withcapability (having moodle/role:review)' =>
array(
'settings' => array(
'coursegroupmode' => SEPARATEGROUPS,
'withcapability' => 'moodle/course:bulkmessaging',
'groupid' => null,
'onlyactive' => false,
'allowedcaps' => array('moodle/role:review', 'moodle/course:bulkmessaging'),
),
'results' => array( // Users (previous) have withcapability, and all have 'moodle/role:review'.
'user0' => array('canview' => array()),
'user1' => array('canview' => array('user1')),
'user2' => array('canview' => array('user2')),
'userall' => array('canview' => array('user1', 'user2', 'userall')),
),
),
);
}
/**
* Verify get_enrolled_users() returned users are the expected in every situation.
*
* @dataProvider get_enrolled_users_visibility_provider
*/
public function test_get_enrolled_users_visibility($settings, $results) {
global $USER;
$this->resetAfterTest(true);
$this->resetAfterTest();
$course = self::getDataGenerator()->create_course();
$user1 = self::getDataGenerator()->create_user();
$user2 = self::getDataGenerator()->create_user();
$user3 = self::getDataGenerator()->create_user();
$this->setUser($user3);
// Create the course and the users.
$course = $this->getDataGenerator()->create_course(array('groupmode' => $settings['coursegroupmode']));
$coursecontext = context_course::instance($course->id);
$user0 = $this->getDataGenerator()->create_user(array('username' => 'user0')); // A user without group.
$user1 = $this->getDataGenerator()->create_user(array('username' => 'user1')); // User for group 1.
$user2 = $this->getDataGenerator()->create_user(array('username' => 'user2')); // Two users for group 2.
$user2su = $this->getDataGenerator()->create_user(array('username' => 'user2su')); // (one suspended).
$user31 = $this->getDataGenerator()->create_user(array('username' => 'user31')); // Two users for group 3.
$user32 = $this->getDataGenerator()->create_user(array('username' => 'user32')); // (both enabled).
$userall = $this->getDataGenerator()->create_user(array('username' => 'userall')); // A user in all groups.
// Set the required capabilities by the external function.
$context = context_course::instance($course->id);
$roleid = $this->assignUserCapability('moodle/course:viewparticipants', $context->id);
$this->assignUserCapability('moodle/user:viewdetails', $context->id, $roleid);
// Create utility array of created users, to produce better assertion messages.
$createdusers = array();
foreach (array($user0, $user1, $user2, $user2su, $user31, $user32, $userall) as $createduser) {
$createdusers[$createduser->id] = $createduser->username;
}
// Enrol the users in the course.
$this->getDataGenerator()->enrol_user($user1->id, $course->id, $roleid, 'manual');
$this->getDataGenerator()->enrol_user($user2->id, $course->id, $roleid, 'manual');
$this->getDataGenerator()->enrol_user($user3->id, $course->id, $roleid, 'manual');
$this->getDataGenerator()->enrol_user($user0->id, $course->id);
$this->getDataGenerator()->enrol_user($user1->id, $course->id);
$this->getDataGenerator()->enrol_user($user2->id, $course->id);
$this->getDataGenerator()->enrol_user($user2su->id, $course->id, null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
$this->getDataGenerator()->enrol_user($user31->id, $course->id);
$this->getDataGenerator()->enrol_user($user32->id, $course->id);
$this->getDataGenerator()->enrol_user($userall->id, $course->id);
// Call the external function.
$enrolledusers = core_enrol_external::get_enrolled_users($course->id);
// Create 3 groups.
$group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
$group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
$group3 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
// We need to execute the return values cleaning process to simulate the web service server.
$enrolledusers = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_returns(), $enrolledusers);
// Add the users to the groups.
$this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $user1->id));
$this->getDataGenerator()->create_group_member(array('groupid' => $group2->id, 'userid' => $user2->id));
$this->getDataGenerator()->create_group_member(array('groupid' => $group2->id, 'userid' => $user2su->id));
$this->getDataGenerator()->create_group_member(array('groupid' => $group3->id, 'userid' => $user31->id));
$this->getDataGenerator()->create_group_member(array('groupid' => $group3->id, 'userid' => $user32->id));
$this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $userall->id));
$this->getDataGenerator()->create_group_member(array('groupid' => $group2->id, 'userid' => $userall->id));
$this->getDataGenerator()->create_group_member(array('groupid' => $group3->id, 'userid' => $userall->id));
// Check the result set.
$this->assertEquals(3, count($enrolledusers));
$this->assertArrayHasKey('email', $enrolledusers[0]);
// Create a role to add the allowedcaps. Users will have this role assigned.
$roleid = $this->getDataGenerator()->create_role();
// Allow the specified capabilities.
if (!empty($settings['allowedcaps'])) {
foreach ($settings['allowedcaps'] as $capability) {
assign_capability($capability, CAP_ALLOW, $roleid, $coursecontext);
}
}
// Call the function with some parameters set.
$enrolledusers = core_enrol_external::get_enrolled_users($course->id, array(
array('name' => 'limitfrom', 'value' => 2),
array('name' => 'limitnumber', 'value' => 1),
array('name' => 'userfields', 'value' => 'id')
));
// For each of the users, configure everything, perform the call, and assert results.
foreach ($results as $user => $expectations) {
// Convert canview expectations into a nice array of ids for easier handling.
$canview = array();
$exception = null;
// Analyse the expectations.
if (isset($expectations['canview'])) {
foreach ($expectations['canview'] as $canviewuser) {
$canview[] = $createdusers[${$canviewuser}->id];
}
} else if (isset($expectations['exception'])) {
$exception = $expectations['exception'];
$this->setExpectedException($exception['type'], $exception['message']);
} else {
// Failed, only canview and exception are supported.
$this->markTestIncomplete('Incomplete, only canview and exception are supported');
}
// Switch to the user and assign the role.
$this->setUser(${$user});
role_assign($roleid, $USER->id, $coursecontext);
// We need to execute the return values cleaning process to simulate the web service server.
$enrolledusers = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_returns(), $enrolledusers);
// Convert groupid to proper id.
$groupid = 0;
if (isset($settings['groupid'])) {
$groupid = ${$settings['groupid']}->id;
}
// Check the result set, we should only get the 3rd result, which is $user3.
$this->assertCount(1, $enrolledusers);
$this->assertEquals($user3->id, $enrolledusers[0]['id']);
$this->assertArrayHasKey('id', $enrolledusers[0]);
$this->assertArrayNotHasKey('email', $enrolledusers[0]);
// Call to the function.
$options = array(
array('name' => 'withcapability', 'value' => $settings['withcapability']),
array('name' => 'groupid', 'value' => $groupid),
array('name' => 'onlyactive', 'value' => $settings['onlyactive']),
array('name' => 'userfields', 'value' => 'id')
);
$enrolledusers = core_enrol_external::get_enrolled_users($course->id, $options);
// Call without required capability.
$this->unassignUserCapability('moodle/course:viewparticipants', $context->id, $roleid);
$this->setExpectedException('moodle_exception');
$categories = core_enrol_external::get_enrolled_users($course->id);
// We need to execute the return values cleaning process to simulate the web service server.
$enrolledusers = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_returns(), $enrolledusers);
// We are only interested in ids to check visibility.
$viewed = array();
// Verify the user canview the expected users.
foreach ($enrolledusers as $enrolleduser) {
$viewed[] = $createdusers[$enrolleduser['id']];
}
// Verify viewed matches canview expectation (using canonicalize to ignore ordering).
$this->assertEquals($canview, $viewed, "Problem checking visible users for '{$createdusers[$USER->id]}'", 0, 1, true);
}
}
/**
@@ -144,96 +410,6 @@ class core_enrol_externallib_testcase extends externallib_advanced_testcase {
}
}
/**
* Test get_enrolled_users_with_capability
*/
public function test_get_enrolled_users_with_capability () {
global $DB, $USER;
$this->resetAfterTest(true);
$user1 = $this->getDataGenerator()->create_user();
$coursedata['idnumber'] = 'idnumbercourse1';
$coursedata['fullname'] = 'Lightwork Course 1';
$coursedata['summary'] = 'Lightwork Course 1 description';
$coursedata['summaryformat'] = FORMAT_MOODLE;
$course1 = self::getDataGenerator()->create_course($coursedata);
// Create a manual enrolment record.
$manual_enrol_data['enrol'] = 'manual';
$manual_enrol_data['status'] = 0;
$manual_enrol_data['courseid'] = $course1->id;
$enrolid = $DB->insert_record('enrol', $manual_enrol_data);
// Create the users and give them capabilities in the course context.
$context = context_course::instance($course1->id);
$roleid = $this->assignUserCapability('moodle/course:viewparticipants', $context->id, 3);
// Create 2 students.
$student1 = self::getDataGenerator()->create_user();
$student2 = self::getDataGenerator()->create_user();
// Give the capability to student2.
assign_capability('moodle/course:viewparticipants', CAP_ALLOW, 3, $context->id);
role_assign(3, $student2->id, $context->id);
accesslib_clear_all_caches_for_unit_testing();
// Enrol both the user and the students in the course.
$user_enrolment_data['status'] = 0;
$user_enrolment_data['enrolid'] = $enrolid;
$user_enrolment_data['userid'] = $USER->id;
$DB->insert_record('user_enrolments', $user_enrolment_data);
$user_enrolment_data['status'] = 0;
$user_enrolment_data['enrolid'] = $enrolid;
$user_enrolment_data['userid'] = $student1->id;
$DB->insert_record('user_enrolments', $user_enrolment_data);
$user_enrolment_data['status'] = 0;
$user_enrolment_data['enrolid'] = $enrolid;
$user_enrolment_data['userid'] = $student2->id;
$DB->insert_record('user_enrolments', $user_enrolment_data);
$params = array("coursecapabilities" => array('courseid' => $course1->id,
'capabilities' => array('moodle/course:viewparticipants')));
$options = array();
$result = core_enrol_external::get_enrolled_users_with_capability($params, $options);
// We need to execute the return values cleaning process to simulate the web service server.
$result = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_with_capability_returns(), $result);
// Check an array containing the expected user for the course capability is returned.
$expecteduserlist = $result[0];
$this->assertEquals($course1->id, $expecteduserlist['courseid']);
$this->assertEquals('moodle/course:viewparticipants', $expecteduserlist['capability']);
$this->assertEquals(2, count($expecteduserlist['users']));
// Now doing the query again with options.
$params = array(
"coursecapabilities" => array(
'courseid' => $course1->id,
'capabilities' => array('moodle/course:viewparticipants')
)
);
$options = array(
array('name' => 'limitfrom', 'value' => 1),
array('name' => 'limitnumber', 'value' => 1),
array('name' => 'userfields', 'value' => 'id')
);
$result = core_enrol_external::get_enrolled_users_with_capability($params, $options);
// We need to execute the return values cleaning process to simulate the web service server.
$result = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_with_capability_returns(), $result);
// Check an array containing the expected user for the course capability is returned.
$expecteduserlist = $result[0]['users'];
$expecteduser = reset($expecteduserlist);
$this->assertEquals(1, count($expecteduserlist));
$this->assertEquals($student2->id, $expecteduser['id']);
}
/**
* Test get_course_enrolment_methods
*/
@@ -299,4 +475,189 @@ class core_enrol_externallib_testcase extends externallib_advanced_testcase {
$this->assertTrue($enrolmentmethod['status']);
$this->assertEquals('enrol_self_get_instance_info', $enrolmentmethod['wsfunction']);
}
public function get_enrolled_users_setup($capability) {
global $USER;
$this->resetAfterTest(true);
$return = new stdClass();
$return->course = self::getDataGenerator()->create_course();
$return->user1 = self::getDataGenerator()->create_user();
$return->user2 = self::getDataGenerator()->create_user();
$return->user3 = self::getDataGenerator()->create_user();
$this->setUser($return->user3);
// Set the required capabilities by the external function.
$return->context = context_course::instance($return->course->id);
$return->roleid = $this->assignUserCapability($capability, $return->context->id);
$this->assignUserCapability('moodle/user:viewdetails', $return->context->id, $return->roleid);
// Enrol the users in the course.
$this->getDataGenerator()->enrol_user($return->user1->id, $return->course->id, $return->roleid, 'manual');
$this->getDataGenerator()->enrol_user($return->user2->id, $return->course->id, $return->roleid, 'manual');
$this->getDataGenerator()->enrol_user($return->user3->id, $return->course->id, $return->roleid, 'manual');
return $return;
}
/**
* Test get_enrolled_users from core_enrol_external without additional
* parameters.
*/
public function test_get_enrolled_users_without_parameters() {
$capability = 'moodle/course:viewparticipants';
$data = $this->get_enrolled_users_setup($capability);
// Call the external function.
$enrolledusers = core_enrol_external::get_enrolled_users($data->course->id);
// We need to execute the return values cleaning process to simulate the web service server.
$enrolledusers = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_returns(), $enrolledusers);
// Check the result set.
$this->assertEquals(3, count($enrolledusers));
$this->assertArrayHasKey('email', $enrolledusers[0]);
}
/**
* Test get_enrolled_users from core_enrol_external with some parameters set.
*/
public function test_get_enrolled_users_with_parameters() {
$capability = 'moodle/course:viewparticipants';
$data = $this->get_enrolled_users_setup($capability);
// Call the function with some parameters set.
$enrolledusers = core_enrol_external::get_enrolled_users($data->course->id, array(
array('name' => 'limitfrom', 'value' => 2),
array('name' => 'limitnumber', 'value' => 1),
array('name' => 'userfields', 'value' => 'id')
));
// We need to execute the return values cleaning process to simulate the web service server.
$enrolledusers = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_returns(), $enrolledusers);
// Check the result set, we should only get the 3rd result, which is $user3.
$this->assertCount(1, $enrolledusers);
$this->assertEquals($data->user3->id, $enrolledusers[0]['id']);
$this->assertArrayHasKey('id', $enrolledusers[0]);
$this->assertArrayNotHasKey('email', $enrolledusers[0]);
}
/**
* Test get_enrolled_users from core_enrol_external with capability to
* viewparticipants removed.
*/
public function test_get_enrolled_users_without_capability() {
$capability = 'moodle/course:viewparticipants';
$data = $this->get_enrolled_users_setup($capability);
// Call without required capability.
$this->unassignUserCapability($capability, $data->context->id, $data->roleid);
$this->setExpectedException('moodle_exception');
$categories = core_enrol_external::get_enrolled_users($data->course->id);
}
public function get_enrolled_users_with_capability_setup($capability) {
global $USER, $DB;
$this->resetAfterTest(true);
$return = new stdClass();
// Create the course and fetch its context.
$return->course = self::getDataGenerator()->create_course();
$context = context_course::instance($return->course->id);
// Create one teacher, and two students.
$return->teacher = self::getDataGenerator()->create_user();
$return->student1 = self::getDataGenerator()->create_user();
$return->student2 = self::getDataGenerator()->create_user();
// Create a new student role based on the student archetype but with the capability prohibitted.
$fakestudentroleid = create_role('Fake student role', 'fakestudent', 'Fake student role', 'student');
assign_capability($capability, CAP_PROHIBIT, $fakestudentroleid, $context->id);
// Enrol all of the users in the course.
// * 'teacher' is an editing teacher.
// * 'student1' is a standard student.
// * 'student2' is a student with the capability prohibitted.
$editingteacherroleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
$studentroleid = $DB->get_field('role', 'id', array('shortname' => 'student'));
$this->getDataGenerator()->enrol_user($return->teacher->id, $return->course->id, $editingteacherroleid);
$this->getDataGenerator()->enrol_user($return->student1->id, $return->course->id, $studentroleid);
$this->getDataGenerator()->enrol_user($return->student2->id, $return->course->id, $fakestudentroleid);
// Log in as the teacher.
$this->setUser($return->teacher);
// Clear caches.
accesslib_clear_all_caches_for_unit_testing();
return $return;
}
/**
* Test get_enrolled_users_with_capability without additional paramaters.
*/
public function test_get_enrolled_users_with_capability_without_parameters() {
$capability = 'moodle/course:viewparticipants';
$data = $this->get_enrolled_users_with_capability_setup($capability);
$result = core_enrol_external::get_enrolled_users_with_capability(
array(
'coursecapabilities' => array(
'courseid' => $data->course->id,
'capabilities' => array(
$capability,
),
),
),
array()
);
// We need to execute the return values cleaning process to simulate the web service server.
$result = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_with_capability_returns(), $result);
// Check an array containing the expected user for the course capability is returned.
$expecteduserlist = $result[0];
$this->assertEquals($data->course->id, $expecteduserlist['courseid']);
$this->assertEquals($capability, $expecteduserlist['capability']);
$this->assertEquals(2, count($expecteduserlist['users']));
}
/**
* Test get_enrolled_users_with_capability
*/
public function test_get_enrolled_users_with_capability_with_parameters () {
$capability = 'moodle/course:viewparticipants';
$data = $this->get_enrolled_users_with_capability_setup($capability);
$result = core_enrol_external::get_enrolled_users_with_capability(
array(
'coursecapabilities' => array(
'courseid' => $data->course->id,
'capabilities' => array(
$capability,
),
),
),
array(
array('name' => 'limitfrom', 'value' => 1),
array('name' => 'limitnumber', 'value' => 1),
array('name' => 'userfields', 'value' => 'id')
)
);
// We need to execute the return values cleaning process to simulate the web service server.
$result = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_with_capability_returns(), $result);
// Check an array containing the expected user for the course capability is returned.
$expecteduserlist = $result[0]['users'];
$expecteduser = reset($expecteduserlist);
$this->assertEquals(1, count($expecteduserlist));
$this->assertEquals($data->student1->id, $expecteduser['id']);
}
}
+1
View File
@@ -7,6 +7,7 @@ information provided here is intended especially for developers.
enrol_instance_deleted . Always trigger them when changing records in the
DB table 'enrol'.
* Constant CACHE_COURSE_CONTACTS_TTL was deleted.
* External function core_enrol_external::get_enrolled_users now returns only the viewable participants if the course has groups enabled and no groupid is passed.
=== 2.9 ===
-3
View File
@@ -3142,9 +3142,6 @@ abstract class grade_helper {
JOIN {user_info_category} c ON f.categoryid=c.id
WHERE f.shortname $wherefields
ORDER BY c.sortorder ASC, f.sortorder ASC", $whereparams);
if (!is_array($customfields)) {
continue;
}
foreach ($customfields as $field) {
// Make sure we can display this custom field
+8
View File
@@ -1109,6 +1109,14 @@ class grade_report_grader extends grade_report {
}
}
// Enable keyboard navigation if the grade is editable (not locked, not in a unoverridable category, etc).
if ($enableajax && $grade->is_editable()) {
// If a grade item is type text, and we don't have show quick feedback on, it can't be edited.
if ($item->gradetype != GRADE_TYPE_TEXT || $showquickfeedback) {
$itemcell->attributes['class'] .= ' gbnavigable';
}
}
if (!empty($this->gradeserror[$item->id][$userid])) {
$itemcell->text .= $this->gradeserror[$item->id][$userid];
}
+19 -16
View File
@@ -251,6 +251,7 @@ M.gradereport_grader.classes.ajax.prototype.keypress_enter = function(e) {
* @param {Bool} ignoreshift If true and shift is pressed then don't exec
*/
M.gradereport_grader.classes.ajax.prototype.keypress_tab = function(e, ignoreshift) {
e.preventDefault();
var next = null;
if (e.shiftKey) {
if (ignoreshift) {
@@ -328,8 +329,8 @@ M.gradereport_grader.classes.ajax.prototype.get_next_cell = function(cell) {
if (!next) {
return this.current.node;
}
// Continue on until we find a clickable cell
if (!next.hasClass('clickable')) {
// Continue on until we find a navigable cell
if (!next.hasClass('gbnavigable')) {
return this.get_next_cell(next);
}
return next;
@@ -352,8 +353,8 @@ M.gradereport_grader.classes.ajax.prototype.get_prev_cell = function(cell) {
if (!next) {
return this.current.node;
}
// Continue on until we find a clickable cell
if (!next.hasClass('clickable')) {
// Continue on until we find a navigable cell
if (!next.hasClass('gbnavigable')) {
return this.get_prev_cell(next);
}
return next;
@@ -380,8 +381,8 @@ M.gradereport_grader.classes.ajax.prototype.get_above_cell = function(cell) {
if (!next) {
return this.current.node;
}
// Continue on until we find a clickable cell
if (!next.hasClass('clickable')) {
// Continue on until we find a navigable cell
if (!next.hasClass('gbnavigable')) {
return this.get_above_cell(next);
}
return next;
@@ -408,8 +409,8 @@ M.gradereport_grader.classes.ajax.prototype.get_below_cell = function(cell) {
if (!next) {
return this.current.node;
}
// Continue on until we find a clickable cell
if (!next.hasClass('clickable')) {
// Continue on until we find a navigable cell
if (!next.hasClass('gbnavigable')) {
return this.get_below_cell(next);
}
return next;
@@ -700,13 +701,13 @@ M.gradereport_grader.classes.existingfield = function(ajax, userid, itemid) {
}
} else if (this.grade) {
// Handle Tab and Shift+Tab.
this.keyevents.push(this.report.Y.on('key', this.keypress_tab, this.grade, 'press:9', this));
this.keyevents.push(this.report.Y.on('key', this.keypress_tab, this.grade, 'down:9', this));
}
if (this.grade) {
// Handle the Enter key being pressed.
this.keyevents.push(this.report.Y.on('key', this.keypress_enter, this.grade, 'press:13', this));
this.keyevents.push(this.report.Y.on('key', this.keypress_enter, this.grade, 'up:13', this));
// Handle CTRL + arrow keys.
this.keyevents.push(this.report.Y.on('key', this.keypress_arrows, this.grade, 'press:37,38,39,40+ctrl', this));
this.keyevents.push(this.report.Y.on('key', this.keypress_arrows, this.grade, 'down:37,38,39,40+ctrl', this));
}
};
/**
@@ -759,6 +760,7 @@ M.gradereport_grader.classes.existingfield.prototype.keypress_tab = function(e,
* @param {Event} e
*/
M.gradereport_grader.classes.existingfield.prototype.keypress_arrows = function(e) {
e.preventDefault();
var next = null;
switch (e.keyCode) {
case 37: // Left
@@ -785,6 +787,7 @@ M.gradereport_grader.classes.existingfield.prototype.keypress_arrows = function(
M.gradereport_grader.classes.existingfield.prototype.move_focus = function(node) {
if (node) {
var properties = this.report.get_cell_info(node);
this.report.ajax.current = node;
switch(properties.itemtype) {
case 'scale':
properties.cell.one('select.select').focus();
@@ -1078,16 +1081,16 @@ M.gradereport_grader.classes.textfield.prototype.attach_key_events = function()
if (this.editfeedback) {
if (this.grade) {
// Handle Shift+Tab.
this.keyevents.push(this.report.Y.on('key', a.keypress_tab, this.grade, 'press:9+shift', a));
this.keyevents.push(this.report.Y.on('key', a.keypress_tab, this.grade, 'down:9+shift', a));
}
// Handle Tab.
this.keyevents.push(this.report.Y.on('key', a.keypress_tab, this.feedback, 'press:9', a, true));
this.keyevents.push(this.report.Y.on('key', a.keypress_tab, this.feedback, 'down:9', a, true));
// Handle the Enter key being pressed.
this.keyevents.push(this.report.Y.on('key', a.keypress_enter, this.feedback, 'press:13', a));
this.keyevents.push(this.report.Y.on('key', a.keypress_enter, this.feedback, 'up:13', a));
} else {
if (this.grade) {
// Handle Tab and Shift+Tab.
this.keyevents.push(this.report.Y.on('key', a.keypress_tab, this.grade, 'press:9', a));
this.keyevents.push(this.report.Y.on('key', a.keypress_tab, this.grade, 'down:9', a));
}
}
@@ -1097,7 +1100,7 @@ M.gradereport_grader.classes.textfield.prototype.attach_key_events = function()
if (this.grade) {
// Handle the Enter key being pressed.
this.keyevents.push(this.report.Y.on('key', a.keypress_enter, this.grade, 'press:13', a));
this.keyevents.push(this.report.Y.on('key', a.keypress_enter, this.grade, 'up:13', a));
// Prevent the default key action on all fields for arrow keys on all key events!
// Note: this still does not work in FF!!!!!
this.keyevents.push(this.report.Y.on('key', function(e){e.preventDefault();}, this.grade, 'down:37,38,39,40+ctrl'));
@@ -333,19 +333,13 @@ class grade extends tablelike implements selectable_items, filterable_items {
$data->$field = empty($grade) ? $null : $grade->finalgrade;
$data->{"old$field"} = $data->$field;
preg_match('/_(\d+)_(\d+)/', $field, $oldoverride);
$oldoverride = 'oldoverride' . $oldoverride[0];
if (empty($data->$oldoverride)) {
$data->$field = (!isset($grade->rawgrade)) ? $null : $grade->rawgrade;
}
}
}
foreach ($data as $varname => $value) {
if (preg_match('/^oldoverride_(\d+)_(\d+)/', $varname, $matches)) {
// If we've selected override or overriding all grades.
if (!empty($data->$matches[0]) || $filter == 'all') {
// If we've selected overriding all grades.
if ($filter == 'all') {
$override = "override_{$matches[1]}_{$matches[2]}";
$data->$override = '1';
}
@@ -336,29 +336,32 @@ class user extends tablelike implements selectable_items {
}
$oldfinalgradefield = "oldfinalgrade_{$gradeitem->id}_{$this->itemid}";
if (!empty($data->$oldfinalgradefield)) {
// Bulk grade changes for all grades need to be processed and shouldn't be skipped if they had a previous grade.
if ($gradeitem->is_course_item() || ($filter != 'all' && !empty($data->$oldfinalgradefield))) {
if ($gradeitem->is_course_item()) {
// The course total should not be overridden.
unset($data->$field);
unset($data->oldfinalgradefield);
$oldoverride = "oldoverride_{$gradeitem->id}_{$this->itemid}";
unset($data->$oldoverride);
$oldfeedback = "oldfeedback_{$gradeitem->id}_{$this->itemid}";
unset($data->$oldfeedback);
}
continue;
}
$grade = grade_grade::fetch(array(
'itemid' => $this->itemid,
'itemid' => $gradeitemid,
'userid' => $userid
));
$data->$field = empty($grade) ? $null : $grade->finalgrade;
$data->{"old$field"} = $data->$field;
preg_match('/_(\d+)_(\d+)/', $field, $oldoverride);
$oldoverride = 'oldoverride' . $oldoverride[0];
if (empty($data->$oldoverride)) {
$data->$field = (!isset($grade->rawgrade)) ? $null : $grade->rawgrade;
}
}
foreach ($data as $varname => $value) {
if (preg_match('/^oldoverride_(\d+)_(\d+)/', $varname, $matches)) {
// If we've selected override or overriding all grades.
if (!empty($data->$matches[0]) || $filter == 'all') {
// If we've selected overriding all grades.
if ($filter == 'all') {
$override = "override_{$matches[1]}_{$matches[2]}";
$data->$override = '1';
}
@@ -47,14 +47,7 @@ class finalgrade extends grade_attribute_format implements unique_value, be_disa
public function get_value() {
$this->label = $this->grade->grade_item->itemname;
$isoverridden = $this->grade->is_overridden();
// If the grade is overridden or the grade type is not an activity then use finalgrade.
if (!empty($isoverridden) || $this->grade->grade_item->itemtype != 'mod') {
$val = $this->grade->finalgrade;
} else {
$val = $this->grade->rawgrade;
}
$val = $this->grade->finalgrade;
if ($this->grade->grade_item->scaleid) {
return $val ? (int)$val : -1;
} else {
@@ -105,7 +105,7 @@ Feature: We can use Single view
And I set the field "Insert value" to "1.0"
And I click on "Perform bulk insert" "checkbox"
And I press "Save"
Then I should see "Grades were set for 9 items"
Then I should see "Grades were set for 8 items"
Scenario: Navigation works in the Single view.
Given I follow "Single view for Student 1"
+1 -1
View File
@@ -90,5 +90,5 @@ $string['welcomep50'] = 'L\'ús de totes les aplicacions d\'aquest paquet és go
<a href="http://www.opensource.org/docs/definition_plain.html">codi font obert</a> i es distribueix
sota llicència <a href="http://www.gnu.org/copyleft/gpl.html">GPL</a>.';
$string['welcomep60'] = 'Les pàgines següents us guiaran per una sèrie de passos fàcils de seguir per configurar <strong>Moodle</strong> en el vostre ordinador. Podeu acceptar els paràmetres per defecte o, opcionalment, modificar-los perquè s\'ajustin a les vostres necessitats.';
$string['welcomep70'] = 'Feu clic en el botó "Següent" per continuar la configuració de <strong>Moodle</strong>.';
$string['welcomep70'] = 'Feu clic al botó «Següent» per continuar amb la configuració de <strong>Moodle</strong>.';
$string['wwwroot'] = 'Adreça web';
+1
View File
@@ -42,3 +42,4 @@ Použijte volbu --help pro získání nápovědy';
$string['cliyesnoprompt'] = 'zadejte a (pro "ano") nebo n (pro "ne")';
$string['environmentrequireinstall'] = 'musí být nainstalováno a povoleno';
$string['environmentrequireversion'] = 'vyžadována je verze {$a->needed}, nyní používáte verzi {$a->current}';
$string['upgradekeyset'] = 'Klíč pro upgrade (ponechte prázdné, nechcete-li nenastavil)';
+1
View File
@@ -42,3 +42,4 @@ $string['cliunknowoption'] = 'Opzioni non riconosciute:
$string['cliyesnoprompt'] = 'inserisci s (per Si) o n (per No)';
$string['environmentrequireinstall'] = 'E\' necessaria l\'installazione e attivazione';
$string['environmentrequireversion'] = 'Versione minima necessaria: {$a->needed}. Versione in uso: {$a->current}';
$string['upgradekeyset'] = 'Chiave per l\'aggiornamento (lasciare vuoto se non impostata)';
+1
View File
@@ -42,3 +42,4 @@ Vennligst bruk Hjelp.';
$string['cliyesnoprompt'] = 'Tast j (betyr JA) eller n (betyr NEI)';
$string['environmentrequireinstall'] = 'er påkrevd at du installerer/tillater';
$string['environmentrequireversion'] = 'Du må ha versjon {$a->needed}. Du kjører {$a->current}';
$string['upgradekeyset'] = 'Oppgraderingsnøkkel (la være tom for å kreve noen)';
+1 -1
View File
@@ -27,7 +27,7 @@ $string['addrestriction'] = 'Add restriction...';
$string['and'] = 'and';
$string['condition_group'] = 'Restriction set';
$string['condition_group_info'] = 'Add a set of nested restrictions to apply complex logic.';
$string['enableavailability'] = 'Enable conditional access';
$string['enableavailability'] = 'Enable restricted access';
$string['enableavailability_desc'] = 'When enabled, this lets you set conditions (based on date, grade, or completion) that control whether an activity or resource can be accessed.';
$string['error_list_nochildren'] = 'Restriction sets should contain at least one condition.';
$string['hidden_marker'] = '(hidden otherwise)';
+1 -1
View File
@@ -106,7 +106,7 @@ $string['badgeprivacysetting_help'] = 'Badges you earn can be displayed on your
You can still control individual badge privacy settings on your badges page.';
$string['badgeprivacysetting_str'] = 'Automatically show badges I earn on my profile page';
$string['badgesalt'] = 'Salt for hashing the recepient\'s email address';
$string['badgesalt'] = 'Salt for hashing the recipient\'s email address';
$string['badgesalt_desc'] = 'Using a hash allows backpack services to confirm the badge earner without having to expose their email address. This setting should only use numbers and letters.
Note: For recipient verification purposes, please avoid changing this setting once you start issuing badges.';
+1 -1
View File
@@ -41,7 +41,7 @@ $string['completed'] = 'Completed';
$string['completedunlocked'] = 'Completion options unlocked';
$string['completedunlockedtext'] = 'When you save changes, completion state for all students will be erased. If you change your mind about this, do not save the form.';
$string['completedwarning'] = 'Completion options locked';
$string['completedwarningtext'] = 'One or more students ({$a}) has already marked this activity as completed. Changing completion options will erase their completion state and may cause confusion. Thus the options have been locked and should not be unlocked unless absolutely necessary.';
$string['completedwarningtext'] = 'This activity has already been marked as completed for {$a} participant(s). Changing completion options will erase their completion state and may cause confusion. Thus the options have been locked and should not be unlocked unless absolutely necessary.';
$string['completion'] = 'Completion tracking';
$string['completion-alt-auto-enabled'] = 'The system marks this item complete according to conditions: {$a}';
$string['completion-alt-auto-fail'] = 'Completed: {$a} (did not achieve pass grade)';
+1 -1
View File
@@ -98,7 +98,7 @@ $string['migratetomanual'] = 'Migrate to manual enrolments';
$string['nochange'] = 'No change';
$string['noexistingparticipants'] = 'No existing participants';
$string['nogroup'] = 'No group';
$string['noguestaccess'] = 'Guests can not access this course, please try to log in.';
$string['noguestaccess'] = 'Guests cannot access this course. Please log in.';
$string['none'] = 'None';
$string['notenrollable'] = 'You can not enrol yourself in this course.';
$string['notenrolledusers'] = 'Other users';
+2 -2
View File
@@ -304,7 +304,7 @@ $string['invalidcourselevel'] = 'Incorrect context level';
$string['invalidcoursemodule'] = 'Invalid course module ID';
$string['invalidcoursenameshort'] = 'Invalid short course name';
$string['invaliddata'] = 'Data submitted is invalid';
$string['invaliddatarootpermissions'] = 'Invalid permissions detected in $CFG->dataroot directory, administrator has to fix permissions.';
$string['invaliddatarootpermissions'] = 'Invalid permissions detected when trying to create a directory. Turn debugging on for further details.';
$string['invaliddevicetype'] = 'Invalid device type';
$string['invalidelementid'] = 'Incorrect element id!';
$string['invalidentry'] = 'This is not valid entry!';
@@ -388,7 +388,7 @@ $string['movecatcontentstoroot'] = 'Moving the category content to root is not a
$string['movecategorynotpossible'] = 'You cannot move category \'{$a}\' into the selected category.';
$string['movecategoryownparent'] = 'You cannot make category \'{$a}\' a parent of itself.';
$string['movecategoryparentconflict'] = 'You cannot make category \'{$a}\' a subcategory of one of its own subcategories.';
$string['mssqlrcsmodemissing'] = 'The database is not using the expected READ_COMMITTED_SNAPSHOT mode which can lead to wrong results, especially under high concurrency scenarios. Please enable it for correct behaviour. You can find more information in the <a href="https://docs.moodle.org/en/Installing_MSSQL_for_PHP#Configuration">Moodle Docs</a>.';
$string['mssqlrcsmodemissing'] = 'The database is not using the expected READ_COMMITTED_SNAPSHOT mode which can lead to wrong results, especially under high concurrency scenarios. Please enable it for correct behaviour. For further information, see the documentation <a href="https://docs.moodle.org/en/Installing_MSSQL_for_PHP">Installing MSSQL for PHP</a>.';
$string['multiplerecordsfound'] = 'Multiple records found, only one record expected.';
$string['multiplerestorenotallow'] = 'Multiple restore execution not allowed!';
$string['mustbeloggedin'] = 'You must be logged in to do this';
+1
View File
@@ -50,6 +50,7 @@ $string['mustbeoverriden'] = 'Abstract form_definition() method in class {$a} mu
$string['nomethodforaddinghelpbutton'] = 'There is no method for adding a help button to form element {$a->name} (class {$a->classname})';
$string['nonexistentformelements'] = 'Trying to add help buttons to non-existent form elements : {$a}';
$string['noselection'] = 'No selection';
$string['nosuggestions'] = 'No suggestions';
$string['optional'] = 'Optional';
$string['othersettings'] = 'Other settings';
$string['requiredelement'] = 'Required field';
+3 -1
View File
@@ -240,7 +240,9 @@ $string['gradeanalysis'] = 'Grade analysis';
$string['gradebook'] = 'Gradebook';
$string['gradebookcalculationsuptodate'] = 'The calculations in the gradebook are up to date. You may need to reload this page to see changes.';
$string['gradebookcalculationsfixbutton'] = 'Accept grade changes and fix calculation errors';
$string['gradebookcalculationswarning'] = 'Note: Some errors have been detected in calculating the grades displayed in the gradebook. It is recommended that the errors are fixed by clicking the button below, though this will result in some grades being changed. For details, see the changes between versions {$a->gradebookversion} and {$a->currentversion} in <a href="{$a->url}">Gradebook calculation changes</a>.';
$string['gradebookcalculationswarning'] = 'Note: Some errors have been detected in calculating the grades displayed in the gradebook. If your course has not started or is in progress, it is recommended that the errors are fixed by clicking the button below, though this will result in some grades being changed. If your course has already ended and grades have been submitted, you probably do not want to fix this issue.
The newer version is {$a->currentversion}; you are using gradebook version {$a->gradebookversion}. You can see a list of changes in <a href="{$a->url}">Gradebook calculation changes</a>.';
$string['gradebookhiddenerror'] = 'The gradebook is currently set to hide everything from students.';
$string['gradebookhistories'] = 'Grade histories';
$string['gradebooksetup'] = 'Gradebook setup';
+1
View File
@@ -143,6 +143,7 @@ $string['thisconversation'] = 'this conversation';
$string['timenosee'] = 'Minutes since I was last seen online';
$string['timesent'] = 'Time sent';
$string['touserdoesntexist'] = 'You can not send a message to a user id ({$a}) that doesn\'t exist';
$string['unabletomessageuser'] = 'You are not permitted to send a message to that user';
$string['unblockcontact'] = 'Unblock contact';
$string['unreadmessages'] = 'Unread messages ({$a})';
$string['unreadnewmessages'] = 'New messages ({$a})';
+1 -1
View File
@@ -1002,7 +1002,7 @@ $string['institution'] = 'Institution';
$string['instudentview'] = 'in student view';
$string['interests'] = 'Interests';
$string['interestslist'] = 'List of interests';
$string['interestslist_help'] = 'Enter your interests. Your interests will be displayed on your profile page as tags.';
$string['interestslist_help'] = 'Enter your interests, one by one, which will then be displayed on your profile page as tags.';
$string['invalidemail'] = 'Invalid email address';
$string['invalidlogin'] = 'Invalid login, please try again';
$string['invalidusername'] = 'The username can only contain alphanumeric lowercase characters, underscore (_), hyphen (-), period (.) or at symbol (@)';
+2 -2
View File
@@ -30,7 +30,7 @@ $string['availability'] = 'Availability';
$string['cancelinstallall'] = 'Cancel new installations ({$a})';
$string['cancelinstallone'] = 'Cancel this installation';
$string['cancelinstallhead'] = 'Cancelling installation of plugins';
$string['cancelinstallinfo'] = 'Following plugins are not fully installed yet and their installation can be cancelled. To do so, the plugin folder must be removed from your server now. Make sure that is really what you want to prevent accidental data loss (such as your own code modifications).';
$string['cancelinstallinfo'] = 'The following plugins are not fully installed yet so their installation may be cancelled. To do so, the plugin folder must be removed from the server now. Make sure this is really what you want to prevent accidental data loss (such as your own code modifications).';
$string['cancelinstallinfodir'] = 'Folder to be deleted: {$a}';
$string['cancelupgradeall'] = 'Cancel upgrades ({$a})';
$string['cancelupgradehead'] = 'Restoring previous version of plugins';
@@ -67,7 +67,7 @@ $string['notdownloadable'] = 'Can not download the package';
$string['notdownloadable_help'] = 'ZIP package with the update can not be downloaded automatically. Please refer to the documentation page for more help.';
$string['notdownloadable_link'] = 'admin/mdeploy/notdownloadable';
$string['notwritable'] = 'Plugin files not writable';
$string['notwritable_help'] = 'Plugin files are not writable by the web server. The web server process has to have write access to the plugin folder and all its contents. Write access to the root folder of the given plugin type may be required, too.';
$string['notwritable_help'] = 'Plugin files are not writable by the web server. The web server process must have write access to the plugin folder and all its contents. Write access to the root folder of the given plugin type may also be required.';
$string['otherplugin'] = '{$a->component}';
$string['otherpluginversion'] = '{$a->component} ({$a->version})';
$string['overviewall'] = 'All plugins';
+1 -1
View File
@@ -117,7 +117,7 @@ $string['iprestriction'] = 'IP restriction';
$string['iprestriction_help'] = 'The user will need to call the web service from the listed IPs (separated by commas).';
$string['key'] = 'Key';
$string['keyshelp'] = 'The keys are used to access your Moodle account from external applications.';
$string['loginrequired'] = 'Restricted to logged in users';
$string['loginrequired'] = 'Restricted to logged-in users';
$string['manageprotocols'] = 'Manage protocols';
$string['managetokens'] = 'Manage tokens';
$string['missingcaps'] = 'Missing capabilities';
File diff suppressed because one or more lines are too long
+236 -210
View File
@@ -46,11 +46,11 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
* @method activateSelection
* @private
* @param {Number} index The index in the current (visible) list of selection.
* @param {String} selectionId The id of the selection element for this instance of the autocomplete.
* @param {Object} state State variables for this autocomplete element.
*/
var activateSelection = function(index, selectionId) {
var activateSelection = function(index, state) {
// Find the elements in the DOM.
var selectionElement = $(document.getElementById(selectionId));
var selectionElement = $(document.getElementById(state.selectionId));
// Count the visible items.
var length = selectionElement.children('[aria-selected=true]').length;
@@ -62,7 +62,7 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
// Find the specified element.
var element = $(selectionElement.children('[aria-selected=true]').get(index));
// Create an id we can assign to this element.
var itemId = selectionId + '-' + index;
var itemId = state.selectionId + '-' + index;
// Deselect all the selections.
selectionElement.children().attr('data-active-selection', false).attr('id', '');
@@ -77,26 +77,29 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
*
* @method deselectItem
* @private
* @param {String} inputId The id of the input element for this instance of the autocomplete.
* @param {String} selectionId The id of the selection element for this instance of the autocomplete.
* @param {Object} options Original options for this autocomplete element.
* @param {Object} state State variables for this autocomplete element.
* @param {Element} The item to be deselected.
* @param {Element} originalSelect The original select list.
* @param {Boolean} multiple Is this a multi select.
*/
var deselectItem = function(inputId, selectionId, item, originalSelect, multiple) {
var deselectItem = function(options, state, item, originalSelect) {
var selectedItemValue = $(item).attr('data-value');
// We can only deselect items if this is a multi-select field.
if (multiple) {
if (options.multiple) {
// Look for a match, and toggle the selected property if there is a match.
originalSelect.children('option').each(function(index, ele) {
if ($(ele).attr('value') == selectedItemValue) {
$(ele).prop('selected', false);
// We remove newly created custom tags from the suggestions list when they are deselected.
if ($(ele).attr('data-iscustom')) {
$(ele).remove();
}
}
});
}
// Rerender the selection list.
updateSelectionList(selectionId, inputId, originalSelect, multiple);
updateSelectionList(options, state, originalSelect);
};
/**
@@ -105,13 +108,12 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
* @method activateItem
* @private
* @param {Number} index The index in the current (visible) list of suggestions.
* @param {String} inputId The id of the input element for this instance of the autocomplete.
* @param {String} suggestionsId The id of the suggestions element for this instance of the autocomplete.
* @param {Object} state State variables for this instance of autocomplete.
*/
var activateItem = function(index, inputId, suggestionsId) {
var activateItem = function(index, state) {
// Find the elements in the DOM.
var inputElement = $(document.getElementById(inputId));
var suggestionsElement = $(document.getElementById(suggestionsId));
var inputElement = $(document.getElementById(state.inputId));
var suggestionsElement = $(document.getElementById(state.suggestionsId));
// Count the visible items.
var length = suggestionsElement.children('[aria-hidden=false]').length;
@@ -125,7 +127,7 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
// Find the index of this item in the full list of suggestions (including hidden).
var globalIndex = $(suggestionsElement.children('[role=option]')).index(element);
// Create an id we can assign to this element.
var itemId = suggestionsId + '-' + globalIndex;
var itemId = state.suggestionsId + '-' + globalIndex;
// Deselect all the suggestions.
suggestionsElement.children().attr('aria-selected', false).attr('id', '');
@@ -133,6 +135,15 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
element.attr('aria-selected', true).attr('id', itemId);
// Tell the input field it has a new active descendant so the item is announced.
inputElement.attr('aria-activedescendant', itemId);
// Scroll it into view.
var scrollPos = element.offset().top
- suggestionsElement.offset().top
+ suggestionsElement.scrollTop()
- (suggestionsElement.height() / 2);
suggestionsElement.animate({
scrollTop: scrollPos
}, 100);
};
/**
@@ -140,18 +151,17 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
*
* @method activateNextItem
* @private
* @param {String} inputId The id of the input element for this instance of the autocomplete.
* @param {String} suggestionsId The id of the suggestions element for this instance of the autocomplete.
* @param {Object} state State variable for this auto complete element.
*/
var activateNextItem = function(inputId, suggestionsId) {
var activateNextItem = function(state) {
// Find the list of suggestions.
var suggestionsElement = $(document.getElementById(suggestionsId));
var suggestionsElement = $(document.getElementById(state.suggestionsId));
// Find the active one.
var element = suggestionsElement.children('[aria-selected=true]');
// Find it's index.
var current = suggestionsElement.children('[aria-hidden=false]').index(element);
// Activate the next one.
activateItem(current+1, inputId, suggestionsId);
activateItem(current+1, state);
};
/**
@@ -159,42 +169,42 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
*
* @method activatePreviousSelection
* @private
* @param {String} selectionId The id of the selection element for this instance of the autocomplete.
* @param {Object} state State variables for this instance of autocomplete.
*/
var activatePreviousSelection = function(selectionId) {
var activatePreviousSelection = function(state) {
// Find the list of selections.
var selectionsElement = $(document.getElementById(selectionId));
var selectionsElement = $(document.getElementById(state.selectionId));
// Find the active one.
var element = selectionsElement.children('[data-active-selection=true]');
if (!element) {
activateSelection(0, selectionId);
activateSelection(0, state);
return;
}
// Find it's index.
var current = selectionsElement.children('[aria-selected=true]').index(element);
// Activate the next one.
activateSelection(current-1, selectionId);
activateSelection(current-1, state);
};
/**
* Find the index of the current active selection, and activate the next one.
*
* @method activateNextSelection
* @private
* @param {String} selectionId The id of the selection element for this instance of the autocomplete.
* @param {Object} state State variables for this instance of autocomplete.
*/
var activateNextSelection = function(selectionId) {
var activateNextSelection = function(state) {
// Find the list of selections.
var selectionsElement = $(document.getElementById(selectionId));
var selectionsElement = $(document.getElementById(state.selectionId));
// Find the active one.
var element = selectionsElement.children('[data-active-selection=true]');
if (!element) {
activateSelection(0, selectionId);
activateSelection(0, state);
return;
}
// Find it's index.
var current = selectionsElement.children('[aria-selected=true]').index(element);
// Activate the next one.
activateSelection(current+1, selectionId);
activateSelection(current+1, state);
};
/**
@@ -202,18 +212,17 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
*
* @method activatePreviousItem
* @private
* @param {String} inputId The id of the input element for this instance of the autocomplete.
* @param {String} suggestionsId The id of the suggestions element for this instance of the autocomplete.
* @param {Object} state State variables for this autocomplete element.
*/
var activatePreviousItem = function(inputId, suggestionsId) {
var activatePreviousItem = function(state) {
// Find the list of suggestions.
var suggestionsElement = $(document.getElementById(suggestionsId));
var suggestionsElement = $(document.getElementById(state.suggestionsId));
// Find the active one.
var element = suggestionsElement.children('[aria-selected=true]');
// Find it's index.
var current = suggestionsElement.children('[aria-hidden=false]').index(element);
// Activate the next one.
activateItem(current-1, inputId, suggestionsId);
activateItem(current-1, state);
};
/**
@@ -221,16 +230,15 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
*
* @method closeSuggestions
* @private
* @param {String} inputId The id of the input element for this instance of the autocomplete.
* @param {String} suggestionsId The id of the suggestions element for this instance of the autocomplete.
* @param {Object} state State variables for this autocomplete element.
*/
var closeSuggestions = function(inputId, suggestionsId, selectionId) {
var closeSuggestions = function(state) {
// Find the elements in the DOM.
var inputElement = $(document.getElementById(inputId));
var suggestionsElement = $(document.getElementById(suggestionsId));
var inputElement = $(document.getElementById(state.inputId));
var suggestionsElement = $(document.getElementById(state.suggestionsId));
// Announce the list of suggestions was closed, and read the current list of selections.
inputElement.attr('aria-expanded', false).attr('aria-activedescendant', selectionId);
inputElement.attr('aria-expanded', false).attr('aria-activedescendant', state.selectionId);
// Hide the suggestions list (from screen readers too).
suggestionsElement.hide().attr('aria-hidden', true);
};
@@ -240,43 +248,44 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
*
* @method updateSuggestions
* @private
* @param {String} query The current query typed in the input field.
* @param {String} inputId The id of the input element for this instance of the autocomplete.
* @param {String} suggestionsId The id of the suggestions element for this instance of the autocomplete.
* @param {Object} options The original options for this autocomplete.
* @param {Object} state The state variables for this autocomplete.
* @param {String} query The current text for the search string.
* @param {JQuery} originalSelect The JQuery object matching the hidden select list.
* @param {Boolean} multiple Are multiple items allowed to be selected?
* @param {Boolean} tags Are we allowed to create new items on the fly?
*/
var updateSuggestions = function(query, inputId, suggestionsId, originalSelect, multiple, tags) {
var updateSuggestions = function(options, state, query, originalSelect) {
// Find the elements in the DOM.
var inputElement = $(document.getElementById(inputId));
var suggestionsElement = $(document.getElementById(suggestionsId));
var inputElement = $(document.getElementById(state.inputId));
var suggestionsElement = $(document.getElementById(state.suggestionsId));
// Used to track if we found any visible suggestions.
var matchingElements = false;
// Options is used by the context when rendering the suggestions from a template.
var options = [];
var suggestions = [];
originalSelect.children('option').each(function(index, option) {
if ($(option).prop('selected') !== true) {
options[options.length] = { label: option.innerHTML, value: $(option).attr('value') };
suggestions[suggestions.length] = { label: option.innerHTML, value: $(option).attr('value') };
}
});
// Re-render the list of suggestions.
var searchquery = state.caseSensitive ? query : query.toLocaleLowerCase();
var context = $.extend({ options: suggestions}, options, state);
templates.render(
'core/form_autocomplete_suggestions',
{ inputId: inputId, suggestionsId: suggestionsId, options: options, multiple: multiple}
context
).done(function(newHTML) {
// We have the new template, insert it in the page.
suggestionsElement.replaceWith(newHTML);
// Get the element again.
suggestionsElement = $(document.getElementById(suggestionsId));
suggestionsElement = $(document.getElementById(state.suggestionsId));
// Show it if it is hidden.
suggestionsElement.show().attr('aria-hidden', false);
// For each option in the list, hide it if it doesn't match the query.
suggestionsElement.children().each(function(index, node) {
node = $(node);
if (node.text().indexOf(query) > -1) {
if ((options.caseSensitive && node.text().indexOf(searchquery) > -1) ||
(!options.caseSensitive && node.text().toLocaleLowerCase().indexOf(searchquery) > -1)) {
node.show().attr('aria-hidden', false);
matchingElements = true;
} else {
@@ -284,19 +293,19 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
}
});
// If we found any matches, show the list.
inputElement.attr('aria-expanded', true);
if (matchingElements) {
inputElement.attr('aria-expanded', true);
// We only activate the first item in the list if tags is false,
// because otherwise "Enter" would select the first item, instead of
// creating a new tag.
if (!tags) {
activateItem(0, inputId, suggestionsId);
if (!options.tags) {
activateItem(0, state);
}
} else {
// Abort - nothing matches. Hide the suggestions properly.
suggestionsElement.hide();
suggestionsElement.attr('aria-hidden', true);
inputElement.attr('aria-expanded', false);
// Nothing matches. Tell them that.
str.get_string('nosuggestions', 'form').done(function(nosuggestionsstr) {
suggestionsElement.html(nosuggestionsstr);
});
}
}).fail(notification.exception);
@@ -307,14 +316,13 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
*
* @method createItem
* @private
* @param {String} inputId The id of the input element for this instance of the autocomplete.
* @param {String} suggestionsId The id of the suggestions element for this instance of the autocomplete.
* @param {Boolean} multiple Are multiple items allowed to be selected?
* @param {Object} options The original options for the autocomplete.
* @param {Object} state State variables for the autocomplete.
* @param {JQuery} originalSelect The JQuery object matching the hidden select list.
*/
var createItem = function(inputId, suggestionsId, selectionId, multiple, originalSelect) {
var createItem = function(options, state, originalSelect) {
// Find the element in the DOM.
var inputElement = $(document.getElementById(inputId));
var inputElement = $(document.getElementById(state.inputId));
// Get the current text in the input field.
var query = inputElement.val();
var tags = query.split(',');
@@ -324,7 +332,7 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
// If we can only select one at a time, deselect any current value.
tag = tag.trim();
if (tag !== '') {
if (!multiple) {
if (!options.multiple) {
originalSelect.children('option').prop('selected', false);
}
// Look for an existing option in the select list that matches this new tag.
@@ -341,34 +349,18 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
option.attr('value', tag);
originalSelect.append(option);
option.prop('selected', true);
// We mark newly created custom options as we handle them differently if they are "deselected".
option.attr('data-iscustom', true);
}
}
});
// Get the selection element.
var newSelection = $(document.getElementById(selectionId));
// Build up a valid context to re-render the selection.
var items = [];
originalSelect.children('option').each(function(index, ele) {
if ($(ele).prop('selected')) {
items.push( { label: $(ele).html(), value: $(ele).attr('value') } );
}
});
var context = {
selectionId: selectionId,
items: items,
multiple: multiple
};
// Re-render the selection.
templates.render('core/form_autocomplete_selection', context).done(function(newHTML) {
// Update the page.
newSelection.empty().append($(newHTML).html());
}).fail(notification.exception);
updateSelectionList(options, state, originalSelect);
// Clear the input field.
inputElement.val('');
// Close the suggestions list.
closeSuggestions(inputId, suggestionsId, selectionId);
// Trigger a change event so that the mforms javascript can check for required fields etc.
originalSelect.change();
closeSuggestions(state);
};
/**
@@ -376,29 +368,40 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
*
* @method updateSelectionList
* @private
* @param {String} selectionId The id of the selections element for this instance of the autocomplete.
* @param {String} inputId The id of the input element for this instance of the autocomplete.
* @param {Object} options Original options for this autocomplete element.
* @param {Object} state State variables for this autocomplete element.
* @param {JQuery} originalSelect The JQuery object matching the hidden select list.
* @param {Boolean} multiple Does this element support multiple selections.
*/
var updateSelectionList = function(selectionId, inputId, originalSelect, multiple) {
var updateSelectionList = function(options, state, originalSelect) {
// Build up a valid context to re-render the template.
var items = [];
var newSelection = $(document.getElementById(selectionId));
var newSelection = $(document.getElementById(state.selectionId));
var activeId = newSelection.attr('aria-activedescendant');
var activeValue = false;
if (activeId) {
activeValue = $(document.getElementById(activeId)).attr('data-value');
}
originalSelect.children('option').each(function(index, ele) {
if ($(ele).prop('selected')) {
items.push( { label: $(ele).html(), value: $(ele).attr('value') } );
}
});
var context = {
selectionId: selectionId,
items: items,
multiple: multiple
};
var context = $.extend({ items: items }, options, state);
// Render the template.
templates.render('core/form_autocomplete_selection', context).done(function(newHTML) {
// Add it to the page.
newSelection.empty().append($(newHTML).html());
if (activeValue !== false) {
// Reselect any previously selected item.
newSelection.children('[aria-selected=true]').each(function(index, ele) {
if ($(ele).attr('data-value') === activeValue) {
activateSelection(index, state);
}
});
}
}).fail(notification.exception);
// Because this function get's called after changing the selection, this is a good place
// to trigger a change notification.
@@ -410,16 +413,14 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
*
* @method selectCurrentItem
* @private
* @param {String} inputId The id of the input element for this instance of the autocomplete.
* @param {String} suggestionsId The id of the suggestions element for this instance of the autocomplete.
* @param {String} selectionId The id of the selection element for this instance of the autocomplete.
* @param {Boolean} multiple Are multiple items allowed to be selected?
* @param {Object} options The original options for the autocomplete.
* @param {Object} state State variables for the autocomplete.
* @param {JQuery} originalSelect The JQuery object matching the hidden select list.
*/
var selectCurrentItem = function(inputId, suggestionsId, selectionId, multiple, originalSelect) {
var selectCurrentItem = function(options, state, originalSelect) {
// Find the elements in the page.
var inputElement = $(document.getElementById(inputId));
var suggestionsElement = $(document.getElementById(suggestionsId));
var inputElement = $(document.getElementById(state.inputId));
var suggestionsElement = $(document.getElementById(state.suggestionsId));
// Here loop through suggestions and set val to join of all selected items.
var selectedItemValue = suggestionsElement.children('[aria-selected=true]').attr('data-value');
@@ -427,7 +428,7 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
// select one or more items correctly.
// Take care to use 'prop' and not 'attr' for selected properties.
// If only one can be selected at a time, start by deselecting everything.
if (!multiple) {
if (!options.multiple) {
originalSelect.children('option').prop('selected', false);
}
// Look for a match, and toggle the selected property if there is a match.
@@ -437,11 +438,11 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
}
});
// Rerender the selection list.
updateSelectionList(selectionId, inputId, originalSelect, multiple);
updateSelectionList(options, state, originalSelect);
// Clear the input element.
inputElement.val('');
// Close the list of suggestions.
closeSuggestions(inputId, suggestionsId, selectionId);
closeSuggestions(state);
};
/**
@@ -450,21 +451,18 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
* @method updateAjax
* @private
* @param {Event} e The event that triggered this update.
* @param {String} selector The selector pointing to the original select.
* @param {String} inputId The id of the input element for this instance of the autocomplete.
* @param {String} suggestionsId The id of the suggestions element for this instance of the autocomplete.
* @param {Object} options The original options for the autocomplete.
* @param {Object} state The state variables for the autocomplete.
* @param {JQuery} originalSelect The JQuery object matching the hidden select list.
* @param {Boolean} multiple Are multiple items allowed to be selected?
* @param {Boolean} tags Are we allowed to create new items on the fly?
* @param {Object} ajaxHandler This is a module that does the ajax fetch and translates the results.
*/
var updateAjax = function(e, selector, inputId, suggestionsId, originalSelect, multiple, tags, ajaxHandler) {
var updateAjax = function(e, options, state, originalSelect, ajaxHandler) {
// Get the query to pass to the ajax function.
var query = $(e.currentTarget).val();
// Call the transport function to do the ajax (name taken from Select2).
ajaxHandler.transport(selector, query, function(results) {
ajaxHandler.transport(options.selector, query, function(results) {
// We got a result - pass it through the translator before using it.
var processedResults = ajaxHandler.processResults(selector, results);
var processedResults = ajaxHandler.processResults(options.selector, results);
var existingValues = [];
// Now destroy all options that are not currently selected.
@@ -486,7 +484,7 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
}
});
// Update the list of suggestions now from the new values in the select list.
updateSuggestions('', inputId, suggestionsId, originalSelect, multiple, tags);
updateSuggestions(options, state, '', originalSelect);
}, notification.exception);
};
@@ -495,54 +493,60 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
*
* @method addNavigation
* @private
* @param {String} inputId The id of the input element for this instance of the autocomplete.
* @param {String} suggestionsId The id of the suggestions element for this instance of the autocomplete.
* @param {String} downArrowId The id of arrow to open the suggestions list.
* @param {String} selectionId The id of element that shows the current selections.
* @param {Object} options The options used to create this autocomplete element.
* @param {Object} state State variables for this autocomplete element.
* @param {JQuery} originalSelect The JQuery object matching the hidden select list.
* @param {Boolean} multiple Are multiple items allowed to be selected?
* @param {Boolean} tags Are we allowed to create new items on the fly?
*/
var addNavigation = function(inputId, suggestionsId, downArrowId, selectionId, originalSelect, multiple, tags) {
var addNavigation = function(options, state, originalSelect) {
// Start with the input element.
var inputElement = $(document.getElementById(inputId));
var inputElement = $(document.getElementById(state.inputId));
// Add keyboard nav with keydown.
inputElement.on('keydown', function(e) {
switch (e.keyCode) {
case KEYS.DOWN:
// If the suggestion list is open, move to the next item.
if (inputElement.attr('aria-expanded') === "true") {
activateNextItem(inputId, suggestionsId);
if (!options.showSuggestions) {
// Do not consume this event.
return true;
} else if (inputElement.attr('aria-expanded') === "true") {
activateNextItem(state);
} else {
// Else - open the suggestions list.
updateSuggestions(inputElement.val(), inputId, suggestionsId, originalSelect, multiple, tags);
// Handle ajax population of suggestions.
if (!inputElement.val() && options.ajax) {
require([options.ajax], function(ajaxHandler) {
updateAjax(e, options, state, originalSelect, ajaxHandler);
});
} else {
// Else - open the suggestions list.
updateSuggestions(options, state, inputElement.val(), originalSelect);
}
}
// We handled this event, so prevent it.
e.preventDefault();
return false;
case KEYS.COMMA:
if (tags) {
if (options.tags) {
// If we are allowing tags, comma should create a tag (or enter).
createItem(inputId, suggestionsId, selectionId, multiple, originalSelect);
createItem(options, state, originalSelect);
}
// We handled this event, so prevent it.
e.preventDefault();
return false;
case KEYS.UP:
// Choose the previous active item.
activatePreviousItem(inputId, suggestionsId);
activatePreviousItem(state);
// We handled this event, so prevent it.
e.preventDefault();
return false;
case KEYS.ENTER:
var suggestionsElement = $(document.getElementById(suggestionsId));
var suggestionsElement = $(document.getElementById(state.suggestionsId));
if ((inputElement.attr('aria-expanded') === "true") &&
(suggestionsElement.children('[aria-selected=true]').length > 0)) {
// If the suggestion list has an active item, select it.
selectCurrentItem(inputId, suggestionsId, selectionId, multiple, originalSelect);
} else if (tags) {
selectCurrentItem(options, state, originalSelect);
} else if (options.tags) {
// If tags are enabled, create a tag.
createItem(inputId, suggestionsId, selectionId, multiple, originalSelect);
createItem(options, state, originalSelect);
}
// We handled this event, so prevent it.
e.preventDefault();
@@ -550,7 +554,7 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
case KEYS.ESCAPE:
if (inputElement.attr('aria-expanded') === "true") {
// If the suggestion list is open, close it.
closeSuggestions(inputId, suggestionsId, selectionId);
closeSuggestions(state);
}
// We handled this event, so prevent it.
e.preventDefault();
@@ -560,8 +564,8 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
});
// Handler used to force set the value from behat.
inputElement.on('behat:set-value', function() {
if (tags) {
createItem(inputId, suggestionsId, selectionId, multiple, originalSelect);
if (options.tags) {
createItem(options, state, originalSelect);
}
});
inputElement.on('blur focus', function(e) {
@@ -572,65 +576,69 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
window.clearTimeout(closeSuggestionsTimer);
}
closeSuggestionsTimer = window.setTimeout(function() {
if ((e.type == 'blur') && tags) {
createItem(inputId, suggestionsId, selectionId, multiple, originalSelect);
if (e.type == 'blur') {
if (options.tags) {
createItem(options, state, originalSelect);
}
closeSuggestions(state);
}
closeSuggestions(inputId, suggestionsId, selectionId);
}, 500);
});
var arrowElement = $(document.getElementById(downArrowId));
arrowElement.on('click', function() {
// Prevent the close timer, or we will open, then close the suggestions.
inputElement.focus();
if (closeSuggestionsTimer) {
window.clearTimeout(closeSuggestionsTimer);
}
// Show the suggestions list.
updateSuggestions(inputElement.val(), inputId, suggestionsId, originalSelect, multiple, tags);
});
if (options.showSuggestions) {
var arrowElement = $(document.getElementById(state.downArrowId));
arrowElement.on('click', function() {
// Prevent the close timer, or we will open, then close the suggestions.
inputElement.focus();
if (closeSuggestionsTimer) {
window.clearTimeout(closeSuggestionsTimer);
}
// Show the suggestions list.
updateSuggestions(options, state, inputElement.val(), originalSelect);
});
}
var suggestionsElement = $(document.getElementById(suggestionsId));
var suggestionsElement = $(document.getElementById(state.suggestionsId));
suggestionsElement.parent().on('click', '[role=option]', function(e) {
// Handle clicks on suggestions.
var element = $(e.currentTarget).closest('[role=option]');
var suggestionsElement = $(document.getElementById(suggestionsId));
var suggestionsElement = $(document.getElementById(state.suggestionsId));
// Find the index of the clicked on suggestion.
var current = suggestionsElement.children('[aria-hidden=false]').index(element);
// Activate it.
activateItem(current, inputId, suggestionsId);
activateItem(current, state);
// And select it.
selectCurrentItem(inputId, suggestionsId, selectionId, multiple, originalSelect);
selectCurrentItem(options, state, originalSelect);
});
var selectionElement = $(document.getElementById(selectionId));
var selectionElement = $(document.getElementById(state.selectionId));
// Handle clicks on the selected items (will unselect an item).
selectionElement.parent().on('click', '[role=listitem]', function(e) {
selectionElement.on('click', '[role=listitem]', function(e) {
// Get the item that was clicked.
var item = $(e.currentTarget);
// Remove it from the selection.
deselectItem(inputId, selectionId, item, originalSelect, multiple);
deselectItem(options, state, item, originalSelect);
});
// Keyboard navigation for the selection list.
selectionElement.parent().on('keydown', function(e) {
selectionElement.on('keydown', function(e) {
switch (e.keyCode) {
case KEYS.DOWN:
// Choose the next selection item.
activateNextSelection(selectionId);
activateNextSelection(state);
// We handled this event, so prevent it.
e.preventDefault();
return false;
case KEYS.UP:
// Choose the previous selection item.
activatePreviousSelection(selectionId);
activatePreviousSelection(state);
// We handled this event, so prevent it.
e.preventDefault();
return false;
case KEYS.SPACE:
case KEYS.ENTER:
// Get the item that is currently selected.
var selectedItem = $(document.getElementById(selectionId)).children('[data-active-selection=true]');
var selectedItem = $(document.getElementById(state.selectionId)).children('[data-active-selection=true]');
if (selectedItem) {
// Unselect this item.
deselectItem(inputId, selectionId, selectedItem, originalSelect, multiple);
deselectItem(options, state, selectedItem, originalSelect);
// We handled this event, so prevent it.
e.preventDefault();
}
@@ -639,10 +647,21 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
return true;
});
// Whenever the input field changes, update the suggestion list.
inputElement.on('input', function(e) {
var query = $(e.currentTarget).val();
updateSuggestions(query, inputId, suggestionsId, originalSelect, multiple, tags);
});
if (options.showSuggestions) {
inputElement.on('input', function(e) {
var query = $(e.currentTarget).val();
var last = $(e.currentTarget).data('last-value');
// IE11 fires many more input events than required - even when the value has not changed.
// We need to only do this for real value changed events or the suggestions will be
// unclickable on IE11 (because they will be rebuilt before the click event fires).
// Note - because of this we cannot close the list when the query is empty or it will break
// on IE11.
if (last !== query) {
updateSuggestions(options, state, query, originalSelect);
}
$(e.currentTarget).data('last-value', query);
});
}
};
return /** @alias module:core/form-autocomplete */ {
@@ -657,14 +676,29 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
* module must expose 2 functions "transport" and "processResults".
* These are modeled on Select2 see: https://select2.github.io/options.html#ajax
* @param {String} placeholder - The text to display before a selection is made.
* @param {Boolean} caseSensitive - If search has to be made case sensitive.
*/
enhance: function(selector, tags, ajax, placeholder) {
enhance: function(selector, tags, ajax, placeholder, caseSensitive, showSuggestions) {
// Set some default values.
if (typeof tags === "undefined") {
tags = false;
var options = {
selector: selector,
tags: false,
ajax: false,
placeholder: placeholder,
caseSensitive: false,
showSuggestions: true
};
if (typeof tags !== "undefined") {
options.tags = tags;
}
if (typeof ajax === "undefined") {
ajax = false;
if (typeof ajax !== "undefined") {
options.ajax = ajax;
}
if (typeof caseSensitive !== "undefined") {
options.caseSensitive = caseSensitive;
}
if (typeof showSuggestions !== "undefined") {
options.showSuggestions = showSuggestions;
}
// Look for the select element.
@@ -678,38 +712,30 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
originalSelect.hide().attr('aria-hidden', true);
// Find or generate some ids.
var selectId = originalSelect.attr('id');
var multiple = originalSelect.attr('multiple');
var inputId = 'form_autocomplete_input-' + $.now();
var suggestionsId = 'form_autocomplete_suggestions-' + $.now();
var selectionId = 'form_autocomplete_selection-' + $.now();
var downArrowId = 'form_autocomplete_downarrow-' + $.now();
var state = {
selectId: originalSelect.attr('id'),
inputId: 'form_autocomplete_input-' + $.now(),
suggestionsId: 'form_autocomplete_suggestions-' + $.now(),
selectionId: 'form_autocomplete_selection-' + $.now(),
downArrowId: 'form_autocomplete_downarrow-' + $.now()
};
options.multiple = originalSelect.attr('multiple');
var originalLabel = $('[for=' + selectId + ']');
var originalLabel = $('[for=' + state.selectId + ']');
// Create the new markup and insert it after the select.
var options = [];
var suggestions = [];
originalSelect.children('option').each(function(index, option) {
options[index] = { label: option.innerHTML, value: $(option).attr('value') };
suggestions[index] = { label: option.innerHTML, value: $(option).attr('value') };
});
// Render all the parts of our UI.
var renderInput = templates.render(
'core/form_autocomplete_input',
{ downArrowId: downArrowId,
inputId: inputId,
suggestionsId: suggestionsId,
selectionId: selectionId,
placeholder: placeholder,
multiple: multiple }
);
var renderDatalist = templates.render(
'core/form_autocomplete_suggestions',
{ inputId: inputId, suggestionsId: suggestionsId, options: options, multiple: multiple}
);
var renderSelection = templates.render(
'core/form_autocomplete_selection',
{ selectionId: selectionId, items: [], multiple: multiple}
);
var context = $.extend({}, options, state);
context.options = suggestions;
context.items = [];
var renderInput = templates.render('core/form_autocomplete_input', context);
var renderDatalist = templates.render('core/form_autocomplete_suggestions', context);
var renderSelection = templates.render('core/form_autocomplete_selection', context);
$.when(renderInput, renderDatalist, renderSelection).done(function(input, suggestions, selection) {
// Add our new UI elements to the page.
@@ -717,29 +743,29 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
originalSelect.after(input);
originalSelect.after(selection);
// Update the form label to point to the text input.
originalLabel.attr('for', inputId);
originalLabel.attr('for', state.inputId);
// Add the event handlers.
addNavigation(inputId, suggestionsId, downArrowId, selectionId, originalSelect, multiple, tags);
addNavigation(options, state, originalSelect);
var inputElement = $(document.getElementById(inputId));
var suggestionsElement = $(document.getElementById(suggestionsId));
var inputElement = $(document.getElementById(state.inputId));
var suggestionsElement = $(document.getElementById(state.suggestionsId));
// Hide the suggestions by default.
suggestionsElement.hide().attr('aria-hidden', true);
// If this field uses ajax, set it up.
if (ajax) {
require([ajax], function(ajaxHandler) {
if (options.ajax) {
require([options.ajax], function(ajaxHandler) {
var handler = function(e) {
updateAjax(e, selector, inputId, suggestionsId, originalSelect, multiple, tags, ajaxHandler);
updateAjax(e, options, state, originalSelect, ajaxHandler);
};
// Trigger an ajax update after the text field value changes.
inputElement.on("input keypress", handler);
var arrowElement = $(document.getElementById(downArrowId));
var arrowElement = $(document.getElementById(state.downArrowId));
arrowElement.on("click", handler);
});
}
// Show the current values in the selection list.
updateSelectionList(selectionId, inputId, originalSelect, multiple);
updateSelectionList(options, state, originalSelect);
});
}
};
+6 -1
View File
@@ -86,7 +86,12 @@ class behat_field_manager {
// Get the field type if is part of a moodleform.
if (self::is_moodleform_field($fieldnode)) {
$type = self::get_field_node_type($fieldnode, $session);
// This might go out of scope, finding element beyond the dom and fail. So fallback to guessing type.
try {
$type = self::get_field_node_type($fieldnode, $session);
} catch (WebDriver\Exception\InvalidSelector $e) {
$type = 'field';
}
}
// If is not a moodleforms field use the base field type.
+3 -1
View File
@@ -106,7 +106,9 @@ class behat_form_field {
// using the generic behat_form_field is because we are
// dealing with a fgroup element.
$instance = $this->guess_type();
return $instance->field->keyPress($char, $modifier);
$instance->field->keyDown($char, $modifier);
$instance->field->keyPress($char, $modifier);
$instance->field->keyUp($char, $modifier);
}
/**
-1
View File
@@ -537,7 +537,6 @@ class core_date {
self::$badzones['5.5'] = 'Asia/Kolkata';
self::$badzones['6.5'] = 'Asia/Rangoon';
self::$badzones['9.5'] = 'Australia/Darwin';
self::$badzones['11.5'] = 'Pacific/Norfolk';
// Remove bad zones that are elsewhere.
foreach (self::$bczones as $zone => $unused) {
+6
View File
@@ -491,6 +491,12 @@ class manager {
}
}
// Make sure the task data is unchanged.
if (!$DB->record_exists('task_scheduled', (array) $record)) {
$lock->release();
continue;
}
if (!$task->is_blocking()) {
$cronlock->release();
} else {
+3 -3
View File
@@ -180,13 +180,13 @@ class mysqli_native_moodle_database extends moodle_database {
return $engine;
}
// get the default database engine
$sql = "SELECT @@storage_engine";
// Get the default database engine.
$sql = "SELECT @@default_storage_engine engine";
$this->query_start($sql, NULL, SQL_QUERY_AUX);
$result = $this->mysqli->query($sql);
$this->query_end($result);
if ($rec = $result->fetch_assoc()) {
$engine = $rec['@@storage_engine'];
$engine = $rec['engine'];
}
$result->close();
+8
View File
@@ -44,6 +44,14 @@ $PAGE->set_context($context);
require_login($course, false, $cm);
require_sesskey();
if (isguestuser()) {
print_error('accessdenied', 'admin');
}
if (!in_array('atto', explode(',', get_config('core', 'texteditors')))) {
print_error('accessdenied', 'admin');
}
$action = required_param('action', PARAM_ALPHA);
$response = array();
+4
View File
@@ -166,6 +166,10 @@ class atto_texteditor extends texteditor {
}
$contentcss = $PAGE->theme->editor_css_url()->out(false);
// Autosave disabled for guests.
if (isguestuser()) {
$autosave = false;
}
// Note <> is a safe separator, because it will not appear in the output of s().
$pagehash = sha1($PAGE->url . '<>' . s($this->get_text()));
$params = array(
@@ -333,8 +333,8 @@ Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.Edi
};
xhr.open("POST", M.cfg.wwwroot + '/repository/repository_ajax.php?action=upload', true);
xhr.send(formData);
return false;
}
return false;
},
File diff suppressed because one or more lines are too long
@@ -333,8 +333,8 @@ Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.Edi
};
xhr.open("POST", M.cfg.wwwroot + '/repository/repository_ajax.php?action=upload', true);
xhr.send(formData);
return false;
}
return false;
},
+1 -1
View File
@@ -331,8 +331,8 @@ Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.Edi
};
xhr.open("POST", M.cfg.wwwroot + '/repository/repository_ajax.php?action=upload', true);
xhr.send(formData);
return false;
}
return false;
},
@@ -28,13 +28,13 @@ $string['all'] = 'Around each cell';
$string['allowbackgroundcolour'] = 'Allow background colour';
$string['allowbackgroundcolour_desc'] = 'Allow users to set the background colour for the table';
$string['allowborder'] = 'Allow borders';
$string['allowborder_desc'] = 'If checked, users will be able to customize borders when creating and editing a table';
$string['allowborder_desc'] = 'If enabled, table and cell borders can be customised. The default border is a solid, 1px, black line.';
$string['allowborderstyle'] = 'Allow border styling';
$string['allowborderstyle_desc'] = 'Allow users to style borders on tables. If this is not checked, but allowborder is checked, the default style is solid.<br>NOTE that this setting will not have an impact unless allowborder is checked';
$string['allowborderstyle_desc'] = 'This setting has no effect unless \'Allow borders\' is enabled.';
$string['allowbordersize'] = 'Allow border size control';
$string['allowbordersize_desc'] = 'Allow users to set the size of borders on tables. If this is not checked, but allowborder is checked, the default size is 1px.<br>NOTE that this setting will not have an impact unless allowborder is checked';
$string['allowbordersize_desc'] = 'This setting has no effect unless \'Allow borders\' is enabled.';
$string['allowbordercolour'] = 'Allow border colour';
$string['allowbordercolour_desc'] = 'Allow users to set the colour of the borders on tables. If this is not checked, but allowborder is checked, the default colour is black.<br>NOTE that this setting will not have an impact unless allowborder is checked';
$string['allowbordercolour_desc'] = 'This setting has no effect unless \'Allow borders\' is enabled.';
$string['allowwidth'] = 'Allow width';
$string['allowwidth_desc'] = 'Allow users to set the width of the tables.';
$string['appearance'] = 'Appearance';
@@ -45,15 +45,22 @@ $string['borders'] = 'Borders';
$string['bordersize'] = 'Size of borders';
$string['borderstyles'] = 'Style of borders';
$string['borderstyles_desc'] = 'Allowed border styles, separated by commas';
$string['borderstyles_default'] = 'solid,dashed,dotted';
$string['caption'] = 'Caption';
$string['captionposition'] = 'Caption position';
$string['columns'] = 'Columns';
$string['createtable'] = 'Create table';
$string['deletecolumn'] = 'Delete column';
$string['deleterow'] = 'Delete row';
$string['dashed'] = 'Dashed';
$string['dotted'] = 'Dotted';
$string['double'] = 'Double';
$string['edittable'] = 'Edit table';
$string['groove'] = 'Groove';
$string['headers'] = 'Define headers on';
$string['hidden'] = 'Hidden';
$string['inherit'] = 'Inherit';
$string['initial'] = 'Initial';
$string['inset'] = 'Inset';
$string['movecolumnleft'] = 'Move column left';
$string['movecolumnright'] = 'Move column right';
$string['moverowdown'] = 'Move row down';
@@ -63,10 +70,14 @@ $string['none'] = 'None';
$string['numberofcolumns'] = 'Number of columns';
$string['numberofrows'] = 'Number of rows';
$string['outer'] = 'Around table';
$string['outset'] = 'Outset';
$string['pluginname'] = 'Table';
$string['ridge'] = 'Ridge';
$string['rows'] = 'Rows';
$string['settings'] = 'Table settings';
$string['updatetable'] = 'Update table';
$string['width'] = 'Table width (in %)';
$string['solid'] = 'Solid';
$string['themedefault'] = 'Theme default';
$string['transparent'] = 'Transparent';
$string['unset'] = 'Unset';
$string['updatetable'] = 'Update table';
$string['width'] = 'Table width (in %)';
+13 -2
View File
@@ -60,8 +60,19 @@ function atto_table_strings_for_js() {
'width',
'outer',
'noborder',
'transparent',
'themedefault'),
'inherit',
'themedefault',
'initial',
'unset',
'hidden',
'dotted',
'dashed',
'solid',
'double',
'groove',
'ridge',
'inset',
'outset'),
'atto_table');
$PAGE->requires->strings_for_js(array('top',
+14 -10
View File
@@ -50,16 +50,20 @@ if ($ADMIN->fulltree) {
$name = new lang_string('borderstyles', 'atto_table');
$desc = new lang_string('borderstyles_desc', 'atto_table');
$default = ['none' => 'none',
'hidden' => 'hidden',
'dotted' => 'dotted',
'dashed' => 'dashed',
'solid' => 'solid',
'double' => 'double',
'groove' => 'groove',
'ridge' => 'ridge',
'inset' => 'inset',
'outset' => 'outset'];
$default = array(
'initial' => new lang_string('initial', 'atto_table'),
'unset' => new lang_string('unset', 'atto_table'),
'none' => new lang_string('none', 'atto_table'),
'hidden' => new lang_string('hidden', 'atto_table'),
'dotted' => new lang_string('dotted', 'atto_table'),
'dashed' => new lang_string('dashed', 'atto_table'),
'solid' => new lang_string('solid', 'atto_table'),
'double' => new lang_string('double', 'atto_table'),
'groove' => new lang_string('groove', 'atto_table'),
'ridge' => new lang_string('ridge', 'atto_table'),
'inset' => new lang_string('inset', 'atto_table'),
'outset' => new lang_string('outset', 'atto_table'),
);
$setting = new admin_setting_configmultiselect('atto_table/borderstyles',
$name,
+3
View File
@@ -34,3 +34,6 @@ div.availablecolors label input[type="radio"] {
margin-left: -15px;
}
input[name="bordersize"], input[name="width"] {
margin-right: 0.3em;
}
@@ -35,6 +35,71 @@ Feature: Atto tables
And I press "Save changes"
Then ".blog_entry table caption" "css_element" should be visible
@javascript
Scenario: Check table setting defaults on new table
Given the following config values are set as admin:
| config | value | plugin |
| allowborders | 1 | atto_table |
| allowborderstyles | 1 | atto_table |
| allowbordersize | 1 | atto_table |
| allowbordercolour | 1 | atto_table |
| allowbackgroundcolour | 1 | atto_table |
| allowwidth | 1 | atto_table |
And I log in as "admin"
And I follow "Profile" in the user menu
And I follow "Blog entries"
And I follow "Add a new entry"
And I set the field "Entry title" to "How to make a table"
And I set the field "Blog entry body" to "Table test"
And I select the text in the "Blog entry body" Atto editor
And I click on "Show more buttons" "button"
When I click on "Table" "button"
Then the ".moodle-dialogue-base .atto_form .borderstyle" "css_element" should be disabled
And the ".moodle-dialogue-base .atto_form .bordersize" "css_element" should be disabled
And the "borderColour" "radio" should be disabled
And the field "borders" matches value "default"
And the field "Theme default" matches value "1"
And the field "Table width (in %)" matches value ""
@javascript
Scenario: Edit a table restores settings
Given the following config values are set as admin:
| config | value | plugin |
| allowborders | 1 | atto_table |
| allowborderstyles | 1 | atto_table |
| allowbordersize | 1 | atto_table |
| allowbordercolour | 1 | atto_table |
| allowbackgroundcolour | 1 | atto_table |
| allowwidth | 1 | atto_table |
And I log in as "admin"
And I follow "Profile" in the user menu
And I follow "Blog entries"
And I follow "Add a new entry"
And I set the field "Entry title" to "How to make a table"
And I set the field "Blog entry body" to "<table><tr><td>Cell</td></tr></table>"
And I select the text in the "Blog entry body" Atto editor
And I click on "Show more buttons" "button"
And I click on "Table" "button"
And I click on "Edit table" "link"
And I click on "#FFFFFF" "radio" in the ".moodle-dialogue-base .atto_form .backgroundcolour" "css_element"
And I set the field "Table width (in %)" to "100"
And I set the field "Borders" to "Around table"
And I set the field "Style of borders" to "Dashed"
And I set the field "Size of borders" to "2"
And I click on "#FFFFFF" "radio" in the ".moodle-dialogue-base .atto_form .bordercolour" "css_element"
And I press "Update table"
And I select the text in the "Blog entry body" Atto editor
And I click on "Table" "button"
When I click on "Edit table" "link"
# Check that the background colour is set correctly.
Then the field with xpath "//label/input[@name='backgroundColour' and @value='#FFFFFF']" matches value "1"
And the field "Table width (in %)" matches value "100"
And the field "Borders" matches value "Around table"
And the field "Style of borders" matches value "dashed"
And the field "Size of borders" matches value "2"
# Check that the border colour is set correctly.
And the field with xpath "//label/input[@name='borderColour' and @value='#FFFFFF']" matches value "1"
@javascript
Scenario: Create a table with background colour and width with border settings off
Given the following config values are set as admin:
@@ -67,7 +132,8 @@ Feature: Atto tables
And I follow "Edit"
And I click on "Show more buttons" "button"
And I click on "HTML" "button"
Then I should see "style=\"background-color:#FFFFFF;width:100%;\""
Then I should see "background-color:rgb(255,255,255);"
And I should see "width:100%;"
@javascript
Scenario: Edit a table with background colour and width with border settings off
@@ -102,7 +168,8 @@ Feature: Atto tables
And I follow "Edit"
And I click on "Show more buttons" "button"
And I click on "HTML" "button"
Then I should see "style=\"width:100%;background-color:rgb(255,255,255);\""
Then I should see "background-color:rgb(255,255,255);"
And I should see "width:100%;"
@javascript
Scenario: Create a table with background colour and width with borders on
@@ -137,7 +204,8 @@ Feature: Atto tables
And I follow "Edit"
And I click on "Show more buttons" "button"
And I click on "HTML" "button"
Then I should see "style=\"border:1px solid #FFFFFF;background-color:#FFFFFF;width:100%;\""
Then I should see "background-color:rgb(255,255,255);"
And I should see "width:100%;"
@javascript
Scenario: Edit a table with background colour and width with borders on
@@ -167,13 +235,14 @@ Feature: Atto tables
Then ".moodle-dialogue-base .atto_form .customwidth" "css_element" should exist
And I click on "#FFFFFF" "radio" in the ".moodle-dialogue-base .atto_form .backgroundcolour" "css_element"
And I set the field "Table width (in %)" to "100"
And I set the field "Borders" to "Around table"
And I set the field "Borders" to "Around each cell"
And I press "Update table"
And I press "Save changes"
And I follow "Edit"
And I click on "Show more buttons" "button"
And I click on "HTML" "button"
Then I should see "style=\"border:1px solid rgb(255,255,255);width:100%;background-color:rgb(255,255,255);\""
Then I should see "background-color:rgb(255,255,255);"
And I should see "width:100%;"
@javascript
Scenario: Create a table with background colour and width with borders and border styling on
@@ -203,13 +272,15 @@ Feature: Atto tables
And I click on "#FFFFFF" "radio" in the ".moodle-dialogue-base .atto_form .backgroundcolour" "css_element"
And I set the field "Table width (in %)" to "100"
And I set the field "Borders" to "Around table"
And I set the field "Style of borders" to "dashed"
And I set the field "Style of borders" to "Dashed"
And I press "Create table"
And I press "Save changes"
And I follow "Edit"
And I click on "Show more buttons" "button"
And I click on "HTML" "button"
Then I should see "style=\"border:1px dashed #FFFFFF;background-color:#FFFFFF;width:100%;\""
Then I should see "background-color:rgb(255,255,255);"
And I should see "width:100%;"
And I should see "border-style:dashed;"
@javascript
Scenario: Edit a table with background colour and width with borders and border styling on
@@ -240,13 +311,15 @@ Feature: Atto tables
And I click on "#FFFFFF" "radio" in the ".moodle-dialogue-base .atto_form .backgroundcolour" "css_element"
And I set the field "Table width (in %)" to "100"
And I set the field "Borders" to "Around table"
And I set the field "Style of borders" to "dashed"
And I set the field "Style of borders" to "Dashed"
And I press "Update table"
And I press "Save changes"
And I follow "Edit"
And I click on "Show more buttons" "button"
And I click on "HTML" "button"
Then I should see "style=\"border:1px dashed rgb(255,255,255);width:100%;background-color:rgb(255,255,255);\""
Then I should see "background-color:rgb(255,255,255);"
And I should see "width:100%;"
And I should see "border-style:dashed;"
@javascript
Scenario: Create a table with background colour and width with borders, border styling, and border size on
@@ -276,14 +349,17 @@ Feature: Atto tables
And I click on "#FFFFFF" "radio" in the ".moodle-dialogue-base .atto_form .backgroundcolour" "css_element"
And I set the field "Table width (in %)" to "100"
And I set the field "Borders" to "Around table"
And I set the field "Style of borders" to "dashed"
And I set the field "Style of borders" to "Dashed"
And I set the field "Size of borders" to "2"
And I press "Create table"
And I press "Save changes"
And I follow "Edit"
And I click on "Show more buttons" "button"
And I click on "HTML" "button"
Then I should see "style=\"border:2px dashed #FFFFFF;background-color:#FFFFFF;width:100%;\""
Then I should see "background-color:rgb(255,255,255);"
And I should see "width:100%;"
And I should see "border-style:dashed;"
And I should see "border-width:2px;"
@javascript
Scenario: Edit a table with background colour and width with borders, border styling, and border size on
@@ -314,14 +390,17 @@ Feature: Atto tables
And I click on "#FFFFFF" "radio" in the ".moodle-dialogue-base .atto_form .backgroundcolour" "css_element"
And I set the field "Table width (in %)" to "100"
And I set the field "Borders" to "Around table"
And I set the field "Style of borders" to "dashed"
And I set the field "Style of borders" to "Dashed"
And I set the field "Size of borders" to "2"
And I press "Update table"
And I press "Save changes"
And I follow "Edit"
And I click on "Show more buttons" "button"
And I click on "HTML" "button"
Then I should see "style=\"border:2px dashed rgb(255,255,255);width:100%;background-color:rgb(255,255,255);\""
Then I should see "background-color:rgb(255,255,255);"
And I should see "width:100%;"
And I should see "border-style:dashed;"
And I should see "border-width:2px;"
@javascript
Scenario: Create a table with all settings on
@@ -351,7 +430,7 @@ Feature: Atto tables
And I click on "#FFFFFF" "radio" in the ".moodle-dialogue-base .atto_form .backgroundcolour" "css_element"
And I set the field "Table width (in %)" to "100"
And I set the field "Borders" to "Around table"
And I set the field "Style of borders" to "dashed"
And I set the field "Style of borders" to "Dashed"
And I set the field "Size of borders" to "2"
And I click on "#FFFFFF" "radio" in the ".moodle-dialogue-base .atto_form .bordercolour" "css_element"
And I press "Create table"
@@ -359,7 +438,11 @@ Feature: Atto tables
And I follow "Edit"
And I click on "Show more buttons" "button"
And I click on "HTML" "button"
Then I should see "style=\"border:2px dashed #FFFFFF;background-color:#FFFFFF;width:100%;\""
Then I should see "background-color:rgb(255,255,255);"
And I should see "width:100%;"
And I should see "dashed"
And I should see "2px"
And I should see "rgb(255,255,255)"
@javascript
Scenario: Edit a table with background colour and width with borders, border styling, and border size on
@@ -387,15 +470,19 @@ Feature: Atto tables
Then ".moodle-dialogue-base .atto_form .bordercolour" "css_element" should exist
Then ".moodle-dialogue-base .atto_form .backgroundcolour" "css_element" should exist
Then ".moodle-dialogue-base .atto_form .customwidth" "css_element" should exist
And I click on "#FFFFFF" "radio" in the ".moodle-dialogue-base .atto_form .backgroundcolour" "css_element"
And I click on "FFFFFF" "radio" in the ".moodle-dialogue-base .atto_form .backgroundcolour" "css_element"
And I set the field "Table width (in %)" to "100"
And I set the field "Borders" to "Around table"
And I set the field "Style of borders" to "dashed"
And I set the field "Style of borders" to "Dashed"
And I set the field "Size of borders" to "2"
And I click on "#FFFFFF" "radio" in the ".moodle-dialogue-base .atto_form .bordercolour" "css_element"
And I click on "FFFFFF" "radio" in the ".moodle-dialogue-base .atto_form .bordercolour" "css_element"
And I press "Update table"
And I press "Save changes"
And I follow "Edit"
And I click on "Show more buttons" "button"
And I click on "HTML" "button"
Then I should see "style=\"border:2px dashed rgb(255,255,255);width:100%;background-color:rgb(255,255,255);\""
Then I should see "background-color:rgb(255,255,255);"
And I should see "width:100%;"
And I should see "dashed"
And I should see "2px"
And I should see "rgb(255,255,255)"
@@ -35,11 +35,11 @@ YUI.add('moodle-atto_table-button', function (Y, NAME) {
var COMPONENT = 'atto_table',
DEFAULT = {
BORDERSTYLE: 'solid',
BORDERSTYLE: 'inherit',
BORDERWIDTH: '1'
},
DIALOGUE = {
WIDTH: '450px'
WIDTH: '480px'
},
TEMPLATE = '' +
'<form class="{{CSS.FORM}}">' +
@@ -78,9 +78,8 @@ var COMPONENT = 'atto_table',
'<legend class="mdl-align">{{get_string "appearance" component}}</legend>' +
'{{#if allowBorders}}' +
'<label for="{{elementid}}_atto_table_borders" class="sameline">{{get_string "borders" component}}</label>' +
'<select class="{{CSS.BORDERS}}" id="{{elementid}}_atto_table_borders">' +
'<select name="borders" class="{{CSS.BORDERS}}" id="{{elementid}}_atto_table_borders">' +
'<option value="default">{{get_string "themedefault" component}}' + '</option>' +
'<option value="none">{{get_string "noborder" component}}' + '</option>' +
'<option value="outer">{{get_string "outer" component}}' + '</option>' +
'<option value="all">{{get_string "all" component}}' + '</option>' +
'</select>' +
@@ -88,9 +87,10 @@ var COMPONENT = 'atto_table',
'{{#if allowBorderStyles}}' +
'<label for="{{elementid}}_atto_table_borderstyle" class="sameline">' +
'{{get_string "borderstyles" component}}</label>' +
'<select class="{{CSS.BORDERSTYLE}}" id="{{elementid}}_atto_table_borderstyle">' +
'<select name="borderstyles" class="{{CSS.BORDERSTYLE}}" id="{{elementid}}_atto_table_borderstyle">' +
'<option value="inherit">{{get_string "inherit" component}}</option>' +
'{{#each borderStyles}}' +
'<option value="' + '{{this}}' + '">' + '{{this}}' + '</option>' +
'<option value="' + '{{this}}' + '">' + '{{get_string this ../component}}' + '</option>' +
'{{/each}}' +
'</select>' +
'<br>' +
@@ -98,7 +98,7 @@ var COMPONENT = 'atto_table',
'{{#if allowBorderSize}}' +
'<label for="{{elementid}}_atto_table_bordersize" class="sameline">' +
'{{get_string "bordersize" component}}</label>' +
'<input id="{{elementid}}_atto_table_bordersize" class="{{CSS.BORDERSIZE}}"' +
'<input name="bordersize" id="{{elementid}}_atto_table_bordersize" class="{{CSS.BORDERSIZE}}"' +
'type="number" value="1" size="8" min="1" max="50"/>' +
'<label style="display: inline-block;">{{CSS.BORDERSIZEUNIT}}</label>' +
'<br>' +
@@ -112,10 +112,10 @@ var COMPONENT = 'atto_table',
'style="background-color:transparent;color:transparent">' +
'<input id="{{../elementid}}_atto_table_bordercolour_-1"' +
'type="radio" name="borderColour" value="transparent" checked="checked"'+
'title="{{get_string "transparent" component}}"></input>' +
'type="radio" name="borderColour" value="none" checked="checked"'+
'title="{{get_string "themedefault" component}}"></input>' +
'{{get_string "transparent" component}}' +
'{{get_string "themedefault" component}}' +
'</label>' +
'{{#each availableColours}}' +
'<label for="{{../elementid}}_atto_table_bordercolour_{{@index}}"' +
@@ -140,10 +140,10 @@ var COMPONENT = 'atto_table',
'style="background-color:transparent;color:transparent">' +
'<input id="{{../elementid}}_atto_table_backgroundcolour_-1"' +
'type="radio" name="backgroundColour" value="transparent" checked="checked"'+
'title="{{get_string "transparent" component}}"></input>' +
'type="radio" name="backgroundColour" value="none" checked="checked"'+
'title="{{get_string "themedefault" component}}"></input>' +
'{{get_string "transparent" component}}' +
'{{get_string "themedefault" component}}' +
'</label>' +
'{{#each availableColours}}' +
@@ -162,7 +162,7 @@ var COMPONENT = 'atto_table',
'{{#if allowWidth}}' +
'<label for="{{elementid}}_atto_table_width" class="sameline">' +
'{{get_string "width" component}}</label>' +
'<input id="{{elementid}}_atto_table_width" class="{{CSS.WIDTH}}" size="8" type="number" min="0" max="100"/>' +
'<input name="width" id="{{elementid}}_atto_table_width" class="{{CSS.WIDTH}}" size="8" type="number" min="0" max="100"/>' +
'<label style="display: inline-block;">{{CSS.WIDTHUNIT}}</label>' +
'<br>' +
'{{/if}}' +
@@ -289,6 +289,8 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
// Set the dialogue content, and then show the dialogue.
dialogue.set('bodyContent', this._getDialogueContent(false))
.show();
this._updateAvailableSettings();
}
},
@@ -334,7 +336,7 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
*/
_getDialogueContent: function(edit) {
var template = Y.Handlebars.compile(TEMPLATE);
var availableColours = this.get('availableColors');
var allowBorders = this.get('allowBorders');
this._content = Y.Node.create(template({
CSS: CSS,
@@ -343,13 +345,13 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
edit: edit,
nonedit: !edit,
allowStyling: this.get('allowStyling'),
allowBorders: this.get('allowBorders'),
allowBorders: allowBorders,
allowBorderStyles: this.get('allowBorderStyles'),
borderStyles: this.get('borderStyles'),
allowBorderSize: this.get('allowBorderSize'),
allowBorderColour: this.get('allowBorderColour'),
allowBackgroundColour: this.get('allowBackgroundColour'),
availableColours: availableColours,
availableColours: this.get('availableColors'),
allowWidth: this.get('allowWidth')
}));
@@ -360,9 +362,48 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
this._content.one('.submit').on('click', this._setTable, this);
}
if (allowBorders) {
this._content.one('[name="borders"]').on('change', this._updateAvailableSettings, this);
}
return this._content;
},
/**
* Disables options within the dialogue if they shouldn't be available.
* E.g.
* If borders are set to "Theme default" then the border size, style and
* colour options are disabled.
*
* @method _updateAvailableSettings
* @private
*/
_updateAvailableSettings: function() {
var tableForm = this._content,
enableBorders = tableForm.one('[name="borders"]'),
borderStyle = tableForm.one('[name="borderstyles"]'),
borderSize = tableForm.one('[name="bordersize"]'),
borderColour = tableForm.all('[name="borderColour"]'),
disabledValue = 'removeAttribute';
if (enableBorders.get('value') === 'default') {
disabledValue = 'setAttribute';
}
if (borderStyle) {
borderStyle[disabledValue]('disabled');
}
if (borderSize) {
borderSize[disabledValue]('disabled');
}
if (borderColour) {
borderColour[disabledValue]('disabled');
}
},
/**
* Given the current selection, return a table cell suitable for table editing
* purposes, i.e. the first table cell selected, or the first cell in the table
@@ -436,10 +477,6 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
borderstyle,
bordercolour,
backgroundcolour,
backgroundcolourvalue = '#FFFFFF',
borderSizeValue = '1',
borderStyleValue = 'solid',
borderhex = '#FFFFFF',
table,
width,
captionnode;
@@ -461,51 +498,16 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
backgroundcolour = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.SELECTEDBACKGROUNDCOLOUR);
width = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.WIDTH);
if (bordercolour) {
borderhex = bordercolour.get('value');
}
if (bordersize) {
borderSizeValue = bordersize.get('value');
}
if (borderstyle) {
borderStyleValue = borderstyle.get('value');
}
if (backgroundcolour) {
backgroundcolourvalue = backgroundcolour.get('value');
}
table = this._lastTarget.ancestor('table');
// Clear the inline border styling
table.removeAttribute('style');
table.all('td, th').each(function() {
this.removeAttribute('style');
this._setAppearance(table, {
width: width,
borders: borders,
borderColour: bordercolour,
borderSize: bordersize,
borderStyle: borderstyle,
backgroundColour: backgroundcolour
});
if (borders) {
if (borders.get('value') === 'outer') {
table.setStyle('border', borderSizeValue + CSS.BORDERSIZEUNIT + ' ' +
borderStyleValue + ' ' + borderhex);
} else if (borders.get('value') === 'all') {
table.all('td, th').each(function() {
this.setStyle('border', borderSizeValue + CSS.BORDERSIZEUNIT + ' ' +
borderStyleValue + ' ' + borderhex);
});
} else if (borders.get('value') === 'none') {
table.setStyle('border', 'none');
}
}
if (width && width.get('value')) {
table.setStyle('width', width.get('value') + CSS.WIDTHUNIT);
}
if (backgroundcolourvalue !== '') {
table.setStyle('background-color', backgroundcolourvalue);
}
captionnode = table.one('caption');
if (!captionnode) {
captionnode = Y.Node.create('<caption></caption>');
@@ -589,14 +591,9 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
var caption,
captionposition,
borders,
bordertable = '',
bordercell = '',
bordersize,
borderstyle,
bordercolour,
borderSizeValue = '1',
borderStyleValue = 'solid',
borderhex = '#FFFFFF',
rows,
cols,
headers,
@@ -624,52 +621,13 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
headers = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.HEADERS);
width = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.WIDTH);
if (bordercolour) {
borderhex = bordercolour.get('value');
}
if (bordersize) {
borderSizeValue = bordersize.get('value');
}
if (borderstyle) {
borderStyleValue = borderstyle.get('value');
}
if (borders) {
if (borders.get('value') === 'outer') {
bordertable = ' style="border: ' + borderSizeValue + CSS.BORDERSIZEUNIT + ' ' +
borderStyleValue + ' ' + borderhex + ';"';
} else if (borders.get('value') === 'all') {
bordercell = ' style="border: ' + borderSizeValue + CSS.BORDERSIZEUNIT + ' ' +
borderStyleValue + ' ' + borderhex + ';"';
}
}
if (backgroundcolour) {
if (bordertable !== '') {
bordertable = bordertable.substring(0, bordertable.length-1) +
'background-color: ' + backgroundcolour.get('value') + ';"';
} else {
bordertable = ' style="background-color: ' + backgroundcolour.get('value') + ';"';
}
}
if (width && width.get('value')) {
if (bordertable !== '') {
bordertable = bordertable.substring(0, bordertable.length-1) + 'width: ' +
width.get('value') + CSS.WIDTHUNIT + ';"';
} else {
bordertable = ' style="width: ' + width.get('value') + CSS.WIDTHUNIT + ';"';
}
}
// Set the selection.
this.get('host').setSelection(this._currentSelection);
// Note there are some spaces inserted in the cells and before and after, so that users have somewhere to click.
var nl = "\n";
tablehtml = '<br/>' + nl + '<table' + bordertable + '>' + nl;
var tableId = Y.guid();
tablehtml = '<br/>' + nl + '<table id="' + tableId + '">' + nl;
var captionstyle = '';
if (captionposition.get('value')) {
@@ -681,7 +639,7 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
i = 1;
tablehtml += '<thead>' + nl + '<tr>' + nl;
for (j = 0; j < parseInt(cols.get('value'), 10); j++) {
tablehtml += '<th scope="col"' + bordercell + '></th>' + nl;
tablehtml += '<th scope="col"></th>' + nl;
}
tablehtml += '</tr>' + nl + '</thead>' + nl;
}
@@ -690,9 +648,9 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
tablehtml += '<tr>' + nl;
for (j = 0; j < parseInt(cols.get('value'), 10); j++) {
if (j === 0 && (headers.get('value') === 'rows' || headers.get('value') === 'both')) {
tablehtml += '<th scope="row"' + bordercell + '></th>' + nl;
tablehtml += '<th scope="row"></th>' + nl;
} else {
tablehtml += '<td ' + bordercell + '></td>' + nl;
tablehtml += '<td ></td>' + nl;
}
}
tablehtml += '</tr>' + nl;
@@ -702,6 +660,17 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
this.get('host').insertContentAtFocusPoint(tablehtml);
var tableNode = Y.one('#' + tableId);
this._setAppearance(tableNode, {
width: width,
borders: borders,
borderColour: bordercolour,
borderSize: bordersize,
borderStyle: borderstyle,
backgroundColour: backgroundcolour
});
tableNode.removeAttribute('id');
// Mark the content as updated.
this.markUpdated();
},
@@ -1148,6 +1117,73 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
return false;
},
/**
* Set the appropriate styles on the given table node according to
* the provided configuration.
*
* @method _setAppearance
* @param {Node} The table node to be modified.
* @param {Object} Configuration object (associative array) containing the form nodes for
* border styling.
* @private
*/
_setAppearance: function(tableNode, configuration) {
var borderhex,
borderSizeValue,
borderStyleValue,
backgroundcolourvalue;
if (configuration.borderColour) {
borderhex = configuration.borderColour.get('value');
}
if (configuration.borderSize) {
borderSizeValue = configuration.borderSize.get('value');
}
if (configuration.borderStyle) {
borderStyleValue = configuration.borderStyle.get('value');
}
if (configuration.backgroundColour) {
backgroundcolourvalue = configuration.backgroundColour.get('value');
}
// Clear the inline border styling
tableNode.removeAttribute('style');
tableNode.all('td, th').each(function(cell) {
cell.removeAttribute('style');
}, this);
if (configuration.borders) {
if (configuration.borders.get('value') === 'outer') {
tableNode.setStyle('borderWidth', borderSizeValue + CSS.BORDERSIZEUNIT);
tableNode.setStyle('borderStyle', borderStyleValue);
if (borderhex !== 'none') {
tableNode.setStyle('borderColor', borderhex);
}
} else if (configuration.borders.get('value') === 'all') {
tableNode.all('td, th').each(function(cell) {
cell.setStyle('borderWidth', borderSizeValue + CSS.BORDERSIZEUNIT);
cell.setStyle('borderStyle', borderStyleValue);
if (borderhex !== 'none') {
cell.setStyle('borderColor', borderhex);
}
}, this);
}
}
if (backgroundcolourvalue !== 'none') {
tableNode.setStyle('backgroundColor', backgroundcolourvalue);
}
if (configuration.width && configuration.width.get('value')) {
tableNode.setStyle('width', configuration.width.get('value') + CSS.WIDTHUNIT);
}
},
/**
* Edit table (show the dialogue).
*
@@ -1174,7 +1210,9 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
backgroundcolours = node.all(SELECTORS.BACKGROUNDCOLOURS),
width = node.one(SELECTORS.WIDTH),
table = this._lastTarget.ancestor('table'),
captionnode = table.one('caption');
captionnode = table.one('caption'),
hexColour,
matchedInput;
if (captionnode) {
captioninput.set('value', captionnode.getHTML());
@@ -1193,9 +1231,9 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
captionpositioninput.set('value', '');
}
if (table.getStyle('background-color') !== 'transparent' && this.get('allowBackgroundColour')) {
var hexColour = Y.Color.toHex(table.getStyle('background-color'));
var matchedInput = backgroundcolours.filter('[value="' + hexColour + '"]');
if (table.getStyle('backgroundColor') && this.get('allowBackgroundColour')) {
hexColour = Y.Color.toHex(table.getStyle('backgroundColor'));
matchedInput = backgroundcolours.filter('[value="' + hexColour + '"]');
if (matchedInput) {
matchedInput.set("checked", true);
@@ -1207,11 +1245,7 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
borderConfiguration = this._getBorderConfiguration(table);
if (borderConfiguration) {
if (borderConfiguration.borderStyle && borderConfiguration.borderStyle === 'none') {
borderValue = 'none';
} else {
borderValue = 'outer';
}
borderValue = 'outer';
} else {
borderConfiguration = this._getBorderConfiguration(table.one('td'));
if (borderConfiguration) {
@@ -1226,8 +1260,8 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
bordersize.set('value', borderSize);
borderinput.set('value', borderValue);
var hexColour = borderConfiguration.borderColor;
var matchedInput = bordercolours.filter('[value="' + hexColour + '"]');
hexColour = borderConfiguration.borderColor;
matchedInput = bordercolours.filter('[value="' + hexColour + '"]');
if (matchedInput) {
matchedInput.set("checked", true);
@@ -1244,6 +1278,7 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
}
headersinput.set('value', headersvalue);
dialogue.set('bodyContent', node).show();
this._updateAvailableSettings();
},
File diff suppressed because one or more lines are too long
@@ -35,11 +35,11 @@ YUI.add('moodle-atto_table-button', function (Y, NAME) {
var COMPONENT = 'atto_table',
DEFAULT = {
BORDERSTYLE: 'solid',
BORDERSTYLE: 'inherit',
BORDERWIDTH: '1'
},
DIALOGUE = {
WIDTH: '450px'
WIDTH: '480px'
},
TEMPLATE = '' +
'<form class="{{CSS.FORM}}">' +
@@ -78,9 +78,8 @@ var COMPONENT = 'atto_table',
'<legend class="mdl-align">{{get_string "appearance" component}}</legend>' +
'{{#if allowBorders}}' +
'<label for="{{elementid}}_atto_table_borders" class="sameline">{{get_string "borders" component}}</label>' +
'<select class="{{CSS.BORDERS}}" id="{{elementid}}_atto_table_borders">' +
'<select name="borders" class="{{CSS.BORDERS}}" id="{{elementid}}_atto_table_borders">' +
'<option value="default">{{get_string "themedefault" component}}' + '</option>' +
'<option value="none">{{get_string "noborder" component}}' + '</option>' +
'<option value="outer">{{get_string "outer" component}}' + '</option>' +
'<option value="all">{{get_string "all" component}}' + '</option>' +
'</select>' +
@@ -88,9 +87,10 @@ var COMPONENT = 'atto_table',
'{{#if allowBorderStyles}}' +
'<label for="{{elementid}}_atto_table_borderstyle" class="sameline">' +
'{{get_string "borderstyles" component}}</label>' +
'<select class="{{CSS.BORDERSTYLE}}" id="{{elementid}}_atto_table_borderstyle">' +
'<select name="borderstyles" class="{{CSS.BORDERSTYLE}}" id="{{elementid}}_atto_table_borderstyle">' +
'<option value="inherit">{{get_string "inherit" component}}</option>' +
'{{#each borderStyles}}' +
'<option value="' + '{{this}}' + '">' + '{{this}}' + '</option>' +
'<option value="' + '{{this}}' + '">' + '{{get_string this ../component}}' + '</option>' +
'{{/each}}' +
'</select>' +
'<br>' +
@@ -98,7 +98,7 @@ var COMPONENT = 'atto_table',
'{{#if allowBorderSize}}' +
'<label for="{{elementid}}_atto_table_bordersize" class="sameline">' +
'{{get_string "bordersize" component}}</label>' +
'<input id="{{elementid}}_atto_table_bordersize" class="{{CSS.BORDERSIZE}}"' +
'<input name="bordersize" id="{{elementid}}_atto_table_bordersize" class="{{CSS.BORDERSIZE}}"' +
'type="number" value="1" size="8" min="1" max="50"/>' +
'<label style="display: inline-block;">{{CSS.BORDERSIZEUNIT}}</label>' +
'<br>' +
@@ -112,10 +112,10 @@ var COMPONENT = 'atto_table',
'style="background-color:transparent;color:transparent">' +
'<input id="{{../elementid}}_atto_table_bordercolour_-1"' +
'type="radio" name="borderColour" value="transparent" checked="checked"'+
'title="{{get_string "transparent" component}}"></input>' +
'type="radio" name="borderColour" value="none" checked="checked"'+
'title="{{get_string "themedefault" component}}"></input>' +
'{{get_string "transparent" component}}' +
'{{get_string "themedefault" component}}' +
'</label>' +
'{{#each availableColours}}' +
'<label for="{{../elementid}}_atto_table_bordercolour_{{@index}}"' +
@@ -140,10 +140,10 @@ var COMPONENT = 'atto_table',
'style="background-color:transparent;color:transparent">' +
'<input id="{{../elementid}}_atto_table_backgroundcolour_-1"' +
'type="radio" name="backgroundColour" value="transparent" checked="checked"'+
'title="{{get_string "transparent" component}}"></input>' +
'type="radio" name="backgroundColour" value="none" checked="checked"'+
'title="{{get_string "themedefault" component}}"></input>' +
'{{get_string "transparent" component}}' +
'{{get_string "themedefault" component}}' +
'</label>' +
'{{#each availableColours}}' +
@@ -162,7 +162,7 @@ var COMPONENT = 'atto_table',
'{{#if allowWidth}}' +
'<label for="{{elementid}}_atto_table_width" class="sameline">' +
'{{get_string "width" component}}</label>' +
'<input id="{{elementid}}_atto_table_width" class="{{CSS.WIDTH}}" size="8" type="number" min="0" max="100"/>' +
'<input name="width" id="{{elementid}}_atto_table_width" class="{{CSS.WIDTH}}" size="8" type="number" min="0" max="100"/>' +
'<label style="display: inline-block;">{{CSS.WIDTHUNIT}}</label>' +
'<br>' +
'{{/if}}' +
@@ -289,6 +289,8 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
// Set the dialogue content, and then show the dialogue.
dialogue.set('bodyContent', this._getDialogueContent(false))
.show();
this._updateAvailableSettings();
}
},
@@ -334,7 +336,7 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
*/
_getDialogueContent: function(edit) {
var template = Y.Handlebars.compile(TEMPLATE);
var availableColours = this.get('availableColors');
var allowBorders = this.get('allowBorders');
this._content = Y.Node.create(template({
CSS: CSS,
@@ -343,13 +345,13 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
edit: edit,
nonedit: !edit,
allowStyling: this.get('allowStyling'),
allowBorders: this.get('allowBorders'),
allowBorders: allowBorders,
allowBorderStyles: this.get('allowBorderStyles'),
borderStyles: this.get('borderStyles'),
allowBorderSize: this.get('allowBorderSize'),
allowBorderColour: this.get('allowBorderColour'),
allowBackgroundColour: this.get('allowBackgroundColour'),
availableColours: availableColours,
availableColours: this.get('availableColors'),
allowWidth: this.get('allowWidth')
}));
@@ -360,9 +362,48 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
this._content.one('.submit').on('click', this._setTable, this);
}
if (allowBorders) {
this._content.one('[name="borders"]').on('change', this._updateAvailableSettings, this);
}
return this._content;
},
/**
* Disables options within the dialogue if they shouldn't be available.
* E.g.
* If borders are set to "Theme default" then the border size, style and
* colour options are disabled.
*
* @method _updateAvailableSettings
* @private
*/
_updateAvailableSettings: function() {
var tableForm = this._content,
enableBorders = tableForm.one('[name="borders"]'),
borderStyle = tableForm.one('[name="borderstyles"]'),
borderSize = tableForm.one('[name="bordersize"]'),
borderColour = tableForm.all('[name="borderColour"]'),
disabledValue = 'removeAttribute';
if (enableBorders.get('value') === 'default') {
disabledValue = 'setAttribute';
}
if (borderStyle) {
borderStyle[disabledValue]('disabled');
}
if (borderSize) {
borderSize[disabledValue]('disabled');
}
if (borderColour) {
borderColour[disabledValue]('disabled');
}
},
/**
* Given the current selection, return a table cell suitable for table editing
* purposes, i.e. the first table cell selected, or the first cell in the table
@@ -436,10 +477,6 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
borderstyle,
bordercolour,
backgroundcolour,
backgroundcolourvalue = '#FFFFFF',
borderSizeValue = '1',
borderStyleValue = 'solid',
borderhex = '#FFFFFF',
table,
width,
captionnode;
@@ -461,51 +498,16 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
backgroundcolour = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.SELECTEDBACKGROUNDCOLOUR);
width = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.WIDTH);
if (bordercolour) {
borderhex = bordercolour.get('value');
}
if (bordersize) {
borderSizeValue = bordersize.get('value');
}
if (borderstyle) {
borderStyleValue = borderstyle.get('value');
}
if (backgroundcolour) {
backgroundcolourvalue = backgroundcolour.get('value');
}
table = this._lastTarget.ancestor('table');
// Clear the inline border styling
table.removeAttribute('style');
table.all('td, th').each(function() {
this.removeAttribute('style');
this._setAppearance(table, {
width: width,
borders: borders,
borderColour: bordercolour,
borderSize: bordersize,
borderStyle: borderstyle,
backgroundColour: backgroundcolour
});
if (borders) {
if (borders.get('value') === 'outer') {
table.setStyle('border', borderSizeValue + CSS.BORDERSIZEUNIT + ' ' +
borderStyleValue + ' ' + borderhex);
} else if (borders.get('value') === 'all') {
table.all('td, th').each(function() {
this.setStyle('border', borderSizeValue + CSS.BORDERSIZEUNIT + ' ' +
borderStyleValue + ' ' + borderhex);
});
} else if (borders.get('value') === 'none') {
table.setStyle('border', 'none');
}
}
if (width && width.get('value')) {
table.setStyle('width', width.get('value') + CSS.WIDTHUNIT);
}
if (backgroundcolourvalue !== '') {
table.setStyle('background-color', backgroundcolourvalue);
}
captionnode = table.one('caption');
if (!captionnode) {
captionnode = Y.Node.create('<caption></caption>');
@@ -589,14 +591,9 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
var caption,
captionposition,
borders,
bordertable = '',
bordercell = '',
bordersize,
borderstyle,
bordercolour,
borderSizeValue = '1',
borderStyleValue = 'solid',
borderhex = '#FFFFFF',
rows,
cols,
headers,
@@ -624,52 +621,13 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
headers = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.HEADERS);
width = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.WIDTH);
if (bordercolour) {
borderhex = bordercolour.get('value');
}
if (bordersize) {
borderSizeValue = bordersize.get('value');
}
if (borderstyle) {
borderStyleValue = borderstyle.get('value');
}
if (borders) {
if (borders.get('value') === 'outer') {
bordertable = ' style="border: ' + borderSizeValue + CSS.BORDERSIZEUNIT + ' ' +
borderStyleValue + ' ' + borderhex + ';"';
} else if (borders.get('value') === 'all') {
bordercell = ' style="border: ' + borderSizeValue + CSS.BORDERSIZEUNIT + ' ' +
borderStyleValue + ' ' + borderhex + ';"';
}
}
if (backgroundcolour) {
if (bordertable !== '') {
bordertable = bordertable.substring(0, bordertable.length-1) +
'background-color: ' + backgroundcolour.get('value') + ';"';
} else {
bordertable = ' style="background-color: ' + backgroundcolour.get('value') + ';"';
}
}
if (width && width.get('value')) {
if (bordertable !== '') {
bordertable = bordertable.substring(0, bordertable.length-1) + 'width: ' +
width.get('value') + CSS.WIDTHUNIT + ';"';
} else {
bordertable = ' style="width: ' + width.get('value') + CSS.WIDTHUNIT + ';"';
}
}
// Set the selection.
this.get('host').setSelection(this._currentSelection);
// Note there are some spaces inserted in the cells and before and after, so that users have somewhere to click.
var nl = "\n";
tablehtml = '<br/>' + nl + '<table' + bordertable + '>' + nl;
var tableId = Y.guid();
tablehtml = '<br/>' + nl + '<table id="' + tableId + '">' + nl;
var captionstyle = '';
if (captionposition.get('value')) {
@@ -681,7 +639,7 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
i = 1;
tablehtml += '<thead>' + nl + '<tr>' + nl;
for (j = 0; j < parseInt(cols.get('value'), 10); j++) {
tablehtml += '<th scope="col"' + bordercell + '></th>' + nl;
tablehtml += '<th scope="col"></th>' + nl;
}
tablehtml += '</tr>' + nl + '</thead>' + nl;
}
@@ -690,9 +648,9 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
tablehtml += '<tr>' + nl;
for (j = 0; j < parseInt(cols.get('value'), 10); j++) {
if (j === 0 && (headers.get('value') === 'rows' || headers.get('value') === 'both')) {
tablehtml += '<th scope="row"' + bordercell + '></th>' + nl;
tablehtml += '<th scope="row"></th>' + nl;
} else {
tablehtml += '<td ' + bordercell + '></td>' + nl;
tablehtml += '<td ></td>' + nl;
}
}
tablehtml += '</tr>' + nl;
@@ -702,6 +660,17 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
this.get('host').insertContentAtFocusPoint(tablehtml);
var tableNode = Y.one('#' + tableId);
this._setAppearance(tableNode, {
width: width,
borders: borders,
borderColour: bordercolour,
borderSize: bordersize,
borderStyle: borderstyle,
backgroundColour: backgroundcolour
});
tableNode.removeAttribute('id');
// Mark the content as updated.
this.markUpdated();
},
@@ -1148,6 +1117,73 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
return false;
},
/**
* Set the appropriate styles on the given table node according to
* the provided configuration.
*
* @method _setAppearance
* @param {Node} The table node to be modified.
* @param {Object} Configuration object (associative array) containing the form nodes for
* border styling.
* @private
*/
_setAppearance: function(tableNode, configuration) {
var borderhex,
borderSizeValue,
borderStyleValue,
backgroundcolourvalue;
if (configuration.borderColour) {
borderhex = configuration.borderColour.get('value');
}
if (configuration.borderSize) {
borderSizeValue = configuration.borderSize.get('value');
}
if (configuration.borderStyle) {
borderStyleValue = configuration.borderStyle.get('value');
}
if (configuration.backgroundColour) {
backgroundcolourvalue = configuration.backgroundColour.get('value');
}
// Clear the inline border styling
tableNode.removeAttribute('style');
tableNode.all('td, th').each(function(cell) {
cell.removeAttribute('style');
}, this);
if (configuration.borders) {
if (configuration.borders.get('value') === 'outer') {
tableNode.setStyle('borderWidth', borderSizeValue + CSS.BORDERSIZEUNIT);
tableNode.setStyle('borderStyle', borderStyleValue);
if (borderhex !== 'none') {
tableNode.setStyle('borderColor', borderhex);
}
} else if (configuration.borders.get('value') === 'all') {
tableNode.all('td, th').each(function(cell) {
cell.setStyle('borderWidth', borderSizeValue + CSS.BORDERSIZEUNIT);
cell.setStyle('borderStyle', borderStyleValue);
if (borderhex !== 'none') {
cell.setStyle('borderColor', borderhex);
}
}, this);
}
}
if (backgroundcolourvalue !== 'none') {
tableNode.setStyle('backgroundColor', backgroundcolourvalue);
}
if (configuration.width && configuration.width.get('value')) {
tableNode.setStyle('width', configuration.width.get('value') + CSS.WIDTHUNIT);
}
},
/**
* Edit table (show the dialogue).
*
@@ -1174,7 +1210,9 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
backgroundcolours = node.all(SELECTORS.BACKGROUNDCOLOURS),
width = node.one(SELECTORS.WIDTH),
table = this._lastTarget.ancestor('table'),
captionnode = table.one('caption');
captionnode = table.one('caption'),
hexColour,
matchedInput;
if (captionnode) {
captioninput.set('value', captionnode.getHTML());
@@ -1193,9 +1231,9 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
captionpositioninput.set('value', '');
}
if (table.getStyle('background-color') !== 'transparent' && this.get('allowBackgroundColour')) {
var hexColour = Y.Color.toHex(table.getStyle('background-color'));
var matchedInput = backgroundcolours.filter('[value="' + hexColour + '"]');
if (table.getStyle('backgroundColor') && this.get('allowBackgroundColour')) {
hexColour = Y.Color.toHex(table.getStyle('backgroundColor'));
matchedInput = backgroundcolours.filter('[value="' + hexColour + '"]');
if (matchedInput) {
matchedInput.set("checked", true);
@@ -1207,11 +1245,7 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
borderConfiguration = this._getBorderConfiguration(table);
if (borderConfiguration) {
if (borderConfiguration.borderStyle && borderConfiguration.borderStyle === 'none') {
borderValue = 'none';
} else {
borderValue = 'outer';
}
borderValue = 'outer';
} else {
borderConfiguration = this._getBorderConfiguration(table.one('td'));
if (borderConfiguration) {
@@ -1226,8 +1260,8 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
bordersize.set('value', borderSize);
borderinput.set('value', borderValue);
var hexColour = borderConfiguration.borderColor;
var matchedInput = bordercolours.filter('[value="' + hexColour + '"]');
hexColour = borderConfiguration.borderColor;
matchedInput = bordercolours.filter('[value="' + hexColour + '"]');
if (matchedInput) {
matchedInput.set("checked", true);
@@ -1244,6 +1278,7 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
}
headersinput.set('value', headersvalue);
dialogue.set('bodyContent', node).show();
this._updateAvailableSettings();
},
+158 -123
View File
@@ -33,11 +33,11 @@
var COMPONENT = 'atto_table',
DEFAULT = {
BORDERSTYLE: 'solid',
BORDERSTYLE: 'inherit',
BORDERWIDTH: '1'
},
DIALOGUE = {
WIDTH: '450px'
WIDTH: '480px'
},
TEMPLATE = '' +
'<form class="{{CSS.FORM}}">' +
@@ -76,9 +76,8 @@ var COMPONENT = 'atto_table',
'<legend class="mdl-align">{{get_string "appearance" component}}</legend>' +
'{{#if allowBorders}}' +
'<label for="{{elementid}}_atto_table_borders" class="sameline">{{get_string "borders" component}}</label>' +
'<select class="{{CSS.BORDERS}}" id="{{elementid}}_atto_table_borders">' +
'<select name="borders" class="{{CSS.BORDERS}}" id="{{elementid}}_atto_table_borders">' +
'<option value="default">{{get_string "themedefault" component}}' + '</option>' +
'<option value="none">{{get_string "noborder" component}}' + '</option>' +
'<option value="outer">{{get_string "outer" component}}' + '</option>' +
'<option value="all">{{get_string "all" component}}' + '</option>' +
'</select>' +
@@ -86,9 +85,10 @@ var COMPONENT = 'atto_table',
'{{#if allowBorderStyles}}' +
'<label for="{{elementid}}_atto_table_borderstyle" class="sameline">' +
'{{get_string "borderstyles" component}}</label>' +
'<select class="{{CSS.BORDERSTYLE}}" id="{{elementid}}_atto_table_borderstyle">' +
'<select name="borderstyles" class="{{CSS.BORDERSTYLE}}" id="{{elementid}}_atto_table_borderstyle">' +
'<option value="inherit">{{get_string "inherit" component}}</option>' +
'{{#each borderStyles}}' +
'<option value="' + '{{this}}' + '">' + '{{this}}' + '</option>' +
'<option value="' + '{{this}}' + '">' + '{{get_string this ../component}}' + '</option>' +
'{{/each}}' +
'</select>' +
'<br>' +
@@ -96,7 +96,7 @@ var COMPONENT = 'atto_table',
'{{#if allowBorderSize}}' +
'<label for="{{elementid}}_atto_table_bordersize" class="sameline">' +
'{{get_string "bordersize" component}}</label>' +
'<input id="{{elementid}}_atto_table_bordersize" class="{{CSS.BORDERSIZE}}"' +
'<input name="bordersize" id="{{elementid}}_atto_table_bordersize" class="{{CSS.BORDERSIZE}}"' +
'type="number" value="1" size="8" min="1" max="50"/>' +
'<label style="display: inline-block;">{{CSS.BORDERSIZEUNIT}}</label>' +
'<br>' +
@@ -110,10 +110,10 @@ var COMPONENT = 'atto_table',
'style="background-color:transparent;color:transparent">' +
'<input id="{{../elementid}}_atto_table_bordercolour_-1"' +
'type="radio" name="borderColour" value="transparent" checked="checked"'+
'title="{{get_string "transparent" component}}"></input>' +
'type="radio" name="borderColour" value="none" checked="checked"'+
'title="{{get_string "themedefault" component}}"></input>' +
'{{get_string "transparent" component}}' +
'{{get_string "themedefault" component}}' +
'</label>' +
'{{#each availableColours}}' +
'<label for="{{../elementid}}_atto_table_bordercolour_{{@index}}"' +
@@ -138,10 +138,10 @@ var COMPONENT = 'atto_table',
'style="background-color:transparent;color:transparent">' +
'<input id="{{../elementid}}_atto_table_backgroundcolour_-1"' +
'type="radio" name="backgroundColour" value="transparent" checked="checked"'+
'title="{{get_string "transparent" component}}"></input>' +
'type="radio" name="backgroundColour" value="none" checked="checked"'+
'title="{{get_string "themedefault" component}}"></input>' +
'{{get_string "transparent" component}}' +
'{{get_string "themedefault" component}}' +
'</label>' +
'{{#each availableColours}}' +
@@ -160,7 +160,7 @@ var COMPONENT = 'atto_table',
'{{#if allowWidth}}' +
'<label for="{{elementid}}_atto_table_width" class="sameline">' +
'{{get_string "width" component}}</label>' +
'<input id="{{elementid}}_atto_table_width" class="{{CSS.WIDTH}}" size="8" type="number" min="0" max="100"/>' +
'<input name="width" id="{{elementid}}_atto_table_width" class="{{CSS.WIDTH}}" size="8" type="number" min="0" max="100"/>' +
'<label style="display: inline-block;">{{CSS.WIDTHUNIT}}</label>' +
'<br>' +
'{{/if}}' +
@@ -287,6 +287,8 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
// Set the dialogue content, and then show the dialogue.
dialogue.set('bodyContent', this._getDialogueContent(false))
.show();
this._updateAvailableSettings();
}
},
@@ -332,7 +334,7 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
*/
_getDialogueContent: function(edit) {
var template = Y.Handlebars.compile(TEMPLATE);
var availableColours = this.get('availableColors');
var allowBorders = this.get('allowBorders');
this._content = Y.Node.create(template({
CSS: CSS,
@@ -341,13 +343,13 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
edit: edit,
nonedit: !edit,
allowStyling: this.get('allowStyling'),
allowBorders: this.get('allowBorders'),
allowBorders: allowBorders,
allowBorderStyles: this.get('allowBorderStyles'),
borderStyles: this.get('borderStyles'),
allowBorderSize: this.get('allowBorderSize'),
allowBorderColour: this.get('allowBorderColour'),
allowBackgroundColour: this.get('allowBackgroundColour'),
availableColours: availableColours,
availableColours: this.get('availableColors'),
allowWidth: this.get('allowWidth')
}));
@@ -358,9 +360,48 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
this._content.one('.submit').on('click', this._setTable, this);
}
if (allowBorders) {
this._content.one('[name="borders"]').on('change', this._updateAvailableSettings, this);
}
return this._content;
},
/**
* Disables options within the dialogue if they shouldn't be available.
* E.g.
* If borders are set to "Theme default" then the border size, style and
* colour options are disabled.
*
* @method _updateAvailableSettings
* @private
*/
_updateAvailableSettings: function() {
var tableForm = this._content,
enableBorders = tableForm.one('[name="borders"]'),
borderStyle = tableForm.one('[name="borderstyles"]'),
borderSize = tableForm.one('[name="bordersize"]'),
borderColour = tableForm.all('[name="borderColour"]'),
disabledValue = 'removeAttribute';
if (enableBorders.get('value') === 'default') {
disabledValue = 'setAttribute';
}
if (borderStyle) {
borderStyle[disabledValue]('disabled');
}
if (borderSize) {
borderSize[disabledValue]('disabled');
}
if (borderColour) {
borderColour[disabledValue]('disabled');
}
},
/**
* Given the current selection, return a table cell suitable for table editing
* purposes, i.e. the first table cell selected, or the first cell in the table
@@ -434,10 +475,6 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
borderstyle,
bordercolour,
backgroundcolour,
backgroundcolourvalue = '#FFFFFF',
borderSizeValue = '1',
borderStyleValue = 'solid',
borderhex = '#FFFFFF',
table,
width,
captionnode;
@@ -459,51 +496,16 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
backgroundcolour = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.SELECTEDBACKGROUNDCOLOUR);
width = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.WIDTH);
if (bordercolour) {
borderhex = bordercolour.get('value');
}
if (bordersize) {
borderSizeValue = bordersize.get('value');
}
if (borderstyle) {
borderStyleValue = borderstyle.get('value');
}
if (backgroundcolour) {
backgroundcolourvalue = backgroundcolour.get('value');
}
table = this._lastTarget.ancestor('table');
// Clear the inline border styling
table.removeAttribute('style');
table.all('td, th').each(function() {
this.removeAttribute('style');
this._setAppearance(table, {
width: width,
borders: borders,
borderColour: bordercolour,
borderSize: bordersize,
borderStyle: borderstyle,
backgroundColour: backgroundcolour
});
if (borders) {
if (borders.get('value') === 'outer') {
table.setStyle('border', borderSizeValue + CSS.BORDERSIZEUNIT + ' ' +
borderStyleValue + ' ' + borderhex);
} else if (borders.get('value') === 'all') {
table.all('td, th').each(function() {
this.setStyle('border', borderSizeValue + CSS.BORDERSIZEUNIT + ' ' +
borderStyleValue + ' ' + borderhex);
});
} else if (borders.get('value') === 'none') {
table.setStyle('border', 'none');
}
}
if (width && width.get('value')) {
table.setStyle('width', width.get('value') + CSS.WIDTHUNIT);
}
if (backgroundcolourvalue !== '') {
table.setStyle('background-color', backgroundcolourvalue);
}
captionnode = table.one('caption');
if (!captionnode) {
captionnode = Y.Node.create('<caption></caption>');
@@ -587,14 +589,9 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
var caption,
captionposition,
borders,
bordertable = '',
bordercell = '',
bordersize,
borderstyle,
bordercolour,
borderSizeValue = '1',
borderStyleValue = 'solid',
borderhex = '#FFFFFF',
rows,
cols,
headers,
@@ -622,52 +619,13 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
headers = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.HEADERS);
width = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.WIDTH);
if (bordercolour) {
borderhex = bordercolour.get('value');
}
if (bordersize) {
borderSizeValue = bordersize.get('value');
}
if (borderstyle) {
borderStyleValue = borderstyle.get('value');
}
if (borders) {
if (borders.get('value') === 'outer') {
bordertable = ' style="border: ' + borderSizeValue + CSS.BORDERSIZEUNIT + ' ' +
borderStyleValue + ' ' + borderhex + ';"';
} else if (borders.get('value') === 'all') {
bordercell = ' style="border: ' + borderSizeValue + CSS.BORDERSIZEUNIT + ' ' +
borderStyleValue + ' ' + borderhex + ';"';
}
}
if (backgroundcolour) {
if (bordertable !== '') {
bordertable = bordertable.substring(0, bordertable.length-1) +
'background-color: ' + backgroundcolour.get('value') + ';"';
} else {
bordertable = ' style="background-color: ' + backgroundcolour.get('value') + ';"';
}
}
if (width && width.get('value')) {
if (bordertable !== '') {
bordertable = bordertable.substring(0, bordertable.length-1) + 'width: ' +
width.get('value') + CSS.WIDTHUNIT + ';"';
} else {
bordertable = ' style="width: ' + width.get('value') + CSS.WIDTHUNIT + ';"';
}
}
// Set the selection.
this.get('host').setSelection(this._currentSelection);
// Note there are some spaces inserted in the cells and before and after, so that users have somewhere to click.
var nl = "\n";
tablehtml = '<br/>' + nl + '<table' + bordertable + '>' + nl;
var tableId = Y.guid();
tablehtml = '<br/>' + nl + '<table id="' + tableId + '">' + nl;
var captionstyle = '';
if (captionposition.get('value')) {
@@ -679,7 +637,7 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
i = 1;
tablehtml += '<thead>' + nl + '<tr>' + nl;
for (j = 0; j < parseInt(cols.get('value'), 10); j++) {
tablehtml += '<th scope="col"' + bordercell + '></th>' + nl;
tablehtml += '<th scope="col"></th>' + nl;
}
tablehtml += '</tr>' + nl + '</thead>' + nl;
}
@@ -688,9 +646,9 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
tablehtml += '<tr>' + nl;
for (j = 0; j < parseInt(cols.get('value'), 10); j++) {
if (j === 0 && (headers.get('value') === 'rows' || headers.get('value') === 'both')) {
tablehtml += '<th scope="row"' + bordercell + '></th>' + nl;
tablehtml += '<th scope="row"></th>' + nl;
} else {
tablehtml += '<td ' + bordercell + '></td>' + nl;
tablehtml += '<td ></td>' + nl;
}
}
tablehtml += '</tr>' + nl;
@@ -700,6 +658,17 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
this.get('host').insertContentAtFocusPoint(tablehtml);
var tableNode = Y.one('#' + tableId);
this._setAppearance(tableNode, {
width: width,
borders: borders,
borderColour: bordercolour,
borderSize: bordersize,
borderStyle: borderstyle,
backgroundColour: backgroundcolour
});
tableNode.removeAttribute('id');
// Mark the content as updated.
this.markUpdated();
},
@@ -1146,6 +1115,73 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
return false;
},
/**
* Set the appropriate styles on the given table node according to
* the provided configuration.
*
* @method _setAppearance
* @param {Node} The table node to be modified.
* @param {Object} Configuration object (associative array) containing the form nodes for
* border styling.
* @private
*/
_setAppearance: function(tableNode, configuration) {
var borderhex,
borderSizeValue,
borderStyleValue,
backgroundcolourvalue;
if (configuration.borderColour) {
borderhex = configuration.borderColour.get('value');
}
if (configuration.borderSize) {
borderSizeValue = configuration.borderSize.get('value');
}
if (configuration.borderStyle) {
borderStyleValue = configuration.borderStyle.get('value');
}
if (configuration.backgroundColour) {
backgroundcolourvalue = configuration.backgroundColour.get('value');
}
// Clear the inline border styling
tableNode.removeAttribute('style');
tableNode.all('td, th').each(function(cell) {
cell.removeAttribute('style');
}, this);
if (configuration.borders) {
if (configuration.borders.get('value') === 'outer') {
tableNode.setStyle('borderWidth', borderSizeValue + CSS.BORDERSIZEUNIT);
tableNode.setStyle('borderStyle', borderStyleValue);
if (borderhex !== 'none') {
tableNode.setStyle('borderColor', borderhex);
}
} else if (configuration.borders.get('value') === 'all') {
tableNode.all('td, th').each(function(cell) {
cell.setStyle('borderWidth', borderSizeValue + CSS.BORDERSIZEUNIT);
cell.setStyle('borderStyle', borderStyleValue);
if (borderhex !== 'none') {
cell.setStyle('borderColor', borderhex);
}
}, this);
}
}
if (backgroundcolourvalue !== 'none') {
tableNode.setStyle('backgroundColor', backgroundcolourvalue);
}
if (configuration.width && configuration.width.get('value')) {
tableNode.setStyle('width', configuration.width.get('value') + CSS.WIDTHUNIT);
}
},
/**
* Edit table (show the dialogue).
*
@@ -1172,7 +1208,9 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
backgroundcolours = node.all(SELECTORS.BACKGROUNDCOLOURS),
width = node.one(SELECTORS.WIDTH),
table = this._lastTarget.ancestor('table'),
captionnode = table.one('caption');
captionnode = table.one('caption'),
hexColour,
matchedInput;
if (captionnode) {
captioninput.set('value', captionnode.getHTML());
@@ -1191,9 +1229,9 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
captionpositioninput.set('value', '');
}
if (table.getStyle('background-color') !== 'transparent' && this.get('allowBackgroundColour')) {
var hexColour = Y.Color.toHex(table.getStyle('background-color'));
var matchedInput = backgroundcolours.filter('[value="' + hexColour + '"]');
if (table.getStyle('backgroundColor') && this.get('allowBackgroundColour')) {
hexColour = Y.Color.toHex(table.getStyle('backgroundColor'));
matchedInput = backgroundcolours.filter('[value="' + hexColour + '"]');
if (matchedInput) {
matchedInput.set("checked", true);
@@ -1205,11 +1243,7 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
borderConfiguration = this._getBorderConfiguration(table);
if (borderConfiguration) {
if (borderConfiguration.borderStyle && borderConfiguration.borderStyle === 'none') {
borderValue = 'none';
} else {
borderValue = 'outer';
}
borderValue = 'outer';
} else {
borderConfiguration = this._getBorderConfiguration(table.one('td'));
if (borderConfiguration) {
@@ -1224,8 +1258,8 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
bordersize.set('value', borderSize);
borderinput.set('value', borderValue);
var hexColour = borderConfiguration.borderColor;
var matchedInput = bordercolours.filter('[value="' + hexColour + '"]');
hexColour = borderConfiguration.borderColor;
matchedInput = bordercolours.filter('[value="' + hexColour + '"]');
if (matchedInput) {
matchedInput.set("checked", true);
@@ -1242,6 +1276,7 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
}
headersinput.set('value', headersvalue);
dialogue.set('bodyContent', node).show();
this._updateAvailableSettings();
},
+67
View File
@@ -376,6 +376,73 @@ class core_files_zip_packer_testcase extends advanced_testcase implements file_p
unlink($archive);
}
public function test_close_archive() {
global $CFG;
$this->resetAfterTest(true);
$archive = "$CFG->tempdir/archive.zip";
$textfile = "$CFG->tempdir/textfile.txt";
touch($textfile);
$this->assertFileNotExists($archive);
$this->assertFileExists($textfile);
// Create archive and close it without files.
// (returns true, without any warning).
$zip_archive = new zip_archive();
$result = $zip_archive->open($archive, file_archive::CREATE);
$this->assertTrue($result);
$result = $zip_archive->close();
$this->assertTrue($result);
unlink($archive);
// Create archive and close it with files.
// (returns true, without any warning).
$zip_archive = new zip_archive();
$result = $zip_archive->open($archive, file_archive::CREATE);
$this->assertTrue($result);
$result = $zip_archive->add_file_from_string('test.txt', 'test');
$this->assertTrue($result);
$result = $zip_archive->add_file_from_pathname('test2.txt', $textfile);
$result = $zip_archive->close();
$this->assertTrue($result);
unlink($archive);
// Create archive and close if forcing error.
// (returns true for old PHP versions and
// false with warnings for new PHP versions). MDL-51863.
$zip_archive = new zip_archive();
$result = $zip_archive->open($archive, file_archive::CREATE);
$this->assertTrue($result);
$result = $zip_archive->add_file_from_string('test.txt', 'test');
$this->assertTrue($result);
$result = $zip_archive->add_file_from_pathname('test2.txt', $textfile);
$this->assertTrue($result);
// Delete the file before closing does force close() to fail.
unlink($textfile);
// Behavior is different between old PHP versions and new ones. Let's detect it.
$result = false;
try {
// Old PHP versions were not printing any warning.
$result = $zip_archive->close();
} catch (Exception $e) {
// New PHP versions print PHP Warning.
$this->assertInstanceOf('PHPUnit_Framework_Error_Warning', $e);
$this->assertContains('ZipArchive::close', $e->getMessage());
}
// This is crazy, but it shows how some PHP versions do return true.
try {
// And some PHP versions do return correctly false (5.4.25, 5.6.14...)
$this->assertFalse($result);
} catch (Exception $e) {
// But others do insist into returning true (5.6.13...). Only can accept them.
$this->assertInstanceOf('PHPUnit_Framework_ExpectationFailedException', $e);
$this->assertTrue($result);
}
$this->assertFileNotExists($archive);
}
/**
* @depends test_add_files
*/
+2 -2
View File
@@ -191,7 +191,7 @@ class zip_archive extends file_archive {
}
if ($this->emptyziphack) {
$this->za->close();
@$this->za->close();
$this->za = null;
$this->mode = null;
$this->namelookup = null;
@@ -202,7 +202,7 @@ class zip_archive extends file_archive {
} else if ($this->za->numFiles == 0) {
// PHP can not create empty archives, so let's fake it.
$this->za->close();
@$this->za->close();
$this->za = null;
$this->mode = null;
$this->namelookup = null;
Binary file not shown.
File diff suppressed because one or more lines are too long
+32
View File
@@ -0,0 +1,32 @@
<?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/>.
/**
* Flowplayer handling file.
*
* @package core
* @copyright Petr Skoda <petr.skoda@totaralms.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define('NO_DEBUG_DISPLAY', true);
define('NO_MOODLE_COOKIES', true);
define('NO_UPGRADE_CHECK', true);
require('../../config.php');
require('../../lib/flowplayer/lib.php');
flowplayer_send_flash_content('flowplayer-3.2.18.swf');
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -0,0 +1,32 @@
<?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/>.
/**
* Flowplayer audio swf handling.
*
* @package core
* @copyright Petr Skoda <petr.skoda@totaralms.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define('NO_DEBUG_DISPLAY', true);
define('NO_MOODLE_COOKIES', true);
define('NO_UPGRADE_CHECK', true);
require('../../config.php');
require('../../lib/flowplayer/lib.php');
flowplayer_send_flash_content('flowplayer.audio-3.2.11.swf');
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -0,0 +1,32 @@
<?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/>.
/**
* Flowplayer audio controls handling.
*
* @package core
* @copyright Petr Skoda <petr.skoda@totaralms.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define('NO_DEBUG_DISPLAY', true);
define('NO_MOODLE_COOKIES', true);
define('NO_UPGRADE_CHECK', true);
require('../../config.php');
require('../../lib/flowplayer/lib.php');
flowplayer_send_flash_content('flowplayer.controls-3.2.16.swf');
+78
View File
@@ -0,0 +1,78 @@
<?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/>.
/**
* Flowplayer library.
*
* @package core
* @copyright Petr Skoda <petr.skoda@totaralms.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Safer flash serving code.
*
* @param string $filename
* @return void does not return, ends with die()
*/
function flowplayer_send_flash_content($filename) {
global $CFG;
// Note: Do not use any fancy APIs here, this must work in all supported versions.
// No url params.
if (!empty($_GET) or !empty($_POST) or !empty($_REQUEST)) {
header("HTTP/1.1 404 Not Found");
die;
}
// Our referrers only, nobody else should embed these scripts.
if (!empty($_SERVER['HTTP_REFERER'])) {
$refhost = parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST);
$host = parse_url($CFG->wwwroot . '/', PHP_URL_HOST);
if ($refhost and $host and strtolower($refhost) !== strtolower($host)) {
header("HTTP/1.1 404 Not Found");
die;
}
}
// Fetch and decode the original content.
$content = file_get_contents($CFG->dirroot . '/lib/flowplayer/' . $filename . '.bin');
if (!$content) {
header("HTTP/1.1 404 Not Found");
die;
}
$content = base64_decode($content);
// No caching allowed.
if (strpos($CFG->wwwroot, 'https://') === 0) {
// HTTPS sites - watch out for IE! KB812935 and KB316431.
header('Cache-Control: private, max-age=10, no-transform');
header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
header('Pragma: ');
} else {
header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0, no-transform');
header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
header('Pragma: no-cache');
}
// Send the original binary code.
header('Content-Type: application/x-shockwave-flash');
echo $content;
die;
}
+1
View File
@@ -1,6 +1,7 @@
Description of FlowPlayer import
Our changes:
* the handling of flash content now it's done by PHP, checking url parameter to avoid security issues.
* we do not load the flow player if flash not present - this is much better
for accessibility and flash incompatible devices - they may play mp3 or flv directly
* no splashscreens
+15 -2
View File
@@ -46,6 +46,10 @@ class MoodleQuickForm_autocomplete extends MoodleQuickForm_select {
protected $ajax = '';
/** @var string $placeholder Placeholder text for an empty list. */
protected $placeholder = '';
/** @var bool $casesensitive Whether the search has to be case-sensitive. */
protected $casesensitive = false;
/** @var bool $showsuggestions Show suggestions by default - but this can be turned off. */
protected $showsuggestions = true;
/**
* constructor
@@ -54,7 +58,7 @@ class MoodleQuickForm_autocomplete extends MoodleQuickForm_select {
* @param mixed $elementLabel Label(s) for the select
* @param mixed $options Data to be used to populate options
* @param mixed $attributes Either a typical HTML attribute string or an associative array. Special options
* "tags", "placeholder", "ajax", "multiple" are supported.
* "tags", "placeholder", "ajax", "multiple", "casesensitive" are supported.
*/
function MoodleQuickForm_autocomplete($elementName=null, $elementLabel=null, $options=null, $attributes=null) {
// Even if the constructor gets called twice we do not really want 2x options (crazy forms!).
@@ -66,6 +70,10 @@ class MoodleQuickForm_autocomplete extends MoodleQuickForm_select {
$this->tags = $attributes['tags'];
unset($attributes['tags']);
}
if (isset($attributes['showsuggestions'])) {
$this->showsuggestions = $attributes['showsuggestions'];
unset($attributes['showsuggestions']);
}
$this->placeholder = get_string('search');
if (isset($attributes['placeholder'])) {
$this->placeholder = $attributes['placeholder'];
@@ -75,6 +83,10 @@ class MoodleQuickForm_autocomplete extends MoodleQuickForm_select {
$this->ajax = $attributes['ajax'];
unset($attributes['ajax']);
}
if (isset($attributes['casesensitive'])) {
$this->casesensitive = $attributes['casesensitive'] ? true : false;
unset($attributes['casesensitive']);
}
parent::HTML_QuickForm_select($elementName, $elementLabel, $options, $attributes);
$this->_type = 'autocomplete';
@@ -91,7 +103,8 @@ class MoodleQuickForm_autocomplete extends MoodleQuickForm_select {
// Enhance the select with javascript.
$this->_generateId();
$id = $this->getAttribute('id');
$PAGE->requires->js_call_amd('core/form-autocomplete', 'enhance', $params = array('#' . $id, $this->tags, $this->ajax, $this->placeholder));
$PAGE->requires->js_call_amd('core/form-autocomplete', 'enhance', $params = array('#' . $id, $this->tags, $this->ajax,
$this->placeholder, $this->casesensitive, $this->showsuggestions));
return parent::toHTML();
}
+5 -1
View File
@@ -960,8 +960,12 @@ M.form_filemanager.init = function(Y, options) {
for (var i in attrs) {
if (selectnode.one('.fp-'+attrs[i])) {
var value = (node[attrs[i]+'_f']) ? node[attrs[i]+'_f'] : (node[attrs[i]] ? node[attrs[i]] : '');
// Escape if the attribute being evaluated is not for the list of reference files.
if (attrs[i] !== 'reflist') {
value = Y.Escape.html(value);
}
selectnode.one('.fp-'+attrs[i]).addClassIf('fp-unknown', ''+value == '')
.one('.fp-value').setContent(Y.Escape.html(value));
.one('.fp-value').setContent(value);
}
}
// display thumbnail
+1
View File
@@ -90,6 +90,7 @@ class MoodleQuickForm_tags extends MoodleQuickForm_autocomplete {
}
$attributes['multiple'] = 'multiple';
$attributes['placeholder'] = get_string('entertags', 'tag');
$attributes['showsuggestions'] = $this->showingofficial;
parent::MoodleQuickForm_autocomplete($elementName, $elementLabel, $validoptions, $attributes);
}
+6 -4
View File
@@ -1699,6 +1699,7 @@ M.util.load_flowplayer = function() {
loaded = true;
var controls = {
url: M.cfg.wwwroot + '/lib/flowplayer/flowplayer.controls-3.2.16.swf.php',
autoHide: true
}
/* TODO: add CSS color overrides for the flv flow player */
@@ -1706,9 +1707,9 @@ M.util.load_flowplayer = function() {
for(var i=0; i<M.util.video_players.length; i++) {
var video = M.util.video_players[i];
if (video.width > 0 && video.height > 0) {
var src = {src: M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.18.swf', width: video.width, height: video.height};
var src = {src: M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.18.swf.php', width: video.width, height: video.height};
} else {
var src = M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.18.swf';
var src = M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.18.swf.php';
}
flowplayer(video.id, src, {
plugins: {controls: controls},
@@ -1744,6 +1745,7 @@ M.util.load_flowplayer = function() {
return;
}
var controls = {
url: M.cfg.wwwroot + '/lib/flowplayer/flowplayer.controls-3.2.16.swf.php',
autoHide: false,
fullscreen: false,
next: false,
@@ -1808,8 +1810,8 @@ M.util.load_flowplayer = function() {
controls.height = 25;
controls.time = true;
}
flowplayer(audio.id, M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.18.swf', {
plugins: {controls: controls, audio: {url: M.cfg.wwwroot + '/lib/flowplayer/flowplayer.audio-3.2.11.swf'}},
flowplayer(audio.id, M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.18.swf.php', {
plugins: {controls: controls, audio: {url: M.cfg.wwwroot + '/lib/flowplayer/flowplayer.audio-3.2.11.swf.php'}},
clip: {url: audio.fileurl, provider: "audio", autoPlay: false}
});
}
+1 -1
View File
@@ -2835,7 +2835,7 @@ EOD;
* @return string HTML fragment.
*/
public function notify_message($message) {
$n = new notification($message, notification::NOTIFY_MESSAGE);
$n = new \core\output\notification($message, \core\output\notification::NOTIFY_MESSAGE);
return $this->render($n);
}
+2
View File
@@ -556,6 +556,8 @@ class page_requirements_manager {
$path = realpath("$componentdir/jquery/$file");
if (strpos($path, $CFG->dirroot) === 0) {
$url = $CFG->httpswwwroot.preg_replace('/^'.preg_quote($CFG->dirroot, '/').'/', '', $path);
// Replace all occurences of backslashes characters in url to forward slashes.
$url = str_replace('\\', '/', $url);
$url = new moodle_url($url);
} else {
// Bad luck, fix your server!
+43 -27
View File
@@ -458,7 +458,9 @@ class flexible_table {
} else if (isset($SESSION->flextable[$this->uniqueid])) {
$this->prefs = $SESSION->flextable[$this->uniqueid];
}
if (!$this->prefs) {
// Set up default preferences if needed.
if (!$this->prefs or optional_param($this->request[TABLE_VAR_RESET], false, PARAM_BOOL)) {
$this->prefs = array(
'collapse' => array(),
'sortby' => array(),
@@ -526,17 +528,6 @@ class flexible_table {
$this->prefs['i_first'] = $ifirst;
}
// Allow user to reset table preferences.
if (optional_param($this->request[TABLE_VAR_RESET], 0, PARAM_BOOL) === 1) {
$this->prefs = array(
'collapse' => array(),
'sortby' => array(),
'i_first' => '',
'i_last' => '',
'textsort' => $this->column_textsort,
);
}
// Save user preferences if they have changed.
if ($this->prefs != $oldprefs) {
if ($this->persistent) {
@@ -1416,32 +1407,57 @@ class flexible_table {
/**
* Generate the HTML for the table preferences reset button.
*
* @return string HTML fragment.
* @return string HTML fragment, empty string if no need to reset
*/
private function render_reset_button() {
$userprefs = false;
// Loop through the user table preferences for a setting.
foreach ($this->prefs as $preference) {
if (!empty($preference)) {
// We have a preference.
$userprefs = true;
// We only need one.
break;
}
}
// If no table preferences have been set then don't show the reset button.
if (!$userprefs) {
protected function render_reset_button() {
if (!$this->can_be_reset()) {
return '';
}
$url = $this->baseurl->out(false, array($this->request[TABLE_VAR_RESET] => 1));
$html = html_writer::start_div('mdl-right');
$html = html_writer::start_div('resettable mdl-right');
$html .= html_writer::link($url, get_string('resettable'));
$html .= html_writer::end_div();
return $html;
}
/**
* Are there some table preferences that can be reset?
*
* If true, then the "reset table preferences" widget should be displayed.
*
* @return bool
*/
protected function can_be_reset() {
// Loop through preferences and make sure they are empty or set to the default value.
foreach ($this->prefs as $prefname => $prefval) {
if ($prefname === 'sortby' and !empty($this->sort_default_column)) {
// Check if the actual sorting differs from the default one.
if (empty($prefval) or $prefval !== array($this->sort_default_column => $this->sort_default_order)) {
return true;
}
} else if ($prefname === 'collapse' and !empty($prefval)) {
// Check if there are some collapsed columns (all are expanded by default).
foreach ($prefval as $columnname => $iscollapsed) {
if ($iscollapsed) {
return true;
}
}
} else if (!empty($prefval)) {
// For all other cases, we just check if some preference is set.
return true;
}
}
return false;
}
}

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