Compare commits

..

269 Commits

Author SHA1 Message Date
Jun Pataleta b2d0712748 Moodle release 4.1.3 2023-04-22 16:22:40 +08:00
Eloy Lafuente (stronk7) f70a6d32cd weekly release 4.1.2+ 2023-04-21 19:19:12 +02:00
Eloy Lafuente (stronk7) f691ccf8c6 Merge branch 'install_401_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_401_STABLE 2023-04-21 19:19:07 +02:00
Jun Pataleta ceb2f856b0 Merge branch 'MDL-77927-401' of https://github.com/stevandoMoodle/moodle into MOODLE_401_STABLE 2023-04-21 16:52:21 +08:00
Jun Pataleta 955d89710e Merge branch 'MDL-77229-patch-401' of https://github.com/ilyatregubov/moodle into MOODLE_401_STABLE 2023-04-21 16:44:38 +08:00
Jun Pataleta 526e1af88b MDL-77927 core: mod_assignment subplugins environment check 2023-04-21 15:53:32 +08:00
Ilya Tregubov b142840de1 MDL-77229 mod_lesson: Black list detailed statistics in classic Behat.
The nav element to go to detailed stats page is missing in classic
2023-04-21 15:07:36 +08:00
Ilya Tregubov 8fb9177b74 Merge branch 'MDL-77229-401' of https://github.com/ilyatregubov/moodle into MOODLE_401_STABLE 2023-04-21 12:11:40 +08:00
Ilya Tregubov 6e15a26dce MDL-77229 lesson: Add Behat test 2023-04-21 12:04:17 +08:00
Jun Pataleta b5a65e56fd Merge branch 'MDL-77896-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2023-04-21 11:10:39 +08:00
AMOS bot a6a0e98e53 Automatically generated installer lang files 2023-04-21 00:07:41 +00:00
Paul Holden c28cd3215b MDL-77896 editor_tiny: approximate height for non-visible editors.
When an editor is renderer initially invisible to the browser, e.g.
the forum "Add discussion" form, it has a `clientHeight` value of
zero. We can approximate an alternative value based on the number
of rows in the textarea.

Co-authored-by: Andrew Nicols <andrew@nicols.co.uk>
2023-04-20 15:24:34 +01:00
Juan Leyva 39084a098b MDL-77229 lesson: Fix error for empty responses (numerical pagetype) 2023-04-20 12:46:42 +02:00
Jun Pataleta 9290627da9 Merge branch 'MDL-73012-401' of https://github.com/ferranrecio/moodle into MOODLE_401_STABLE 2023-04-20 16:29:14 +08:00
Ilya Tregubov bb5bd2eed8 Merge branch 'MDL-77436-401' of https://github.com/meirzamoodle/moodle into MOODLE_401_STABLE 2023-04-20 16:23:19 +08:00
Jun Pataleta 12195684b4 Merge branch 'MDL-77922-401' of https://github.com/lameze/moodle into MOODLE_401_STABLE 2023-04-20 16:10:10 +08:00
Ferran Recio f01ad40145 MDL-73012 core_courseformat: add pending to move section modal 2023-04-20 09:56:10 +02:00
Ferran Recio 1d64897a3c MDL-77922 core_courseformat: add pending to move activity modal 2023-04-20 14:42:30 +08:00
Ilya Tregubov 339dfec420 Merge branch 'MDL-77577-401' of https://github.com/andelacruz/moodle into MOODLE_401_STABLE 2023-04-20 12:33:38 +08:00
Andrew Nicols 1d16c049f3 MDL-77718 editor_tiny: Restrict the revision to int for loaders
The revision should always be an int. I suspect this was missed during
debugging and not corrected.
2023-04-19 13:20:25 +00:00
Paul Holden 4d4635228d MDL-77187 mod_wiki: validate external method sort parameters. 2023-04-19 13:20:25 +00:00
Paul Holden ce9cb1156c Merge branch 'MDL-77897-401' of https://github.com/andrewnicols/moodle into MOODLE_401_STABLE 2023-04-19 14:13:04 +01:00
Paul Holden c0429c1c90 Merge branch 'MDL-77960-401' of https://github.com/lameze/moodle into MOODLE_401_STABLE 2023-04-19 13:41:32 +01:00
Paul Holden be8c02fc2c Merge branch 'MDL-77944-401' of https://github.com/andrewnicols/moodle into MOODLE_401_STABLE 2023-04-19 13:06:41 +01:00
Paul Holden 99b5e1059d Merge branch 'MDL-77898-401' of https://github.com/junpataleta/moodle into MOODLE_401_STABLE 2023-04-19 09:03:22 +01:00
Andrew Nicols ee519b690c MDL-77897 editor_tiny: Save editor content on editor blur 2023-04-19 14:57:26 +08:00
Ilya Tregubov 814c63fcc3 git fetch https://github.com/paulholden/moodle.git MDL-77935 && git merge --no-ff FETCH_HEADMerge branch 'MDL-77935-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2023-04-19 13:32:36 +08:00
Jun Pataleta 799e962f1f Merge branch 'MDL-77953_401' of https://github.com/stronk7/moodle into MOODLE_401_STABLE 2023-04-19 12:37:45 +08:00
Simey Lameze c0bea2a82d MDL-77960 behat: make verification steps more specific 2023-04-19 11:57:37 +08:00
Eloy Lafuente (stronk7) e9fcdec98b MDL-77953 pagelib: Ensure that null $SCRIPT continues behaving the same
It's possible to have some Moodle components soft linked instead
of being real directories within codebase (within dirroot).

For example, Composer's "vendor" directory can be soft linked
(from elsewhere), or also plugins can be installed using soft
links.

In those cases, Moodle calculates the $SCRIPT global as null. And,
then, string operations on it are emitting a PHP deprecation message
with PHP 8.1 and up.

This fix just ensures that the behaviour is the same than before
PHP 8.1, aka: ltrim(null) = '' (empty string), without any PHP warning.
2023-04-18 18:50:37 +02:00
Andrew Nicols fc6764324a Merge branch 'MDL-77895-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2023-04-18 23:35:19 +08:00
Andrew Nicols b71f01981a weekly release 4.1.2+ 2023-04-18 22:28:09 +08:00
Andrew Nicols 5d1fcb942a Merge branch 'install_401_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_401_STABLE 2023-04-18 22:28:06 +08:00
Jun Pataleta 24335d67bb MDL-77898 calendar: Add iconclass for upcoming_mini template
The icon's iconclass context data adds additional CSS class(es) to
calendar event icons to better control how the event icon is displayed.
e.g. without filtering for activity events that don't hae monologo
versions of their icons.
2023-04-18 15:58:46 +08:00
Paul Holden 1b828701a6 MDL-77895 editor_tiny: standardize quickbar selection toolbar.
Ensure the same heading tags are available as those defined in the
editor block formats configuration (c51b7e2c).

Co-authored-by: Andrew Nicols <andrew@nicols.co.uk>
2023-04-18 07:45:23 +01:00
Ilya Tregubov f8e468c41a Merge branch 'MDL-77916-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2023-04-18 14:28:44 +08:00
Andrew Nicols b07df211ce Merge branch 'MDL-77829-401' of https://github.com/stevandoMoodle/moodle into MOODLE_401_STABLE 2023-04-18 12:31:46 +08:00
Andrew Nicols 4c289966d3 Merge branch 'MDL-77735-401' of https://github.com/sarjona/moodle into MOODLE_401_STABLE 2023-04-18 12:10:56 +08:00
Ilya Tregubov 0d0fbec616 Merge branch 'MDL-77770-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2023-04-18 12:01:46 +08:00
Ilya Tregubov ca751bdc1f Merge branch 'MDL-76855-401' of https://github.com/Chocolate-lightning/moodle into MOODLE_401_STABLE 2023-04-18 11:18:32 +08:00
Andrew Nicols bc85007834 MDL-77944 behat: Rename chrome options for w3c support
From Selenium 4.8.0, support for non-w3c browser control has ended.

We only use W3C browser control these days, and this was missed as part
of the move to W3C. All browser options must be vendor-prefixed.
2023-04-18 09:04:08 +08:00
Ilya Tregubov dd553c1406 Merge branch 'MDL-77827-401' of https://github.com/HuongNV13/moodle into MOODLE_401_STABLE 2023-04-18 08:59:30 +08:00
AMOS bot 76ab8a1c51 Automatically generated installer lang files 2023-04-18 00:07:38 +00:00
Paul Holden 242ecbf7f3 Merge branch 'MDL-77878-401-enfix' of https://github.com/vmdef/moodle into MOODLE_401_STABLE 2023-04-17 19:12:36 +01:00
Shamim Rezaie cbfde8c11a Merge branch 'MDL-76998-401' of https://github.com/aanabit/moodle into MOODLE_401_STABLE 2023-04-17 21:27:56 +10:00
Paul Holden df4f8dd322 Merge branch 'MDL-76212-401-nav' of https://github.com/kevpercy/moodle into MOODLE_401_STABLE 2023-04-17 11:50:54 +01:00
Paul Holden 60ab09ac87 Merge branch 'MDL-77324-401-2' of https://github.com/ilyatregubov/moodle into MOODLE_401_STABLE 2023-04-17 11:06:14 +01:00
Paul Holden 40fad4d6b7 MDL-77935 contentbank: avoid phpunit crashes during isolated tests.
Simplify the test for allowed contexts by removing problematic use of
data provider annotation.

See: https://github.com/sebastianbergmann/phpunit/issues/2739
2023-04-17 09:28:38 +01:00
Víctor Déniz 1265b0ad84 MDL-77878 lang: Use fixed strings in tests 2023-04-17 09:26:52 +01:00
Mathew May faa121ad30 MDL-76855 gradereport_user: Prevent parent access errors 2023-04-17 11:26:03 +08:00
Andrew Nicols 797cbcc3da Merge branch 'MDL-76995-401' of https://github.com/aanabit/moodle into MOODLE_401_STABLE 2023-04-17 11:17:04 +08:00
Ilya Tregubov 6bac910434 MDL-76994 core_course: Fix version for weeks and topics course formats 2023-04-17 11:04:37 +08:00
Andrew Nicols a9b4b297ac Merge branch 'MDL-77606' of https://github.com/skodak/moodle into MOODLE_401_STABLE 2023-04-17 10:34:55 +08:00
Petr Skoda 073deca0e1 MDL-77606 grunt: fix Windows compatibility
Backport of MDL-77748.
2023-04-17 10:34:09 +08:00
Petr Skoda 2cc1182b16 MDL-77606 core: switch to Dart sass
Backport of MDL-73144.
2023-04-17 10:33:50 +08:00
Ilya Tregubov 82a7b26648 Merge branch 'MDL-76994-401' of https://github.com/aanabit/moodle into MOODLE_401_STABLE 2023-04-17 09:47:04 +08:00
Helen Foster 2db1ddd290 MDL-77878 lang: Import fixed English strings (en_fix) 2023-04-16 20:54:48 +01:00
Sara Arjona 96f0478ec1 Merge branch 'MDL-77913-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2023-04-14 14:02:18 +02:00
Sara Arjona 38f5c818cb MDL-77735 core: Check $CFG->lang isset
In some cases, $CFG->lang might not be set, and this is causing a
Notice to be displayed when, for instance, database connection fails.
This patch should fix this case.
2023-04-14 13:46:51 +02:00
Sara Arjona 38c35247e9 weekly release 4.1.2+ 2023-04-14 10:50:08 +02:00
Sara Arjona 242dcfbc55 Merge branch 'install_401_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_401_STABLE 2023-04-14 10:50:03 +02:00
Huong Nguyen 2f601f1e51 MDL-77827 events: Changed JSON comparison to be less strict 2023-04-14 12:29:32 +07:00
Paul Holden 31a3c8cda4 MDL-77916 h5p: register autoloader in helper testcase. 2023-04-13 23:14:57 +01:00
Paul Holden 39cf556111 MDL-77913 qbank_previewquestion: deterministic ordering of versions.
Ensure the ordering of loaded question versions is consistent, avoids
random Oracle failures.
2023-04-13 21:45:28 +01:00
Sara Arjona 4c4be40c77 Merge branch 'MDL-76986-401' of https://github.com/davewoloszyn/moodle into MOODLE_401_STABLE 2023-04-13 17:32:03 +02:00
Amaia Anabitarte c8f105800c MDL-76995 core_courseindex: Apply indentation in the course index 2023-04-13 16:59:05 +02:00
Amaia Anabitarte 3502ad3da7 MDL-76998 admin: Option to reset course indentation 2023-04-13 16:52:19 +02:00
Amaia Anabitarte 15a5a938b6 MDL-76994 tool_mobile: Return course format indentation setting 2023-04-13 16:51:05 +02:00
Amaia Anabitarte ec4eacd1da MDL-76994 core_course: New course format setting to enable indentation 2023-04-13 16:50:58 +02:00
Sara Arjona bc34e3fef3 Merge branch 'MDL-76859-401' of https://github.com/sarjona/moodle into MOODLE_401_STABLE 2023-04-13 16:24:28 +02:00
Sara Arjona 6aef92c24a Merge branch 'MDL77833-course-content-chng-notificatn-multilang' of https://github.com/Amrita1991/moodle into MOODLE_401_STABLE 2023-04-13 16:20:06 +02:00
Sara Arjona 4b7c58febf Merge branch 'MDL-77860-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2023-04-13 13:45:00 +02:00
Sara Arjona 4ed6c8fbce Merge branch 'MDL-77788-401' of https://github.com/rmady/moodle into MOODLE_401_STABLE 2023-04-13 13:17:28 +02:00
Sara Arjona af585fc755 Merge branch 'MDL-77618-admin-password-autocomplete-MOODLE_401_STABLE' of https://github.com/brendanheywood/moodle into MOODLE_401_STABLE 2023-04-13 12:19:16 +02:00
Amrita Deb Dutta 6ae5ee18a0 MDL-77833 course: content change notification multilang processing
adding context to format string

content change notificatn coursename multilang processing

change context param
2023-04-13 11:28:41 +02:00
Sara Arjona ead7958219 Merge branch 'MDL-77256-401' of https://github.com/rbravod/moodle into MOODLE_401_STABLE 2023-04-13 11:20:32 +02:00
Ilya Tregubov 8dc9cd6cdc MDL-77324 gradereport_singleview: Make action menus consistent 2023-04-13 16:59:23 +08:00
Sara Arjona 69749509ab Merge branch 'MDL-77856-401' of https://github.com/junpataleta/moodle into MOODLE_401_STABLE 2023-04-13 09:43:05 +02:00
Sara Arjona 59eac981eb Merge branch 'MDL-77468-401' of https://github.com/rmady/moodle into MOODLE_401_STABLE 2023-04-13 09:19:58 +02:00
David Woloszyn ab0038da84 MDL-76986 editor_tiny: Convert language code format for getting strings 2023-04-13 16:05:14 +10:00
Juan Leyva 2cde957897 MDL-77468 user: Fix invalid check for group belonging 2023-04-12 22:13:22 -03:00
Sara Arjona a8eb481980 Merge branch 'MDL-73610_401' of https://github.com/stronk7/moodle into MOODLE_401_STABLE 2023-04-12 18:19:40 +02:00
Juan Leyva 4c533b8204 MDL-77468 user: Make user profile visibility consistent web and ws 2023-04-12 09:15:47 -03:00
Sara Arjona 26aaa7486a Merge branch 'MDL-77012-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2023-04-12 14:09:33 +02:00
Petr Skoda f336875b1a MDL-76859 h5p: Fix behat failures
- Only resize if the H5P EmbedCommunicator is defined (otherwise, it was causing a
JS error)
- An unnecessary image has been removed from the greeting-card.h5p fixture package.
That way, the text will always be displayed (even if the iframe is still not
resized). Instead of replacing the original greeting-card-887.h5p file, I've
renamed it to greeting-card.h5p, to remove these ugly and unnecessary numbers
at the end of the file name).
2023-04-12 12:27:06 +02:00
Sara Arjona 6381daf52a Merge branch 'MDL-76993-401' of https://github.com/aanabit/moodle into MOODLE_401_STABLE 2023-04-12 09:54:22 +02:00
Kevin Percy 5b8ad6ae30 MDL-76212 gradebook_nav: Fixed tertiary nav for smaller screens 2023-04-12 14:12:50 +08:00
Amaia Anabitarte 3a38791d62 MDL-76993 core_course: Recover move right/left functionality
This is a backport of MDL-76990
2023-04-11 13:28:00 +02:00
Sara Arjona 5563e99f2c Merge branch 'MDL-77837-401' of https://github.com/andrewnicols/moodle into MOODLE_401_STABLE 2023-04-11 12:12:49 +02:00
Sara Arjona 8bc1c231d6 Merge branch 'MDL-77552-401' of https://github.com/ferranrecio/moodle into MOODLE_401_STABLE 2023-04-11 11:13:41 +02:00
Sara Arjona 1226208a64 Merge branch 'MDL-77148_401' of https://github.com/AnupamaSarjoshi/moodle into MOODLE_401_STABLE 2023-04-11 10:17:08 +02:00
Sara Arjona 725277eb8b Merge branch 'MDL-77612-401' of https://github.com/laurentdavid/moodle into MOODLE_401_STABLE 2023-04-11 10:04:41 +02:00
Laurent David b631966a70 MDL-77612 mod_label: Label name fix
* The @@PLUGINFILE@@ placeholder or URLs should not be displayed in the
course index for labels
2023-04-11 05:24:39 +02:00
Stevani Andolo b796cbd03c MDL-77829 core: Added environment check for mod_assignment
Decided to add an environment check before uninstalling the
mod_assignment plugin to prevent data lost.
2023-04-10 13:54:00 +08:00
AMOS bot 1066f5fe68 Automatically generated installer lang files 2023-04-07 00:07:35 +00:00
Andrew Nicols 202718f968 MDL-77837 core: Improve usage docs for cron_setup_user 2023-04-06 23:21:29 +08:00
Andrew Nicols 44d734147a MDL-77837 phpunit: Ensure that the cron user setter is used
When running an adhoc task in a unit test we should use the cron variant
of the set user method to mimic the behaviour of a real cron run.
2023-04-06 23:21:29 +08:00
Andrew Nicols 346cb39cff MDL-77837 cron: Ensure user is set when running tasks
We should be proactive in ensuring that the environment is clean when
running a task. We already ensure that we have a clean renderer and
other parts of the output chain, but we were not setting a clean user.

This change adds a call to setup the cron user before each task is
actually executed.
2023-04-06 23:21:29 +08:00
Paul Holden eb12428324 MDL-77770 gradereport_user: fix errors when no users to navigate. 2023-04-06 14:41:03 +01:00
Anupama Sarjoshi 8b31922739 MDL-77148 core: Fix to export params for templates in correct format
When questions are filtered by tags in the question bank, the qtagids
params are passed in the array format. Though moodle_url handles this,
single_button::export_for_template cannot. Hence changes done in
weblib.php to provide params for export_for_template in the
suitable format.
Thanks Huong. I have added the Behat test you provided in the patch.
2023-04-06 14:38:29 +01:00
Eloy Lafuente (stronk7) 127174088a MDL-73610 nodejs: Small updates to required packages
The main goal of this issue is to avoid scanners (Dependabot
and friends), reporting about security issues with the current
xmldom 0.6.0 package.

Note that this doesn't affect prod at all, because it's a dev
dependency, hardly exploitable. So it's not a security fix, just
a security_benefit, if something.

So here, we are updating from xmldom 0.6.0 to @xmldom/xmldom 0.8.7
(note that the package was renamed in 0.7.0, so it's the very same)

Also, when proceeding with the changes, it was detected that we
are incorrectly declaring @babel/eslint-parser as a normal dependency
instead of a development one, so we are also fixing that little detail.

The commands executed to get the changes above applied have been:

- nvm use
- npm install @xmldom/xmldom@^0.8.7 --save-dev
- npm uninstall xmldom
- npm install @babel/eslint-parser@^7.17.0 --save-dev

(we haven't run a complete re-install because we only want to modify
the minimum possible at this stage).
2023-04-06 14:59:43 +02:00
Sara Arjona f8e2445513 weekly release 4.1.2+ 2023-04-06 14:02:16 +02:00
Sara Arjona cb8bf1e001 Merge branch 'install_401_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_401_STABLE 2023-04-06 14:02:12 +02:00
Sara Arjona 1f7063cc09 Merge branch 'MDL-75301-fix' of https://github.com/sarjona/moodle into MOODLE_401_STABLE 2023-04-06 08:12:03 +02:00
Sara Arjona 60954253d4 MDL-75301 quiz: Fix failing behat test
I cherry-picked this branch from master (because the current 401
had some conflicts). In master, quiz has been moved to quiz_settings,
so that's why the behat test start failing. Using the proper name
fixes it.
2023-04-06 07:15:57 +02:00
Paul Holden d49ebb79c5 MDL-77860 tool_moodlenet: use localised language strings for import. 2023-04-05 23:05:23 +01:00
Jun Pataleta 85a7c17414 MDL-77856 qtype_multianswer: Use Bootstrap Popover for subq feedback
The YUI Overlay widget encloses the subquestion feedback in a div
which causes a div element to be enclosed in the subquestion span. This
leads to an accessibility issue in terms of HTML parsing as inline
elements (span) should not contain block elements (div)
The YUI Overlay widget is also not accessible as it does not really hide
the overlay contents via aria-hidden when the overlay is not being
shown. It's better if we stop using this and use Bootstrap's
popover component which is more accessible by default.

This patch also removes module.js for the qtype_multianswer plugin as
it only contains codes related to rendering the feedback contents in the
YUI overlay widget which is no longer necessary.
2023-04-06 05:35:11 +08:00
Sara Arjona c9b7b94ed4 Merge branch 'MDL-73642_MOODLE_401_STABLE' of https://github.com/tasosb/moodle into MOODLE_401_STABLE 2023-04-05 17:17:35 +02:00
Mark Johnson 0bc265e900 MDL-75301 quiz: Use "always latest" option for question previews
This will set the "alwayslatest" option when previewing a question from
the quiz according to the version setting used in the quiz slot.
2023-04-05 12:50:05 +02:00
Mark Johnson 6417d795b9 MDL-75301 question: Add "always latest" option to previews 2023-04-05 12:49:58 +02:00
Mark Johnson c55473ad2e MDL-75301 question: Add behat generator for updating questions
This adds "core_question > updated question" as an entity for `the
following "X" exist` and calls the existing update_question() generator
which will create a new question version with the supplied data.
2023-04-05 12:49:51 +02:00
Shamim Rezaie ddaf4b7a58 Merge branch 'MDL-77555-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2023-04-05 20:15:39 +10:00
Paul Holden be58d68f20 MDL-77555 reportbuilder: improve SQL generation within filters.
Use native ANSI SQL syntax for numeric comparisons where possible,
define filter API for the case where filters must re-use the given
field SQL while ensuring uniqueness of any field parameters.

Currently only necessary in the category filter type.
2023-04-05 09:40:31 +01:00
Paul Holden 131d80441a Merge branch 'MDL-69551_401' of https://github.com/timhunt/moodle into MOODLE_401_STABLE 2023-04-05 08:56:45 +01:00
AMOS bot 8a588f963c Automatically generated installer lang files 2023-04-05 00:07:43 +00:00
Juan Leyva 4e32a42415 MDL-77788 mod_assign: Apply format_string to group names in WS 2023-04-04 18:11:03 -03:00
Tim Hunt aa9a462a4b MDL-69551 quiz: start quiz password field should be a passwordunmask
This help accessibility and usability
2023-04-04 12:19:49 +01:00
Paul Holden 571c59b60e Merge branch 'MDL-77712-401' of https://github.com/laurentdavid/moodle into MOODLE_401_STABLE 2023-04-04 12:06:42 +01:00
Paul Holden 360f9e37f8 MDL-77012 editor_tiny: correct block formats property syntax.
Co-authored-by: Hiroto Kagotani <hiroto.kagotani@gmail.com>
2023-04-04 09:53:20 +01:00
Andrew Nicols 65ba7df04b Merge branch 'MDL-77584-401' of https://github.com/ewallah/moodle into MOODLE_401_STABLE 2023-04-04 12:07:23 +08:00
AMOS bot 02aed7f6fc Automatically generated installer lang files 2023-04-04 00:07:43 +00:00
Paul Holden 2a4f86051b Merge branch 'MDL-75906-401' of https://github.com/mickhawkins/moodle into MOODLE_401_STABLE 2023-04-03 14:54:05 +01:00
Paul Holden 701541f35b Merge branch 'MDL-77227-401' of https://github.com/aanabit/moodle into MOODLE_401_STABLE 2023-04-03 10:56:19 +01:00
Sara Arjona 839681dc77 Merge branch 'MDL-77807-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2023-04-03 11:53:19 +02:00
Michael Hawkins b33e5466df MDL-75906 core: Updated security.txt expiry 2023-04-03 17:27:30 +08:00
Laurent David fbbdfefdb6 MDL-77712 core_course: Fix exception with inplace editor
* On fresh install, an exception is raised when we try to modify
the name of a newly inserted activity in the front page
2023-04-03 10:43:11 +02:00
Ferran Recio 8656271a63 MDL-77552 core_courseformat: add plugin and module to cm state
Backport of MDL-77386
2023-04-03 09:35:08 +02:00
Ilya Tregubov e1f3e7f232 Merge branch 'MDL-77603' of https://github.com/ilyatregubov/moodle into MOODLE_401_STABLE 2023-04-03 10:03:53 +08:00
Andrew Nicols f6c4303c58 MDL-77603 theme_boost: Add reference to CL update 2023-04-03 10:02:41 +08:00
Petr Skoda 134dc470c7 MDL-77603 theme_boost: remove Bootstrap version reference 2023-04-03 10:02:36 +08:00
Petr Skoda 45cd887f3b MDL-77603 tool_componentlibrary: import Bootstrap v4.6.2 2023-04-03 10:02:31 +08:00
Petr Skoda b65ec774c5 MDL-77603 theme_boost: import Bootstrap v4.6.2 2023-04-03 10:02:19 +08:00
AMOS bot 215e9f0688 Automatically generated installer lang files 2023-04-02 00:07:37 +00:00
AMOS bot 035ebed95b Automatically generated installer lang files 2023-04-01 00:07:46 +00:00
Paul Holden a78f3a02c6 weekly release 4.1.2+ 2023-03-31 18:11:53 +01:00
Paul Holden 2890546f44 Merge branch 'install_401_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_401_STABLE 2023-03-31 18:11:51 +01:00
Jake Dallimore a1df38dfc6 Merge branch 'MDL-59175-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2023-03-31 11:57:27 +08:00
Jake Dallimore 82f9585f55 Merge branch 'MDL-77794-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2023-03-31 11:48:42 +08:00
Andrew Nicols 0f092b86b5 Merge branch 'MDL-77783-401' of https://github.com/junpataleta/moodle into MOODLE_401_STABLE 2023-03-31 11:31:05 +08:00
Ilya Tregubov 74f3c34cbf Merge branch 'MDL-70976-401' of https://github.com/laurentdavid/moodle into MOODLE_401_STABLE 2023-03-31 09:41:45 +08:00
AMOS bot 1f337e7665 Automatically generated installer lang files 2023-03-31 00:07:40 +00:00
Paul Holden fbb6dc4ca3 MDL-77807 files: normalise file entity size/type column fields.
The `filename` field was only used by each to determine whether the
file was itself a directory, and it's presence meant that aggregation
of each column wasn't working properly.
2023-03-30 20:46:57 +01:00
Laurent David ab3a244568 MDL-77456 core_courseformat: Fix highlight in course index
* When navigating to a restricted activity as a student from the course index
the item is not highlighted when refreshing the page.
2023-03-30 18:57:33 +02:00
Sara Arjona 7b13a543ad Merge branch 'MDL-77761-401' of https://github.com/junpataleta/moodle into MOODLE_401_STABLE 2023-03-30 17:53:20 +02:00
Sara Arjona dd10dead0f Merge branch 'MDL-77764-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2023-03-30 16:31:21 +02:00
Paul Holden b84a48d1ee Merge branch 'MDL-76481_401_Brickfield_TCPDF_error' of https://github.com/brickfield/moodle into MOODLE_401_STABLE 2023-03-30 12:50:09 +01:00
Sara Arjona 3794210019 Merge branch 'MDL-77762-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2023-03-30 13:45:18 +02:00
Jun Pataleta cca75ca956 Merge branch 'MDL-77333_401' of https://github.com/stronk7/moodle into MOODLE_401_STABLE 2023-03-30 19:03:40 +08:00
Paul Holden 93c91af6a9 Merge branch 'MDL-77773-401' of https://github.com/andrewnicols/moodle into MOODLE_401_STABLE 2023-03-30 10:41:20 +01:00
Paul Holden 8755738360 MDL-77794 contentbank: fix plugin type language strings.
AMOS BEGIN
 CPY [type_contentbank,core_plugin],[type_contenttype,core_plugin]
 CPY [type_contentbank_plural,core_plugin],[type_contenttype_plural,core_plugin]
AMOS END
2023-03-30 09:58:32 +01:00
Max Larkin 4f8c5ef086 MDL-76481 tool_brickfield: Fix PHP 8 report download 2023-03-30 09:15:05 +01:00
Paul Holden 5d10e53ba1 Merge branch 'MDL-75017_401' of https://github.com/timhunt/moodle into MOODLE_401_STABLE 2023-03-30 08:47:54 +01:00
Ilya Tregubov cc0c62d564 Merge branch 'MDL-73771_401' of https://github.com/lostrogit/moodle into MOODLE_401_STABLE 2023-03-30 14:22:53 +08:00
AMOS bot 48fb5b6e88 Automatically generated installer lang files 2023-03-30 00:07:44 +00:00
Amaia Anabitarte bc4b6cf8f7 MDL-77227 roles: Remove extra information for override page 2023-03-29 18:20:23 +02:00
Carlos Castillo f17b006dcb MDL-73771 theme: Fix scrollbar position 2023-03-29 09:15:39 -05:00
Shamim Rezaie d50bb07b2b Merge branch 'MDL-74452_401_STABLE' of https://github.com/marxjohnson/moodle into MOODLE_401_STABLE 2023-03-29 15:27:15 +11:00
Ilya Tregubov b85f6a660b Merge branch 'MDL-77740-401' of https://github.com/andrewnicols/moodle into MOODLE_401_STABLE 2023-03-29 10:58:47 +08:00
Jun Pataleta dd584ab981 Merge branch 'MDL-77382-401' of https://github.com/snake/moodle into MOODLE_401_STABLE 2023-03-29 09:48:17 +08:00
Jun Pataleta ab9b09908a Merge branch 'MDL-76941-401-2' of https://github.com/HuongNV13/moodle into MOODLE_401_STABLE 2023-03-29 09:31:51 +08:00
Jun Pataleta c99abd65ca MDL-77783 core: Validate sublugins.json
* Validate the decoded subplugins.json before processing it.
* Log errors if subplugins.json is invalid or if plugintypes is not
defined.
2023-03-29 09:29:57 +08:00
Jun Pataleta 436ed2c4cb MDL-77761 core_form: Add label for editor format selector 2023-03-29 08:09:54 +08:00
Eloy Lafuente (stronk7) d0ddc7692b MDL-77333 mod_resource: fixes generator uploading files + tests
MDL-76499 revealed a few problems with resource generators:

1. We were not covering with unit tests the upload of files from disk
   (and here it's where the problem was).
2. There was a little of confusion between disk paths (only needed
   to upload files) and file_area paths (the generator only creates
   or uploads files to the root directory of the file area.
3. It was possible to request the upload of a file to the generator
   without that file effectively existing.

This commit fixes those points  and covers 99% of the generator code.
2023-03-28 19:06:18 +02:00
Paul Holden 449a8b0b31 weekly release 4.1.2+ 2023-03-28 15:55:18 +01:00
Paul Holden 0f251ce165 Merge branch 'install_401_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_401_STABLE 2023-03-28 15:55:16 +01:00
Andrew Nicols 5df8e8fa5f MDL-77773 editor_tiny: Improve initial editor size 2023-03-28 20:48:05 +08:00
Paul Holden 7f6888bd53 Merge branch 'MDL-77105-401-4' of https://github.com/junpataleta/moodle into MOODLE_401_STABLE 2023-03-28 12:38:52 +01:00
Jun Pataleta 95d5b0aab0 MDL-77105 core: Cast custom data to an array when evaluating filtericon
Since other modules may treat custom data as an object, we need to make
cast it to an array before evaluating for the `filtericon` custom data.
2023-03-28 18:49:27 +08:00
Huong Nguyen 9a05e393dd MDL-76941 tool_usertours: Fix accessibility issue when resizing 2023-03-28 12:18:17 +07:00
Meirza 110ec6a703 MDL-77436 auth_oauth2: Update profile fields based on data mapping.
After the user creation, the system must call an update function to update profile_fields_*.
We also provided two functions into user/profile/lib.php to get available from other areas.
We added PHP unit testing for new public functions and
the Behat tests for custom profile fields with locked and unlocked statuses.

Co-authored-by: Matt Porritt <matt.porritt@moodle.com>
2023-03-28 11:47:23 +07:00
Matt Porritt 714764d966 MDL-77436 auth_oauth2: Allow admin to choose profile fields for mapping
Update oauth2 to allow mapping of provider attributes against
user profile fields. Fields can also be locked to prevent
user changes.

Co-Authored-By: Michael Milette <michael.milette@tngconsulting.ca>
2023-03-28 11:46:51 +07:00
Jun Pataleta ae63139985 Merge branch 'MDL-77630-401' of https://github.com/meirzamoodle/moodle into MOODLE_401_STABLE 2023-03-28 12:06:17 +08:00
Jake Dallimore e144ff8a43 Merge branch 'MDL-77105-401-4' of https://github.com/junpataleta/moodle into MOODLE_401_STABLE 2023-03-28 11:42:42 +08:00
Meirza cd528df093 MDL-77630 mod_forum: correct typo in variable names 2023-03-28 09:39:01 +07:00
Jun Pataleta 6b96cf8676 Merge branch 'MDL-77670-401' of https://github.com/juancs/moodle into MOODLE_401_STABLE 2023-03-28 07:05:04 +08:00
Paul Holden 2b0a0cecdf MDL-77762 contentbank: always provide exit button when appropriate.
If the current user can access the content bank in the context of the
current item, then provide link back to it.
2023-03-27 18:08:53 +01:00
Paul Holden 7b614d6f51 MDL-77764 contentbank: add field label to context selection element. 2023-03-27 16:54:53 +01:00
Sara Arjona e7cda153a2 Merge branch 'MDL-76376_m41' of https://github.com/jrchamp/moodle into MOODLE_401_STABLE 2023-03-27 16:56:30 +02:00
Sara Arjona 0d6eaef200 Merge branch 'MDL-77659_401' of https://github.com/AnupamaSarjoshi/moodle into MOODLE_401_STABLE 2023-03-27 16:50:31 +02:00
Sara Arjona f32c46bf0c Merge branch 'MDL-73226-401' of https://github.com/jleyva/moodle into MOODLE_401_STABLE 2023-03-27 16:08:26 +02:00
Juan Leyva eeae99afc7 MDL-73226 files: Add quota checks to core_user_add_user_private_files 2023-03-27 13:25:43 +02:00
Sara Arjona 8c9b54cc5d Merge branch 'MDL-76303-401' of https://github.com/ssj365/moodle into MOODLE_401_STABLE 2023-03-27 12:41:18 +02:00
Paul Holden da42c76300 Merge branch 'MDL-77729_401' of https://github.com/timhunt/moodle into MOODLE_401_STABLE 2023-03-27 11:09:26 +01:00
Sara Arjona c2714ff1af Merge branch 'MDL-77561-401' of https://github.com/lameze/moodle into MOODLE_401_STABLE 2023-03-27 11:43:26 +02:00
Paul Holden f2cbacbd24 MDL-77555 reportbuilder: method to ensure unique parameters in SQL. 2023-03-27 10:29:31 +01:00
Jun Pataleta bc1d7e854b MDL-77105 core: Add upgrade.txt notes 2023-03-27 14:00:58 +08:00
Jun Pataleta 99bc8f2b30 MDL-77105 mod_url: Declare filtericon custom data
* Set a custom data `filtericon` when the icon being rendered for the
URL resource is not equal to the default plugin icon.
2023-03-27 14:00:58 +08:00
Jun Pataleta 67f739499b MDL-77105 mod_lti: Add 'nofilter' class for custom tool icons
Add a '.nofilter' class when rendering custom tool icons in order
to render them as is and without CSS filter on the activity chooser.
2023-03-27 14:00:38 +08:00
Jun Pataleta 538e17e199 MDL-77105 core_course: Add 'nofilter' class for non-monologo icons
When rendering content items, check whether the plugin has monologo
icons. If so, add a 'nofilter' class so the plugin icon can be
rendered as is and without the CSS filter.
2023-03-27 14:00:18 +08:00
Jun Pataleta 28d8c9c1e7 MDL-77105 block_timeline: Add 'nofilter' class when necessary
Add the `.nofilter` class for activity icons when the icon URL's
`filtericon` parameter is not set, so they get rendered as they are on
the timeline block.
2023-03-27 13:59:54 +08:00
Jun Pataleta 18703b71ac MDL-77105 block_recentlyaccesseditems: Add 'nofilter' class
Add the `.nofilter` class for activity icons when the icon URL's
`filtericon` parameter is not set, so they get rendered as they are on
the recently accessed items block.
2023-03-27 13:59:33 +08:00
Jun Pataleta f59220a76d MDL-77105 theme_boost: Add 'nofilter' class when necessary
Add the `.nofilter` class for activity icons when the icon URL's
`filtericon` parameter is not set, so they get rendered as they are on
the context header on the activity page.
2023-03-27 13:42:43 +08:00
Jun Pataleta 84198e03c7 MDL-77105 course_format: Add 'nofilter' class when necessary
Add the `.nofilter` class for activity icons when the icon URL's
`filtericon` parameter is not set, so they get rendered as they are on
the course homepage.
2023-03-27 13:42:43 +08:00
Jun Pataleta f5d445a68f MDL-77105 core: Conditionally apply icon filter
* Apply the filter CSS property only to activity icons
that don't have the ".nofilter" class. This will allow
activities with non-SVG icons to be rendered as they are.
2023-03-27 13:42:41 +08:00
Jun Pataleta 30b0b0cf40 MDL-77105 core: Add a filtericon parameter to course mod icon URLs
* If a plugin defines a `filtericon` custom data or uses its monologo
version of the icon, a `filtericon` parameter is being added to the
icon's URL. This information can help plugins determine whether to
render the activity icon as is or with CSS filtering.
2023-03-27 13:36:42 +08:00
Jun Pataleta 69948eda10 MDL-77105 core: Method to determine whether a plugin has monolog icons 2023-03-27 13:36:38 +08:00
AMOS bot 1f4daa7402 Automatically generated installer lang files 2023-03-26 00:07:41 +00:00
Tim Hunt c1c1ca6e4d MDL-77729 qformat_missingword: fix form of help link 2023-03-25 15:39:27 +00:00
Andrew Nicols 50d1671a54 MDL-77740 editor_tiny: Set the Editor window from the iFrame element
This is a workaround for an upstream bug which I have not been able to
reproduce outside of Moodle whereby the editor.contentWindow does not
math the editor.iframeElement.contentWindow when it should.

This issue only seems to affect Firefox, and it may even be a bug in
Firefox. It can only be reproduced when using a fresh browser which has
never had a TinyMCE window open.
2023-03-24 21:34:21 +08:00
Tim Hunt fd9b8bf4d0 MDL-75017 questions: give a clear error if the context type is invalid 2023-03-24 13:00:09 +00:00
Tim Hunt e405e6fd6a MDL-75017 questions: fix weird setup in qformat_xml_import_export_test 2023-03-24 13:00:07 +00:00
Sara Arjona 5898c3e5dd weekly release 4.1.2+ 2023-03-24 13:59:31 +01:00
Jun Pataleta d2bc54fba9 Merge branch 'MDL-77669-401' of https://github.com/andrewnicols/moodle into MOODLE_401_STABLE 2023-03-24 11:51:36 +08:00
Jun Pataleta 9b07f56add Merge branch 'MDL-77626_401' of https://github.com/timhunt/moodle into MOODLE_401_STABLE 2023-03-24 10:40:36 +08:00
Simey Lameze 7c1c1071e7 MDL-77561 behat: add step to accept dpa and enable bigbluebutton
The step i_enable_plugin cannot be used as bigbluebuttonbn_default_dpa_accepted
setting needs to be enable in order for the BigBlueButton plugin to be enabled.
2023-03-24 08:21:05 +08:00
Sara Arjona d2d49e150b Merge branch 'MDL-77705-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2023-03-23 18:30:27 +01:00
Sara Arjona a7d71e0e2d Merge branch 'MDL-77666-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2023-03-23 17:53:45 +01:00
Shamiso.Jaravaza b924f6a355 MDL-76303 mod_bigbluebuttonbn: Fix userlimit 2023-03-23 08:23:23 -06:00
Paul Holden 3fa384384e Merge branch 'MDL-77392-401' of https://github.com/srobotta/moodle into MOODLE_401_STABLE 2023-03-23 12:25:29 +00:00
Stephan Robotta 66e88ace0f MDL-77392 calendar: calendar items are hidden because of settings 2023-03-23 12:53:09 +01:00
Paul Holden fe07140e27 Merge branch 'MDL-77691-401' of https://github.com/roland04/moodle into MOODLE_401_STABLE 2023-03-23 11:43:41 +00:00
Sara Arjona fad4e118a4 Merge branch 'MDL-77380-401' of https://github.com/lameze/moodle into MOODLE_401_STABLE 2023-03-22 14:06:07 +01:00
Sara Arjona 0d80eea9af Merge branch 'MDL-77692-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2023-03-22 13:50:11 +01:00
Ilya Tregubov 0b973b52e9 weekly release 4.1.2+ 2023-03-22 09:37:08 +08:00
Ilya Tregubov b158354710 Merge branch 'install_401_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_401_STABLE 2023-03-22 09:37:06 +08:00
Paul Holden ee3924702f MDL-77705 reportbuilder: avoid re-using field alias between entities.
Ensure that when the user entity is added multiple times to a report,
when there are custom profile fields, each of those gets a unique table
alias per-entity.
2023-03-21 17:29:09 +00:00
Mark Johnson 0d4d201a8c MDL-74452 quiz: Display an error if all versions are in draft status 2023-03-21 15:25:02 +00:00
Mikel Martín 0b2085648e MDL-77691 behat: Add step to navigate to profile page directly 2023-03-21 08:08:10 +01:00
Simey Lameze 939a416308 MDL-77380 block_myoverview: improve show toggle functionality test 2023-03-21 14:47:25 +08:00
Jun Pataleta 28d1db2f1d Merge branch 'MDL-75746_401_STABLE' of https://github.com/marxjohnson/moodle into MOODLE_401_STABLE 2023-03-21 12:01:01 +08:00
Angelia Dela Cruz 084c120c79 MDL-77577 Behat: Replaced the use of "Install selected language pack(s)
Evaluated usage of "Install selected language pack(s)" in Behat and
replaced the steps to use generator to install language packs as part
of test setup.
2023-03-21 12:00:53 +08:00
Meirza 29a1cf86a3 MDL-77669 dml: Added extrainfo in the DB options config.
extrainfo is an extra information for the DB driver, e.g. SQL Server,
has additional configuration according to its environment,
which the administrator can specify to alter and override any connection options.

Co-authored-by: LukeCarrier <luke@carrier.im>

This is a backport of MDL-64153.
2023-03-21 08:48:39 +08:00
Ilya Tregubov ea915c29e5 Merge branch 'mdl-72533-event-table-performance-MOODLE_401_STABLE' of https://github.com/petersistrom/moodle into MOODLE_401_STABLE 2023-03-21 08:24:59 +08:00
Paul Holden deced83b06 Merge branch 'MDL-72124_401_STABLE' of https://github.com/marxjohnson/moodle into MOODLE_401_STABLE 2023-03-20 17:04:54 +00:00
Paul Holden fac1828222 MDL-77692 reportbuilder: format custom field condition/filter names. 2023-03-20 16:32:08 +00:00
Sara Arjona 502536c16e Merge branch 'MDL-77574-401' of https://github.com/lameze/moodle into MOODLE_401_STABLE 2023-03-20 17:06:06 +01:00
Anupama Sarjoshi 3fee9d949f MDL-77659 core_reportbuilder: fix user profile fields phpunit tests 2023-03-20 13:53:46 +00:00
Sara Arjona 1bfc0a1d14 Merge branch 'MDL-77523-401' of https://github.com/juancs/moodle into MOODLE_401_STABLE 2023-03-20 13:42:49 +01:00
Mark Sharp f8301fb4bc MDL-72533 core_calendar: improve calendar event table peformance
- Filter searchable courses if set.
- Sites with large groups should now experience better performance.
- Applies and optimises query against a proper date range.

Co-authored-by: Mark Sharp <mark.sharp@solent.ac.uk>
Co-authored-by: Peter Sistrom <petersistrom@catalyst-au.net>
Co-authored-by: Kevin Pham <keevan.pham@gmail.com>
2023-03-20 12:09:24 +11:00
AMOS bot 9e97a353ad Automatically generated installer lang files 2023-03-20 00:07:41 +00:00
Brendan Heywood 6e27e9e481 MDL-77618 admin: Passwords should not auto complete 2023-03-18 13:02:58 +11:00
AMOS bot e8cbc0b653 Automatically generated installer lang files 2023-03-18 00:07:42 +00:00
Juan Segarra Montesinos b296667ebf MDL-77670 course: Preserve course summary format 2023-03-17 20:54:07 +01:00
Sara Arjona 231c25e498 weekly release 4.1.2+ 2023-03-17 17:58:21 +01:00
Sara Arjona 3ff1293c12 Merge branch 'install_401_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_401_STABLE 2023-03-17 17:58:12 +01:00
Jun Pataleta eda00e2c73 Merge branch 'MDL-76257-401' of https://github.com/juancs/moodle into MOODLE_401_STABLE 2023-03-17 11:01:22 +08:00
Jun Pataleta 85b4e2312e Merge branch 'MDL-77608-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2023-03-17 10:29:48 +08:00
AMOS bot 5707e7d40f Automatically generated installer lang files 2023-03-17 00:07:43 +00:00
Paul Holden 71e751565c MDL-77666 contentbank: format category names for display. 2023-03-16 23:27:13 +00:00
info@eWallah.net b3adaaf47f MDL-77584 currencies: Fix outdated ISO 4217 code for Zambian Kwacha
On 2013-01-01 the ZMK code got replaced by the new ZMW code.

AMOS BEGIN
 MOV [ZMK,core_currencies],[ZMW,core_currencies]
AMOS END
2023-03-16 19:53:38 +01:00
Jake Dallimore 282df774a4 Merge branch 'MDL-76309-MOODLE_401_STABLE' of https://github.com/sh-csg/moodle into MOODLE_401_STABLE 2023-03-16 15:47:16 +08:00
Stefan Hanauska 9f5492df71 MDL-76309 mod_assign: Compatibility with zip download 2023-03-16 07:18:35 +01:00
Paul Holden 0920bd0810 MDL-76339 mod_bigbluebuttonbn: correct upgrade note fix versions.
Use agreed task name language string.
2023-03-15 19:15:29 +00:00
Paul Holden a89cba84a1 Merge branch 'MDL-76339-401' of https://github.com/call-learning/moodle into MOODLE_401_STABLE 2023-03-15 19:05:57 +00:00
Andrew Nicols f0b3ff2134 Merge branch 'MDL-77550-401' of https://github.com/dpalou/moodle into MOODLE_401_STABLE 2023-03-15 14:26:26 +08:00
Juan Segarra Montesinos dbd7cedd24 MDL-77523 mod_data: textarea field should honor editor preferred format 2023-03-15 06:49:02 +01:00
Jun Pataleta d5331827c4 Merge branch 'MDL-77611_401' of https://github.com/timhunt/moodle into MOODLE_401_STABLE 2023-03-15 10:01:04 +08:00
AMOS bot 91f503c3f6 Automatically generated installer lang files 2023-03-15 00:07:40 +00:00
Paul Holden 4ee053a278 MDL-59175 theme_boost: style inherited permission on overide page. 2023-03-14 20:58:07 +00:00
Tim Hunt 4bc3782c4c MDL-77626 quiz statistics: Divide by zero if a random Q has max mark 0 2023-03-14 16:34:30 +00:00
Mark Johnson 5a7ad7b284 MDL-75746 mod_quiz: Fix backup and restore of quiz slots 2023-03-14 16:07:46 +00:00
Mark Johnson 34c452afb4 MDL-72124 question: Fix system-level URLs in question events 2023-03-14 15:40:26 +00:00
Eloy Lafuente (stronk7) 6c260e6ae7 weekly release 4.1.2+ 2023-03-14 16:07:32 +01:00
Eloy Lafuente (stronk7) 09e74578a1 Merge branch 'install_401_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_401_STABLE 2023-03-14 16:07:27 +01:00
Jun Pataleta f1a9181a77 Merge branch 'MDL-76680/401' of https://github.com/skodak/moodle into MOODLE_401_STABLE 2023-03-14 21:43:31 +08:00
Laurent David 8ee689f612 MDL-70976 core_files: Allow for draft files url inserted in content
* The file file_remove_editor_orphaned_files should take into account URL that
have been embedded in a tag content instead of an attribute (like src attribute)
* This will fix issue with inserting H5P content in calendar events.
2023-03-14 08:48:32 +01:00
Juan Segarra Montesinos a003b3b882 MDL-76257 core: Activity intro should honor user preference on creation 2023-03-14 06:07:28 +01:00
AMOS bot 9617555887 Automatically generated installer lang files 2023-03-14 00:07:42 +00:00
Tim Hunt a5238d8c94 MDL-77611 tool_uploaduser: fix phpunit assumptions about profile fields 2023-03-13 13:35:38 +00:00
Paul Holden 34e2d74921 MDL-77608 mod_h5pactivity: namespace component lib testcase. 2023-03-13 09:43:57 +00:00
Simey Lameze 76cb263244 MDL-77574 myoverview: wait until next button exists before clicking 2023-03-13 14:12:26 +08:00
Jake Dallimore 4ce5755a63 MDL-77382 core: fix error handling in oauth2 callback for auth'd users
Pass the errors back to the calling code when the user is authenticated,
otherwise, fall back on the existing redirection to the login page.
2023-03-13 10:48:21 +08:00
AMOS bot a509b37926 Automatically generated installer lang files 2023-03-12 00:07:50 +00:00
Petr Skoda 7e96a1c887 MDL-76680 core: disable $USER->ignoresesskey on next page 2023-03-11 10:37:24 +01:00
Dani Palou c90c9b04f6 MDL-77550 lesson: Format decimals in feedback page 2023-03-08 07:46:16 +01:00
Roberto Bravo 9fbd5fb219 MDL-77256 tool_uploaduser: fix get allowed roles for courses
During user upload the permission to assign roles is checked in the wrong context
2023-03-07 18:41:00 +01:00
Laurent David a00d72c622 MDL-76339 mod_bigbluebuttonbn: Update dismissed recordings
* When the server is not reachable (or recordings are not reacheable)
the recording is set to RECORDING_STATUS_DISMISSED and is then
not visible in the list.
* Add a CLI script to solve potential issue with DISMISSED recordings
2023-03-03 06:39:02 +01:00
Anastasios Bithas f23463f8d6 MDL-73642 mod_assign: Disable draft option if submission already made 2023-02-04 18:38:10 +02:00
Mark Holliday c55f219d77 MDL-76376 scorm: fix PHP8 TypeError (null is not Countable) 2022-11-28 17:52:10 -05:00
427 changed files with 38684 additions and 26791 deletions
+1 -1
View File
@@ -156,7 +156,7 @@ const getThirdPartyLibsList = relativeTo => {
* @returns {array}
*/
const getThirdPartyPaths = () => {
const DOMParser = require('xmldom').DOMParser;
const DOMParser = require('@xmldom/xmldom').DOMParser;
const fs = require('fs');
const path = require('path');
const xpath = require('xpath');
+8 -6
View File
@@ -45,6 +45,7 @@ module.exports = grunt => {
grunt.log.write(result);
if (error) {
grunt.log.error(result.stdout);
process.exit(code);
}
done();
@@ -56,7 +57,7 @@ module.exports = grunt => {
* @param {array} args
* @returns {object}
*/
const spawnNodeCall = args => grunt.util.spawn({
const spawnNodeCall = (args) => grunt.util.spawn({
cmd: 'node',
args,
}, getSpawnHandler(grunt.task.current.async()));
@@ -91,13 +92,14 @@ module.exports = grunt => {
* @returns {Object} Reference to the spawned task
*/
const cssBuild = () => spawnNodeCall([
'node_modules/node-sass/bin/node-sass',
'--output-style', 'expanded',
'--source-map', true,
'--source-map-contents', true,
'node_modules/sass/sass.js',
'--style', 'expanded',
'--source-map',
'--embed-sources',
'--precision', 6,
'--load-path', process.cwd(),
getCLPath('hugo/scss/docs.scss'),
'-o', getCLPath('hugo/dist/css/'),
getCLPath('hugo/dist/css/docs.css'),
]);
// Register the various component library tasks.
+1 -1
View File
@@ -32,7 +32,7 @@ module.exports = grunt => {
}
},
options: {
implementation: require('node-sass'),
implementation: require('sass'),
includePaths: ["theme/boost/scss/", "theme/classic/scss/"]
}
},
+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/>.
/**
* Reset course indentation
*
* @copyright 2023 Amaia Anabitarte <amaia@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once('../../config.php');
require_admin();
$format = required_param('format', PARAM_PLUGIN);
$confirm = optional_param('confirm', false, PARAM_BOOL);
$backurl = new moodle_url('/admin/settings.php', ['section' => 'formatsetting'.$format]);
$PAGE->set_url('/admin/course/resetindentation.php', ['format' => $format]);
$PAGE->set_context(context_system::instance());
if ($confirm) {
require_sesskey();
$courses = $DB->get_records('course', ['format' => $format], 'id', 'id');
if (!empty($courses)) {
$courseids = array_keys($courses);
list($courseinsql, $courseparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED, 'course');
$DB->set_field_select('course_modules', 'indent', '0', "course $courseinsql AND indent <> 0", $courseparams);
rebuild_course_cache(0, true);
}
redirect(
$backurl,
get_string('resetindentationsuccess', 'admin'),
null,
\core\output\notification::NOTIFY_SUCCESS
);
}
$strtitle = get_string('resetindentation', 'admin');
$PAGE->set_title($strtitle);
$PAGE->set_heading($strtitle);
navigation_node::override_active_url(new moodle_url(
'/admin/course/resetindentation.php',
['action' => 'confirm', 'format' => $format]
));
echo $OUTPUT->header();
$displayoptions = ['confirmtitle' => get_string('resetindentation_title', 'admin')];
$confirmbutton = new single_button(
new moodle_url('/admin/course/resetindentation.php', ['confirm' => 1, 'format' => $format, 'sesskey' => sesskey()]),
get_string('resetindentation', 'admin'),
'post'
);
$cancelbutton = new single_button($backurl, get_string('cancel'));
echo $OUTPUT->confirm(
get_string('resetindentation_help', 'admin', ['format' => get_string('pluginname', 'format_'.$format)]),
$confirmbutton,
$cancelbutton,
$displayoptions
);
echo $OUTPUT->footer();
+2
View File
@@ -4109,6 +4109,8 @@
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_xmlrpc_usage" level="optional">
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mod_assignment" level="required">
</CUSTOM_CHECK>
</CUSTOM_CHECKS>
</MOODLE>
</COMPATIBILITY_MATRIX>
+23 -12
View File
@@ -30,25 +30,36 @@
require_once(__DIR__ . '/../config.php');
// The state parameter we've given (used in moodle as a redirect url).
// Per https://www.rfc-editor.org/rfc/rfc6749#section-4.1.2.1, state is required, even during error responses.
$state = required_param('state', PARAM_LOCALURL);
$redirecturl = new moodle_url($state);
$params = $redirecturl->params();
$error = optional_param('error', '', PARAM_RAW);
if ($error) {
$message = optional_param('error_description', '', PARAM_RAW);
if ($message) {
$SESSION->loginerrormsg = $message;
redirect(new moodle_url(get_login_url()));
} else {
$SESSION->loginerrormsg = $error;
redirect(new moodle_url(get_login_url()));
$message = optional_param('error_description', null, PARAM_RAW);
// Errors can occur for authenticated users, such as when a user denies authorization for some internal service call.
// In such cases, propagate the error to the component redirect URI.
if (isloggedin()) {
if (isset($params['sesskey']) && confirm_sesskey($params['sesskey'])) {
$redirecturl->param('error', $error);
if ($message) {
$redirecturl->param('error_description', $message);
}
redirect($redirecturl);
}
}
// Not logged in or the sesskey verification failed, redirect to login + show errors.
$SESSION->loginerrormsg = $message ?? $error;
redirect(new moodle_url(get_login_url()));
}
// The authorization code generated by the authorization server.
$code = required_param('code', PARAM_RAW);
// The state parameter we've given (used in moodle as a redirect url).
$state = required_param('state', PARAM_LOCALURL);
$redirecturl = new moodle_url($state);
$params = $redirecturl->params();
if (isset($params['sesskey']) and confirm_sesskey($params['sesskey'])) {
$redirecturl->param('oauth2code', $code);
+1
View File
@@ -90,6 +90,7 @@ $title = get_string('overridepermissionsforrole', 'core_role', $a);
$currenttab = 'permissions';
$PAGE->set_title($title);
$PAGE->activityheader->disable();
$PAGE->navbar->add($straction);
switch ($context->contextlevel) {
case CONTEXT_SYSTEM:
@@ -52,6 +52,7 @@
<span data-passwordunmask="wrapper" data-passwordunmaskid="{{ id }}">
<span data-passwordunmask="editor">
<input type="password"
autocomplete="new-password"
name="{{ name }}"
id="{{ id }}"
value="{{ value }}"
+113
View File
@@ -0,0 +1,113 @@
@core @core_admin @core_course @javascript
Feature: Reset course indentation
In order to reset indentation in course modules
As a admin
I want change indent value for all the modules of a course format courses in one go
Background:
Given the following "courses" exist:
| fullname | shortname | format |
| Topics Course 1 | T1 | topics |
| Topics Course 2 | T2 | topics |
| Weekly Course 1 | W1 | weeks |
| Weekly Course 2 | W2 | weeks |
And the following "activities" exist:
| activity | name | intro | course | idnumber |
| forum | Topics forum name | Topics forum description | T1 | forum1 |
| data | Topics database name | Topics database description | T1 | data1 |
| wiki | Topics wiki name | Topics wiki description | T2 | wiki1 |
| forum | Weekly forum name | Weekly forum description | W1 | forum2 |
| data | Weekly database name | Weekly database description | W1 | data2 |
| wiki | Weekly wiki name | Weekly wiki description | W2 | wiki2 |
And I log in as "admin"
And I am on "Topics Course 1" course homepage with editing mode on
And I open "Topics forum name" actions menu
And I click on "Move right" "link" in the "Topics forum name" activity
And I open "Topics forum name" actions menu
And "Move right" "link" in the "Topics forum name" "activity" should not be visible
And "Move left" "link" in the "Topics forum name" "activity" should be visible
And I press the escape key
And I open "Topics database name" actions menu
And "Move right" "link" in the "Topics database name" "activity" should be visible
And "Move left" "link" in the "Topics database name" "activity" should not be visible
And I am on "Topics Course 2" course homepage with editing mode on
And I open "Topics wiki name" actions menu
And I click on "Move right" "link" in the "Topics wiki name" activity
And I open "Topics wiki name" actions menu
And "Move right" "link" in the "Topics wiki name" "activity" should not be visible
And "Move left" "link" in the "Topics wiki name" "activity" should be visible
And I am on "Weekly Course 1" course homepage with editing mode on
And I open "Weekly forum name" actions menu
And I click on "Move right" "link" in the "Weekly forum name" activity
And I open "Weekly forum name" actions menu
And "Move right" "link" in the "Weekly forum name" "activity" should not be visible
And "Move left" "link" in the "Weekly forum name" "activity" should be visible
And I press the escape key
And I open "Weekly database name" actions menu
And "Move right" "link" in the "Weekly database name" "activity" should be visible
And "Move left" "link" in the "Weekly database name" "activity" should not be visible
And I am on "Weekly Course 2" course homepage with editing mode on
And I open "Weekly wiki name" actions menu
And I click on "Move right" "link" in the "Weekly wiki name" activity
And I open "Weekly wiki name" actions menu
And "Move right" "link" in the "Weekly wiki name" "activity" should not be visible
And "Move left" "link" in the "Weekly wiki name" "activity" should be visible
Scenario Outline: Apply course indentation reset
Given I navigate to "Plugins > Course formats > <format> format" in site administration
And I wait "5" seconds
And "Reset indentation" "link" should exist
When I click on "Reset indentation" "link"
And I should see "Reset indentation on course page"
And "Reset indentation" "button" should exist
And I click on "Reset indentation" "button"
Then I should see "Indentation reset"
And I am on "<format> Course 1" course homepage with editing mode on
And I open "<format> forum name" actions menu
And "Move right" "link" in the "<format> forum name" "activity" should be visible
And "Move left" "link" in the "<format> forum name" "activity" should not be visible
And I press the escape key
And I open "<format> database name" actions menu
And "Move right" "link" in the "<format> database name" "activity" should be visible
And "Move left" "link" in the "<format> database name" "activity" should not be visible
And I am on "<format> Course 2" course homepage with editing mode on
And I open "<format> wiki name" actions menu
And "Move right" "link" in the "<format> wiki name" "activity" should be visible
And "Move left" "link" in the "<format> wiki name" "activity" should not be visible
# Check other course formats had not been reset
And I am on "<other> Course 1" course homepage with editing mode on
And I open "<other> forum name" actions menu
And "Move right" "link" in the "<other> forum name" "activity" should not be visible
And "Move left" "link" in the "<other> forum name" "activity" should be visible
Examples:
| format | other |
| Topics | Weekly |
| Weekly | Topics |
Scenario Outline: Cancel course indentation reset
Given I navigate to "Plugins > Course formats > <format> format" in site administration
And "Reset indentation" "link" should exist
When I click on "Reset indentation" "link"
And I should see "Reset indentation on course page"
And "Reset indentation" "button" should exist
And "Cancel" "button" should exist
And I click on "Cancel" "button"
Then I should not see "Indentation reset"
And I am on "<format> Course 1" course homepage with editing mode on
And I open "<format> forum name" actions menu
And "Move right" "link" in the "<format> forum name" "activity" should not be visible
And "Move left" "link" in the "<format> forum name" "activity" should be visible
And I press the escape key
And I open "<format> database name" actions menu
And "Move right" "link" in the "<format> database name" "activity" should be visible
And "Move left" "link" in the "<format> database name" "activity" should not be visible
And I am on "<format> Course 2" course homepage with editing mode on
And I open "<format> wiki name" actions menu
And "Move right" "link" in the "<format> wiki name" "activity" should not be visible
And "Move left" "link" in the "<format> wiki name" "activity" should be visible
Examples:
| format |
| Topics |
| Weekly |
@@ -254,19 +254,16 @@ class renderer extends \tool_brickfield\output\renderer {
<tr>
<td>
<img src="' . $this->image_path() . 'tachometer-alt-solid.svg" width="15" height="15">' .
' <td style="line-height: 10px;"> ' .
get_string('totalactivitiescount', manager::PLUGINNAME, $data->combodata['total']) .
'</td></td>
'</td>
<td>
<img src="' . $this->image_path() . 'check-square-regular.svg" width="15" height="15">' .
' <td style="line-height: 10px;"> ' .
get_string('passedcount', manager::PLUGINNAME, $data->combodata['passed']) .
'</td></td>
'</td>
<td>
<img src="' . $this->image_path() . 'times-circle-regular.svg" width="15" height="15">' .
' <td style="line-height: 10px;"> ' .
get_string('failedcount', manager::PLUGINNAME, $data->combodata['failed']) .
'</td></td>
'</td>
</tr>
</table>';
@@ -396,8 +393,8 @@ class renderer extends \tool_brickfield\output\renderer {
foreach ($data->groupdata as $key => $group) {
$checkgroup = area_base::checkgroup_name($key);
$icon = $icons[$key];
$tabledata[] = ['<img src="'.$icon.'" width="15" height="15">' . ' ' .' <td style="line-height: 10px;"> '.
get_string('checktype:' . $checkgroup, manager::PLUGINNAME).'</td>', $group->errorinstances];
$tabledata[] = ['<img src="'.$icon.'" width="15" height="15">' . ' ' .
get_string('checktype:' . $checkgroup, manager::PLUGINNAME), $group->errorinstances];
}
return $this->render_table(
+2 -2
View File
@@ -62,8 +62,8 @@ staticDir: "./admin/tool/componentlibrary/content/static"
params:
moodleroot: "MOODLEROOT"
current_version: "4.6.0"
current_ruby_version: "4.6.0"
current_version: "4.6.2"
current_ruby_version: "4.6.2"
docs_version: "4.6"
boostrap_docs: "https://getbootstrap.com/docs/4.6"
repo: "https://github.com/twbs/bootstrap"
@@ -54,4 +54,4 @@ $breadcrumb-divider: none;
Since breadcrumbs provide a navigation, it's a good idea to add a meaningful label such as `aria-label="breadcrumb"` to describe the type of navigation provided in the `<nav>` element, as well as applying an `aria-current="page"` to the last item of the set to indicate that it represents the current page.
For more information, see the [WAI-ARIA Authoring Practices for the breadcrumb pattern](https://www.w3.org/TR/wai-aria-practices/#breadcrumb).
For more information, see the [ARIA Authoring Practices Guide breadcrumb pattern](https://www.w3.org/WAI/ARIA/apg/patterns/breadcrumb/).
@@ -123,10 +123,10 @@ Place a `.btn-group` within another `.btn-group` when you want dropdown menus mi
<button type="button" class="btn btn-secondary">2</button>
<div class="btn-group" role="group">
<button id="btnGroupDrop1" type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
Dropdown
</button>
<div class="dropdown-menu" aria-labelledby="btnGroupDrop1">
<div class="dropdown-menu">
<a class="dropdown-item" href="#">Dropdown link</a>
<a class="dropdown-item" href="#">Dropdown link</a>
</div>
@@ -155,10 +155,10 @@ Make a set of buttons appear vertically stacked rather than horizontally. **Spli
<button type="button" class="btn btn-secondary">Button</button>
<button type="button" class="btn btn-secondary">Button</button>
<div class="btn-group" role="group">
<button id="btnGroupVerticalDrop1" type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
Dropdown
</button>
<div class="dropdown-menu" aria-labelledby="btnGroupVerticalDrop1">
<div class="dropdown-menu">
<a class="dropdown-item" href="#">Dropdown link</a>
<a class="dropdown-item" href="#">Dropdown link</a>
</div>
@@ -166,28 +166,28 @@ Make a set of buttons appear vertically stacked rather than horizontally. **Spli
<button type="button" class="btn btn-secondary">Button</button>
<button type="button" class="btn btn-secondary">Button</button>
<div class="btn-group" role="group">
<button id="btnGroupVerticalDrop2" type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
Dropdown
</button>
<div class="dropdown-menu" aria-labelledby="btnGroupVerticalDrop2">
<div class="dropdown-menu">
<a class="dropdown-item" href="#">Dropdown link</a>
<a class="dropdown-item" href="#">Dropdown link</a>
</div>
</div>
<div class="btn-group" role="group">
<button id="btnGroupVerticalDrop3" type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
Dropdown
</button>
<div class="dropdown-menu" aria-labelledby="btnGroupVerticalDrop3">
<div class="dropdown-menu">
<a class="dropdown-item" href="#">Dropdown link</a>
<a class="dropdown-item" href="#">Dropdown link</a>
</div>
</div>
<div class="btn-group" role="group">
<button id="btnGroupVerticalDrop4" type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
Dropdown
</button>
<div class="dropdown-menu" aria-labelledby="btnGroupVerticalDrop4">
<div class="dropdown-menu">
<a class="dropdown-item" href="#">Dropdown link</a>
<a class="dropdown-item" href="#">Dropdown link</a>
</div>
@@ -81,7 +81,7 @@ Create block level buttons—those that span the full width of a parent—by add
## Active state
Buttons will appear pressed (with a darker background, darker border, and inset shadow) when active. **There's no need to add a class to `<button>`s as they use a pseudo-class**. However, you can still force the same active appearance with `.active` (and include the <code>aria-pressed="true"</code> attribute) should you need to replicate the state programmatically.
Buttons will appear pressed when active with a darker background, darker border, and, when shadows are enabled, an inset shadow. **There's no need to add a class to `<button>`s as they use a pseudo-class**. However, you can still force the same active appearance with `.active` (and include the <code>aria-pressed="true"</code> attribute) should you need to replicate the state programmatically.
{{< example >}}
<a href="#" class="btn btn-primary btn-lg active" role="button" aria-pressed="true">Primary link</a>
@@ -101,19 +101,23 @@ Disabled buttons using the `<a>` element behave a bit different:
- `<a>`s don't support the `disabled` attribute, so you must add the `.disabled` class to make it visually appear disabled.
- Some future-friendly styles are included to disable all `pointer-events` on anchor buttons. In browsers which support that property, you won't see the disabled cursor at all.
- Disabled buttons should include the `aria-disabled="true"` attribute to indicate the state of the element to assistive technologies.
- Disabled buttons using `<a>` should include the `aria-disabled="true"` attribute to indicate the state of the element to assistive technologies.
- Disabled buttons using `<a>` *should not* include the `href` attribute.
{{< example >}}
<a class="btn btn-primary btn-lg disabled" role="button" aria-disabled="true">Primary link</a>
<a class="btn btn-secondary btn-lg disabled" role="button" aria-disabled="true">Link</a>
{{< /example >}}
### Link functionality caveat
To cover cases where you have to keep the `href` attribute on a disabled link, the `.disabled` class uses `pointer-events: none` to try to disable the link functionality of `<a>`s. Note that this CSS property is not yet standardized for HTML, but all modern browsers support it. In addition, even in browsers that do support `pointer-events: none`, keyboard navigation remains unaffected, meaning that sighted keyboard users and users of assistive technologies will still be able to activate these links. So to be safe, in addition to `aria-disabled="true"`, also include a `tabindex="-1"` attribute on these links to prevent them from receiving keyboard focus, and use custom JavaScript to disable their functionality altogether.
{{< example >}}
<a href="#" class="btn btn-primary btn-lg disabled" tabindex="-1" role="button" aria-disabled="true">Primary link</a>
<a href="#" class="btn btn-secondary btn-lg disabled" tabindex="-1" role="button" aria-disabled="true">Link</a>
{{< /example >}}
{{< callout warning >}}
##### Link functionality caveat
The `.disabled` class uses `pointer-events: none` to try to disable the link functionality of `<a>`s, but that CSS property is not yet standardized. In addition, even in browsers that do support `pointer-events: none`, keyboard navigation remains unaffected, meaning that sighted keyboard users and users of assistive technologies will still be able to activate these links. So to be safe, add a `tabindex="-1"` attribute on these links (to prevent them from receiving keyboard focus) and use custom JavaScript to disable their functionality.
{{< /callout >}}
## Button plugin
Do more with buttons. Control button states or create groups of buttons for more components like toolbars.
@@ -309,7 +309,7 @@ Add some navigation to a card's header (or block) with Bootstrap's [nav componen
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
<a class="nav-link disabled">Disabled</a>
</li>
</ul>
</div>
@@ -332,7 +332,7 @@ Add some navigation to a card's header (or block) with Bootstrap's [nav componen
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
<a class="nav-link disabled">Disabled</a>
</li>
</ul>
</div>
@@ -24,7 +24,7 @@ Lastly, if you're building our JavaScript from source, it [requires `util.js`]({
Carousels don't automatically normalize slide dimensions. As such, you may need to use additional utilities or custom styles to appropriately size content. While carousels support previous/next controls and indicators, they're not explicitly required. Add and customize as you see fit.
**The `.active` class needs to be added to one of the slides** otherwise the carousel will not be visible. Also be sure to set a unique id on the `.carousel` for optional controls, especially if you're using multiple carousels on a single page. Control and indicator elements must have a `data-target` attribute (or `href` for links) that matches the id of the `.carousel` element.
**The `.active` class needs to be added to one of the slides** otherwise the carousel will not be visible. Also be sure to set a unique `id` on the `.carousel` for optional controls, especially if you're using multiple carousels on a single page. Control and indicator elements must have a `data-target` attribute (or `href` for links) that matches the `id` of the `.carousel` element.
### Slides only
@@ -48,7 +48,7 @@ Here's a carousel with slides only. Note the presence of the `.d-block` and `.w-
### With controls
Adding in the previous and next controls:
Adding in the previous and next controls. We recommend using `<button>` elements, but you can also use `<a>` elements with `role="button"`.
{{< example >}}
<div id="carouselExampleControls" class="carousel slide" data-ride="carousel">
@@ -63,14 +63,14 @@ Adding in the previous and next controls:
{{< placeholder width="800" height="400" class="bd-placeholder-img-lg d-block w-100" color="#333" background="#555" text="Third slide" >}}
</div>
</div>
<a class="carousel-control-prev" href="#carouselExampleControls" role="button" data-slide="prev">
<button class="carousel-control-prev" type="button" data-target="#carouselExampleControls" data-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="sr-only">Previous</span>
</a>
<a class="carousel-control-next" href="#carouselExampleControls" role="button" data-slide="next">
</button>
<button class="carousel-control-next" type="button" data-target="#carouselExampleControls" data-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="sr-only">Next</span>
</a>
</button>
</div>
{{< /example >}}
@@ -96,14 +96,14 @@ You can also add the indicators to the carousel, alongside the controls, too.
{{< placeholder width="800" height="400" class="bd-placeholder-img-lg d-block w-100" color="#333" background="#555" text="Third slide" >}}
</div>
</div>
<a class="carousel-control-prev" href="#carouselExampleIndicators" role="button" data-slide="prev">
<button class="carousel-control-prev" type="button" data-target="#carouselExampleIndicators" data-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="sr-only">Previous</span>
</a>
<a class="carousel-control-next" href="#carouselExampleIndicators" role="button" data-slide="next">
</button>
<button class="carousel-control-next" type="button" data-target="#carouselExampleIndicators" data-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="sr-only">Next</span>
</a>
</button>
</div>
{{< /example >}}
@@ -141,20 +141,20 @@ Add captions to your slides easily with the `.carousel-caption` element within a
</div>
</div>
</div>
<a class="carousel-control-prev" href="#carouselExampleCaptions" role="button" data-slide="prev">
<button class="carousel-control-prev" type="button" data-target="#carouselExampleCaptions" data-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="sr-only">Previous</span>
</a>
<a class="carousel-control-next" href="#carouselExampleCaptions" role="button" data-slide="next">
</button>
<button class="carousel-control-next" type="button" data-target="#carouselExampleCaptions" data-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="sr-only">Next</span>
</a>
</button>
</div>
{{< /example >}}
### Crossfade
Add `.carousel-fade` to your carousel to animate slides with a fade transition instead of a slide.
Add `.carousel-fade` to your carousel to animate slides with a fade transition instead of a slide. Depending on your carousel content (e.g., text only slides), you may want to add `.bg-body` or some custom CSS to the `.carousel-item`s for proper crossfading.
{{< example >}}
<div id="carouselExampleFade" class="carousel slide carousel-fade" data-ride="carousel">
@@ -169,14 +169,14 @@ Add `.carousel-fade` to your carousel to animate slides with a fade transition i
{{< placeholder width="800" height="400" class="bd-placeholder-img-lg d-block w-100" color="#333" background="#555" text="Third slide" >}}
</div>
</div>
<a class="carousel-control-prev" href="#carouselExampleFade" role="button" data-slide="prev">
<button class="carousel-control-prev" type="button" data-target="#carouselExampleFade" data-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="sr-only">Previous</span>
</a>
<a class="carousel-control-next" href="#carouselExampleFade" role="button" data-slide="next">
</button>
<button class="carousel-control-next" type="button" data-target="#carouselExampleFade" data-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="sr-only">Next</span>
</a>
</button>
</div>
{{< /example >}}
@@ -197,14 +197,14 @@ Add `data-interval=""` to a `.carousel-item` to change the amount of time to del
{{< placeholder width="800" height="400" class="bd-placeholder-img-lg d-block w-100" color="#333" background="#555" text="Third slide" >}}
</div>
</div>
<a class="carousel-control-prev" href="#carouselExampleInterval" role="button" data-slide="prev">
<button class="carousel-control-prev" type="button" data-target="#carouselExampleInterval" data-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="sr-only">Previous</span>
</a>
<a class="carousel-control-next" href="#carouselExampleInterval" role="button" data-slide="next">
</button>
<button class="carousel-control-next" type="button" data-target="#carouselExampleInterval" data-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="sr-only">Next</span>
</a>
</button>
</div>
{{< /example >}}
@@ -225,14 +225,14 @@ Carousels support swiping left/right on touchscreen devices to move between slid
{{< placeholder width="800" height="400" class="bd-placeholder-img-lg d-block w-100" color="#333" background="#555" text="Third slide" >}}
</div>
</div>
<a class="carousel-control-prev" href="#carouselExampleControlsNoTouching" role="button" data-slide="prev">
<button class="carousel-control-prev" type="button" data-target="#carouselExampleControlsNoTouching" data-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="sr-only">Previous</span>
</a>
<a class="carousel-control-next" href="#carouselExampleControlsNoTouching" role="button" data-slide="next">
</button>
<button class="carousel-control-next" type="button" data-target="#carouselExampleControlsNoTouching" data-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="sr-only">Next</span>
</a>
</button>
</div>
{{< /example >}}
@@ -40,6 +40,29 @@ Generally, we recommend using a button with the `data-target` attribute. While n
</div>
{{< /example >}}
## Horizontal
The collapse plugin also supports horizontal collapsing. Add the `.width` modifier class to transition the `width` instead of `height` and set a `width` on the immediate child element. Feel free to write your own custom Sass, use inline styles, or use our [width utilities]({{< docsref "/utilities/sizing" >}}).
{{< callout info >}}
Please note that while the example below has a `min-height` set to avoid excessive repaints in our docs, this is not explicitly required. **Only the `width` on the child element is required.**
{{< /callout >}}
{{< example >}}
<p>
<button class="btn btn-primary" type="button" data-toggle="collapse" data-target="#collapseWidthExample" aria-expanded="false" aria-controls="collapseWidthExample">
Toggle width collapse
</button>
</p>
<div style="min-height: 120px;">
<div class="collapse width" id="collapseWidthExample">
<div class="card card-body" style="width: 320px;">
This is some placeholder content for a horizontal collapse. It's hidden by default and shown when triggered.
</div>
</div>
</div>
{{< /example >}}
## Multiple targets
A `<button>` or `<a>` can show and hide multiple elements by referencing them with a JQuery selector in its `href` or `data-target` attribute.
@@ -127,7 +150,7 @@ Be sure to add `aria-expanded` to the control element. This attribute explicitly
If your control element is targeting a single collapsible element i.e. the `data-target` attribute is pointing to an `id` selector you should add the `aria-controls` attribute to the control element, containing the `id` of the collapsible element. Modern screen readers and similar assistive technologies make use of this attribute to provide users with additional shortcuts to navigate directly to the collapsible element itself.
Note that Bootstrap's current implementation does not cover the various keyboard interactions described in the [WAI-ARIA Authoring Practices 1.1 accordion pattern](https://www.w3.org/TR/wai-aria-practices-1.1/#accordion) - you will need to include these yourself with custom JavaScript.
Note that Bootstrap's current implementation does not cover the various keyboard interactions described in the [ARIA Authoring Practices Guide accordion pattern](https://www.w3.org/WAI/ARIA/apg/patterns/accordion/) - you will need to include these yourself with custom JavaScript.
## Usage
@@ -16,7 +16,7 @@ If you're building our JavaScript from source, it [requires `util.js`]({{< docsr
## Accessibility
The [<abbr title="Web Accessibility Initiative">WAI</abbr> <abbr title="Accessible Rich Internet Applications">ARIA</abbr>](https://www.w3.org/TR/wai-aria/) standard defines an actual [`role="menu"` widget](https://www.w3.org/WAI/PF/aria/roles#menu), but this is specific to application-like menus which trigger actions or functions. <abbr title="Accessible Rich Internet Applications">ARIA</abbr> menus can only contain menu items, checkbox menu items, radio button menu items, radio button groups, and sub-menus.
The [<abbr title="Web Accessibility Initiative">WAI</abbr> <abbr title="Accessible Rich Internet Applications">ARIA</abbr>](https://www.w3.org/TR/wai-aria/) standard defines an actual [`role="menu"` widget](https://www.w3.org/TR/wai-aria/#menu), but this is specific to application-like menus which trigger actions or functions. <abbr title="Accessible Rich Internet Applications">ARIA</abbr> menus can only contain menu items, checkbox menu items, radio button menu items, radio button groups, and sub-menus.
Bootstrap's dropdowns, on the other hand, are designed to be generic and applicable to a variety of situations and markup structures. For instance, it is possible to create dropdowns that contain additional inputs and form controls, such as search fields or login forms. For this reason, Bootstrap does not expect (nor automatically add) any of the `role` and `aria-` attributes required for true <abbr title="Accessible Rich Internet Applications">ARIA</abbr> menus. Authors will have to include these more specific attributes themselves.
@@ -32,10 +32,10 @@ Any single `.btn` can be turned into a dropdown toggle with some markup changes.
{{< example >}}
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button class="btn btn-secondary dropdown-toggle" type="button" data-toggle="dropdown" aria-expanded="false">
Dropdown button
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
<div class="dropdown-menu">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
<a class="dropdown-item" href="#">Something else here</a>
@@ -47,11 +47,11 @@ And with `<a>` elements:
{{< example >}}
<div class="dropdown">
<a class="btn btn-secondary dropdown-toggle" href="#" role="button" id="dropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<a class="btn btn-secondary dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-expanded="false">
Dropdown link
</a>
<div class="dropdown-menu" aria-labelledby="dropdownMenuLink">
<div class="dropdown-menu">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
<a class="dropdown-item" href="#">Something else here</a>
@@ -63,7 +63,7 @@ The best part is you can do this with any button variant, too:
<div class="bd-example">
<div class="btn-group">
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Primary</button>
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-expanded="false">Primary</button>
<div class="dropdown-menu">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
@@ -73,7 +73,7 @@ The best part is you can do this with any button variant, too:
</div>
</div><!-- /btn-group -->
<div class="btn-group">
<button type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Secondary</button>
<button type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" aria-expanded="false">Secondary</button>
<div class="dropdown-menu">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
@@ -83,7 +83,7 @@ The best part is you can do this with any button variant, too:
</div>
</div><!-- /btn-group -->
<div class="btn-group">
<button type="button" class="btn btn-success dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Success</button>
<button type="button" class="btn btn-success dropdown-toggle" data-toggle="dropdown" aria-expanded="false">Success</button>
<div class="dropdown-menu">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
@@ -93,7 +93,7 @@ The best part is you can do this with any button variant, too:
</div>
</div><!-- /btn-group -->
<div class="btn-group">
<button type="button" class="btn btn-info dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Info</button>
<button type="button" class="btn btn-info dropdown-toggle" data-toggle="dropdown" aria-expanded="false">Info</button>
<div class="dropdown-menu">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
@@ -103,7 +103,7 @@ The best part is you can do this with any button variant, too:
</div>
</div><!-- /btn-group -->
<div class="btn-group">
<button type="button" class="btn btn-warning dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Warning</button>
<button type="button" class="btn btn-warning dropdown-toggle" data-toggle="dropdown" aria-expanded="false">Warning</button>
<div class="dropdown-menu">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
@@ -113,7 +113,7 @@ The best part is you can do this with any button variant, too:
</div>
</div><!-- /btn-group -->
<div class="btn-group">
<button type="button" class="btn btn-danger dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Danger</button>
<button type="button" class="btn btn-danger dropdown-toggle" data-toggle="dropdown" aria-expanded="false">Danger</button>
<div class="dropdown-menu">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
@@ -127,7 +127,7 @@ The best part is you can do this with any button variant, too:
```html
<!-- Example single danger button -->
<div class="btn-group">
<button type="button" class="btn btn-danger dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button type="button" class="btn btn-danger dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
Action
</button>
<div class="dropdown-menu">
@@ -149,7 +149,7 @@ We use this extra class to reduce the horizontal `padding` on either side of the
<div class="bd-example">
<div class="btn-group">
<button type="button" class="btn btn-primary">Primary</button>
<button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-expanded="false">
<span class="sr-only">Toggle Dropdown</span>
</button>
<div class="dropdown-menu">
@@ -162,7 +162,7 @@ We use this extra class to reduce the horizontal `padding` on either side of the
</div><!-- /btn-group -->
<div class="btn-group">
<button type="button" class="btn btn-secondary">Secondary</button>
<button type="button" class="btn btn-secondary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button type="button" class="btn btn-secondary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-expanded="false">
<span class="sr-only">Toggle Dropdown</span>
</button>
<div class="dropdown-menu">
@@ -175,7 +175,7 @@ We use this extra class to reduce the horizontal `padding` on either side of the
</div><!-- /btn-group -->
<div class="btn-group">
<button type="button" class="btn btn-success">Success</button>
<button type="button" class="btn btn-success dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button type="button" class="btn btn-success dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-expanded="false">
<span class="sr-only">Toggle Dropdown</span>
</button>
<div class="dropdown-menu">
@@ -188,7 +188,7 @@ We use this extra class to reduce the horizontal `padding` on either side of the
</div><!-- /btn-group -->
<div class="btn-group">
<button type="button" class="btn btn-info">Info</button>
<button type="button" class="btn btn-info dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button type="button" class="btn btn-info dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-expanded="false">
<span class="sr-only">Toggle Dropdown</span>
</button>
<div class="dropdown-menu">
@@ -201,7 +201,7 @@ We use this extra class to reduce the horizontal `padding` on either side of the
</div><!-- /btn-group -->
<div class="btn-group">
<button type="button" class="btn btn-warning">Warning</button>
<button type="button" class="btn btn-warning dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button type="button" class="btn btn-warning dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-expanded="false">
<span class="sr-only">Toggle Dropdown</span>
</button>
<div class="dropdown-menu">
@@ -214,7 +214,7 @@ We use this extra class to reduce the horizontal `padding` on either side of the
</div><!-- /btn-group -->
<div class="btn-group">
<button type="button" class="btn btn-danger">Danger</button>
<button type="button" class="btn btn-danger dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button type="button" class="btn btn-danger dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-expanded="false">
<span class="sr-only">Toggle Dropdown</span>
</button>
<div class="dropdown-menu">
@@ -231,7 +231,7 @@ We use this extra class to reduce the horizontal `padding` on either side of the
<!-- Example split danger button -->
<div class="btn-group">
<button type="button" class="btn btn-danger">Action</button>
<button type="button" class="btn btn-danger dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button type="button" class="btn btn-danger dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-expanded="false">
<span class="sr-only">Toggle Dropdown</span>
</button>
<div class="dropdown-menu">
@@ -250,7 +250,7 @@ Button dropdowns work with buttons of all sizes, including default and split dro
<div class="bd-example">
<div class="btn-group">
<button class="btn btn-secondary btn-lg dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button class="btn btn-secondary btn-lg dropdown-toggle" type="button" data-toggle="dropdown" aria-expanded="false">
Large button
</button>
<div class="dropdown-menu">
@@ -263,7 +263,7 @@ Button dropdowns work with buttons of all sizes, including default and split dro
</div>
<div class="btn-group">
<button type="button" class="btn btn-lg btn-secondary">Large split button</button>
<button type="button" class="btn btn-lg btn-secondary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button type="button" class="btn btn-lg btn-secondary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-expanded="false">
<span class="sr-only">Toggle Dropdown</span>
</button>
<div class="dropdown-menu">
@@ -279,7 +279,7 @@ Button dropdowns work with buttons of all sizes, including default and split dro
```html
<!-- Large button groups (default and split) -->
<div class="btn-group">
<button class="btn btn-secondary btn-lg dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button class="btn btn-secondary btn-lg dropdown-toggle" type="button" data-toggle="dropdown" aria-expanded="false">
Large button
</button>
<div class="dropdown-menu">
@@ -290,7 +290,7 @@ Button dropdowns work with buttons of all sizes, including default and split dro
<button class="btn btn-secondary btn-lg" type="button">
Large split button
</button>
<button type="button" class="btn btn-lg btn-secondary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button type="button" class="btn btn-lg btn-secondary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-expanded="false">
<span class="sr-only">Toggle Dropdown</span>
</button>
<div class="dropdown-menu">
@@ -301,7 +301,7 @@ Button dropdowns work with buttons of all sizes, including default and split dro
<div class="bd-example">
<div class="btn-group">
<button class="btn btn-secondary btn-sm dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button class="btn btn-secondary btn-sm dropdown-toggle" type="button" data-toggle="dropdown" aria-expanded="false">
Small button
</button>
<div class="dropdown-menu">
@@ -314,7 +314,7 @@ Button dropdowns work with buttons of all sizes, including default and split dro
</div>
<div class="btn-group">
<button type="button" class="btn btn-sm btn-secondary">Small split button</button>
<button type="button" class="btn btn-sm btn-secondary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button type="button" class="btn btn-sm btn-secondary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-expanded="false">
<span class="sr-only">Toggle Dropdown</span>
</button>
<div class="dropdown-menu">
@@ -330,7 +330,7 @@ Button dropdowns work with buttons of all sizes, including default and split dro
```html
<!-- Small button groups (default and split) -->
<div class="btn-group">
<button class="btn btn-secondary btn-sm dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button class="btn btn-secondary btn-sm dropdown-toggle" type="button" data-toggle="dropdown" aria-expanded="false">
Small button
</button>
<div class="dropdown-menu">
@@ -341,7 +341,7 @@ Button dropdowns work with buttons of all sizes, including default and split dro
<button class="btn btn-secondary btn-sm" type="button">
Small split button
</button>
<button type="button" class="btn btn-sm btn-secondary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button type="button" class="btn btn-sm btn-secondary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-expanded="false">
<span class="sr-only">Toggle Dropdown</span>
</button>
<div class="dropdown-menu">
@@ -358,7 +358,7 @@ Trigger dropdown menus above elements by adding `.dropup` to the parent element.
<div class="bd-example">
<div class="btn-group dropup">
<button type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
Dropup
</button>
<div class="dropdown-menu">
@@ -373,7 +373,7 @@ Trigger dropdown menus above elements by adding `.dropup` to the parent element.
<button type="button" class="btn btn-secondary">
Split dropup
</button>
<button type="button" class="btn btn-secondary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button type="button" class="btn btn-secondary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-expanded="false">
<span class="sr-only">Toggle Dropdown</span>
</button>
<div class="dropdown-menu">
@@ -389,7 +389,7 @@ Trigger dropdown menus above elements by adding `.dropup` to the parent element.
```html
<!-- Default dropup button -->
<div class="btn-group dropup">
<button type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
Dropup
</button>
<div class="dropdown-menu">
@@ -402,7 +402,7 @@ Trigger dropdown menus above elements by adding `.dropup` to the parent element.
<button type="button" class="btn btn-secondary">
Split dropup
</button>
<button type="button" class="btn btn-secondary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button type="button" class="btn btn-secondary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-expanded="false">
<span class="sr-only">Toggle Dropdown</span>
</button>
<div class="dropdown-menu">
@@ -417,7 +417,7 @@ Trigger dropdown menus at the right of the elements by adding `.dropright` to th
<div class="bd-example">
<div class="btn-group dropright">
<button type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
Dropright
</button>
<div class="dropdown-menu">
@@ -432,7 +432,7 @@ Trigger dropdown menus at the right of the elements by adding `.dropright` to th
<button type="button" class="btn btn-secondary">
Split dropright
</button>
<button type="button" class="btn btn-secondary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button type="button" class="btn btn-secondary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-expanded="false">
<span class="sr-only">Toggle Dropright</span>
</button>
<div class="dropdown-menu">
@@ -448,7 +448,7 @@ Trigger dropdown menus at the right of the elements by adding `.dropright` to th
```html
<!-- Default dropright button -->
<div class="btn-group dropright">
<button type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
Dropright
</button>
<div class="dropdown-menu">
@@ -461,7 +461,7 @@ Trigger dropdown menus at the right of the elements by adding `.dropright` to th
<button type="button" class="btn btn-secondary">
Split dropright
</button>
<button type="button" class="btn btn-secondary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button type="button" class="btn btn-secondary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-expanded="false">
<span class="sr-only">Toggle Dropright</span>
</button>
<div class="dropdown-menu">
@@ -476,7 +476,7 @@ Trigger dropdown menus at the left of the elements by adding `.dropleft` to the
<div class="bd-example">
<div class="btn-group dropleft">
<button type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
Dropleft
</button>
<div class="dropdown-menu">
@@ -488,8 +488,8 @@ Trigger dropdown menus at the left of the elements by adding `.dropleft` to the
</div>
</div>
<div class="btn-group">
<div class="btn-group dropleft" role="group">
<button type="button" class="btn btn-secondary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<div class="btn-group dropleft">
<button type="button" class="btn btn-secondary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-expanded="false">
<span class="sr-only">Toggle Dropleft</span>
</button>
<div class="dropdown-menu">
@@ -509,7 +509,7 @@ Trigger dropdown menus at the left of the elements by adding `.dropleft` to the
```html
<!-- Default dropleft button -->
<div class="btn-group dropleft">
<button type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
Dropleft
</button>
<div class="dropdown-menu">
@@ -519,8 +519,8 @@ Trigger dropdown menus at the left of the elements by adding `.dropleft` to the
<!-- Split dropleft button -->
<div class="btn-group">
<div class="btn-group dropleft" role="group">
<button type="button" class="btn btn-secondary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<div class="btn-group dropleft">
<button type="button" class="btn btn-secondary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-expanded="false">
<span class="sr-only">Toggle Dropleft</span>
</button>
<div class="dropdown-menu">
@@ -539,10 +539,10 @@ Historically dropdown menu contents *had* to be links, but that's no longer the
{{< example >}}
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenu2" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button class="btn btn-secondary dropdown-toggle" type="button" data-toggle="dropdown" aria-expanded="false">
Dropdown
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenu2">
<div class="dropdown-menu">
<button class="dropdown-item" type="button">Action</button>
<button class="dropdown-item" type="button">Another action</button>
<button class="dropdown-item" type="button">Something else here</button>
@@ -580,7 +580,7 @@ Add `.disabled` to items in the dropdown to **style them as disabled**.
{{< example >}}
<div class="dropdown-menu">
<a class="dropdown-item" href="#">Regular link</a>
<a class="dropdown-item disabled" href="#" tabindex="-1" aria-disabled="true">Disabled link</a>
<a class="dropdown-item disabled">Disabled link</a>
<a class="dropdown-item" href="#">Another link</a>
</div>
{{< /example >}}
@@ -595,7 +595,7 @@ By default, a dropdown menu is automatically positioned 100% from the top and al
{{< example >}}
<div class="btn-group">
<button type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
Right-aligned menu
</button>
<div class="dropdown-menu dropdown-menu-right">
@@ -614,7 +614,7 @@ To align **right** the dropdown menu with the given breakpoint or larger, add `.
{{< example >}}
<div class="btn-group">
<button type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" data-display="static" aria-haspopup="true" aria-expanded="false">
<button type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" data-display="static" aria-expanded="false">
Left-aligned but right aligned when large screen
</button>
<div class="dropdown-menu dropdown-menu-lg-right">
@@ -629,7 +629,7 @@ To align **left** the dropdown menu with the given breakpoint or larger, add `.d
{{< example >}}
<div class="btn-group">
<button type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" data-display="static" aria-haspopup="true" aria-expanded="false">
<button type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" data-display="static" aria-expanded="false">
Right-aligned but left aligned when large screen
</button>
<div class="dropdown-menu dropdown-menu-right dropdown-menu-lg-left">
@@ -745,10 +745,10 @@ Use `data-offset` or `data-reference` to change the location of the dropdown.
{{< example >}}
<div class="d-flex">
<div class="dropdown mr-1">
<button type="button" class="btn btn-secondary dropdown-toggle" id="dropdownMenuOffset" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" data-offset="10,20">
<button type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" aria-expanded="false" data-offset="10,20">
Offset
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenuOffset">
<div class="dropdown-menu">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
<a class="dropdown-item" href="#">Something else here</a>
@@ -756,10 +756,10 @@ Use `data-offset` or `data-reference` to change the location of the dropdown.
</div>
<div class="btn-group">
<button type="button" class="btn btn-secondary">Reference</button>
<button type="button" class="btn btn-secondary dropdown-toggle dropdown-toggle-split" id="dropdownMenuReference" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" data-reference="parent">
<button type="button" class="btn btn-secondary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-expanded="false" data-reference="parent">
<span class="sr-only">Toggle Dropdown</span>
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenuReference">
<div class="dropdown-menu">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
<a class="dropdown-item" href="#">Something else here</a>
@@ -784,10 +784,10 @@ Add `data-toggle="dropdown"` to a link or button to toggle a dropdown.
```html
<div class="dropdown">
<button id="dLabel" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button type="button" data-toggle="dropdown" aria-expanded="false">
Dropdown trigger
</button>
<div class="dropdown-menu" aria-labelledby="dLabel">
<div class="dropdown-menu">
...
</div>
</div>
@@ -192,7 +192,7 @@ Multiple add-ons are supported and can be mixed with checkbox and radio input ve
{{< example >}}
<div class="input-group mb-3">
<div class="input-group-prepend">
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Dropdown</button>
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-toggle="dropdown" aria-expanded="false">Dropdown</button>
<div class="dropdown-menu">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
@@ -207,7 +207,7 @@ Multiple add-ons are supported and can be mixed with checkbox and radio input ve
<div class="input-group">
<input type="text" class="form-control" aria-label="Text input with dropdown button">
<div class="input-group-append">
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Dropdown</button>
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-toggle="dropdown" aria-expanded="false">Dropdown</button>
<div class="dropdown-menu">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
@@ -225,7 +225,7 @@ Multiple add-ons are supported and can be mixed with checkbox and radio input ve
<div class="input-group mb-3">
<div class="input-group-prepend">
<button type="button" class="btn btn-outline-secondary">Action</button>
<button type="button" class="btn btn-outline-secondary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button type="button" class="btn btn-outline-secondary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-expanded="false">
<span class="sr-only">Toggle Dropdown</span>
</button>
<div class="dropdown-menu">
@@ -243,7 +243,7 @@ Multiple add-ons are supported and can be mixed with checkbox and radio input ve
<input type="text" class="form-control" aria-label="Text input with segmented dropdown button">
<div class="input-group-append">
<button type="button" class="btn btn-outline-secondary">Action</button>
<button type="button" class="btn btn-outline-secondary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button type="button" class="btn btn-outline-secondary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-expanded="false">
<span class="sr-only">Toggle Dropdown</span>
</button>
<div class="dropdown-menu">
@@ -62,7 +62,7 @@ Be sure to **not use the standard `.btn` classes here**.
<a href="#" class="list-group-item list-group-item-action">A second link item</a>
<a href="#" class="list-group-item list-group-item-action">A third link item</a>
<a href="#" class="list-group-item list-group-item-action">A fourth link item</a>
<a href="#" class="list-group-item list-group-item-action disabled" tabindex="-1" aria-disabled="true">A disabled link item</a>
<a class="list-group-item list-group-item-action disabled">A disabled link item</a>
</div>
{{< /example >}}
@@ -1,7 +1,7 @@
---
layout: docs
title: Navbar
description: Documentation and examples for Bootstrap's powerful, responsive navigation header, the navbar. Includes support for branding, navigation, and more, including support for our collapse plugin.
description: Documentation and examples for Bootstrap's powerful, responsive navigation header, the navbar. Includes support for branding, navigation, collapse plugin, and more.
group: components
toc: true
---
@@ -50,10 +50,10 @@ Here's an example of all the sub-components included in a responsive light-theme
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<a class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-expanded="false">
Dropdown
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<div class="dropdown-menu">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
<div class="dropdown-divider"></div>
@@ -61,7 +61,7 @@ Here's an example of all the sub-components included in a responsive light-theme
</div>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
<a class="nav-link disabled">Disabled</a>
</li>
</ul>
<form class="form-inline my-2 my-lg-0">
@@ -135,7 +135,7 @@ Active states—with `.active`—to indicate the current page can be applied dir
<a class="nav-link" href="#">Pricing</a>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
<a class="nav-link disabled">Disabled</a>
</li>
</ul>
</div>
@@ -155,7 +155,7 @@ And because we use classes for our navs, you can avoid the list-based approach e
<a class="nav-link active" href="#">Home <span class="sr-only">(current)</span></a>
<a class="nav-link" href="#">Features</a>
<a class="nav-link" href="#">Pricing</a>
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
<a class="nav-link disabled">Disabled</a>
</div>
</div>
</nav>
@@ -181,10 +181,10 @@ You can also use dropdowns in your navbar. Dropdown menus require a wrapping ele
<a class="nav-link" href="#">Pricing</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<a class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-expanded="false">
Dropdown link
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">
<div class="dropdown-menu">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
<a class="dropdown-item" href="#">Something else here</a>
@@ -461,10 +461,10 @@ Here's an example navbar using `.navbar-nav-scroll` with `style="max-height: 100
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarScrollingDropdown" role="button" data-toggle="dropdown" aria-expanded="false">
<a class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-expanded="false">
Link
</a>
<ul class="dropdown-menu" aria-labelledby="navbarScrollingDropdown">
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#">Action</a></li>
<li><a class="dropdown-item" href="#">Another action</a></li>
<li><hr class="dropdown-divider"></li>
@@ -472,7 +472,7 @@ Here's an example navbar using `.navbar-nav-scroll` with `style="max-height: 100
</ul>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Link</a>
<a class="nav-link disabled">Link</a>
</li>
</ul>
<form class="d-flex">
@@ -510,7 +510,7 @@ With no `.navbar-brand` shown at the smallest breakpoint:
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
<a class="nav-link disabled">Disabled</a>
</li>
</ul>
<form class="form-inline my-2 my-lg-0">
@@ -539,7 +539,7 @@ With a brand name shown on the left and toggler on the right:
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
<a class="nav-link disabled">Disabled</a>
</li>
</ul>
<form class="form-inline my-2 my-lg-0">
@@ -568,7 +568,7 @@ With a toggler on the left and brand name on the right:
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
<a class="nav-link disabled">Disabled</a>
</li>
</ul>
<form class="form-inline my-2 my-lg-0">
@@ -28,7 +28,7 @@ The base `.nav` component does not include any `.active` state. The following ex
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
<a class="nav-link disabled">Disabled</a>
</li>
</ul>
{{< /example >}}
@@ -40,7 +40,7 @@ Classes are used throughout, so your markup can be super flexible. Use `<ul>`s l
<a class="nav-link active" href="#">Active</a>
<a class="nav-link" href="#">Link</a>
<a class="nav-link" href="#">Link</a>
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
<a class="nav-link disabled">Disabled</a>
</nav>
{{< /example >}}
@@ -66,7 +66,7 @@ Centered with `.justify-content-center`:
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
<a class="nav-link disabled">Disabled</a>
</li>
</ul>
{{< /example >}}
@@ -85,7 +85,7 @@ Right-aligned with `.justify-content-end`:
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
<a class="nav-link disabled">Disabled</a>
</li>
</ul>
{{< /example >}}
@@ -106,7 +106,7 @@ Stack your navigation by changing the flex item direction with the `.flex-column
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
<a class="nav-link disabled">Disabled</a>
</li>
</ul>
{{< /example >}}
@@ -118,7 +118,7 @@ As always, vertical navigation is possible without `<ul>`s, too.
<a class="nav-link active" href="#">Active</a>
<a class="nav-link" href="#">Link</a>
<a class="nav-link" href="#">Link</a>
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
<a class="nav-link disabled">Disabled</a>
</nav>
{{< /example >}}
@@ -138,7 +138,7 @@ Takes the basic nav from above and adds the `.nav-tabs` class to generate a tabb
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
<a class="nav-link disabled">Disabled</a>
</li>
</ul>
{{< /example >}}
@@ -159,7 +159,7 @@ Take that same HTML, but use `.nav-pills` instead:
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
<a class="nav-link disabled">Disabled</a>
</li>
</ul>
{{< /example >}}
@@ -180,7 +180,7 @@ Force your `.nav`'s contents to extend the full available width one of two modif
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
<a class="nav-link disabled">Disabled</a>
</li>
</ul>
{{< /example >}}
@@ -192,7 +192,7 @@ When using a `<nav>`-based navigation, you can safely omit `.nav-item` as only `
<a class="nav-link active" href="#">Active</a>
<a class="nav-link" href="#">Much longer nav link</a>
<a class="nav-link" href="#">Link</a>
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
<a class="nav-link disabled">Disabled</a>
</nav>
{{< /example >}}
@@ -210,7 +210,7 @@ For equal-width elements, use `.nav-justified`. All horizontal space will be occ
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
<a class="nav-link disabled">Disabled</a>
</li>
</ul>
{{< /example >}}
@@ -222,7 +222,7 @@ Similar to the `.nav-fill` example using a `<nav>`-based navigation.
<a class="nav-link active" href="#">Active</a>
<a class="nav-link" href="#">Much longer nav link</a>
<a class="nav-link" href="#">Link</a>
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
<a class="nav-link disabled">Disabled</a>
</nav>
{{< /example >}}
@@ -235,7 +235,7 @@ If you need responsive nav variations, consider using a series of [flexbox utili
<a class="flex-sm-fill text-sm-center nav-link active" href="#">Active</a>
<a class="flex-sm-fill text-sm-center nav-link" href="#">Longer nav link</a>
<a class="flex-sm-fill text-sm-center nav-link" href="#">Link</a>
<a class="flex-sm-fill text-sm-center nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
<a class="flex-sm-fill text-sm-center nav-link disabled">Disabled</a>
</nav>
{{< /example >}}
@@ -243,7 +243,7 @@ If you need responsive nav variations, consider using a series of [flexbox utili
If you're using navs to provide a navigation bar, be sure to add a `role="navigation"` to the most logical parent container of the `<ul>`, or wrap a `<nav>` element around the whole navigation. Do not add the role to the `<ul>` itself, as this would prevent it from being announced as an actual list by assistive technologies.
Note that navigation bars, even if visually styled as tabs with the `.nav-tabs` class, should **not** be given `role="tablist"`, `role="tab"` or `role="tabpanel"` attributes. These are only appropriate for dynamic tabbed interfaces, as described in the [<abbr title="Web Accessibility Initiative">WAI</abbr> <abbr title="Accessible Rich Internet Applications">ARIA</abbr> Authoring Practices](https://www.w3.org/TR/wai-aria-practices/#tabpanel). See [JavaScript behavior](#javascript-behavior) for dynamic tabbed interfaces in this section for an example.
Note that navigation bars, even if visually styled as tabs with the `.nav-tabs` class, should **not** be given `role="tablist"`, `role="tab"` or `role="tabpanel"` attributes. These are only appropriate for dynamic tabbed interfaces, as described in the [ARIA Authoring Practices Guide tabs pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tabpanel/). See [JavaScript behavior](#javascript-behavior) for dynamic tabbed interfaces in this section for an example.
## Using dropdowns
@@ -257,7 +257,7 @@ Add dropdown menus with a little extra HTML and the [dropdowns JavaScript plugin
<a class="nav-link active" href="#">Active</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">Dropdown</a>
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-expanded="false">Dropdown</a>
<div class="dropdown-menu">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
@@ -270,7 +270,7 @@ Add dropdown menus with a little extra HTML and the [dropdowns JavaScript plugin
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
<a class="nav-link disabled">Disabled</a>
</li>
</ul>
{{< /example >}}
@@ -283,7 +283,7 @@ Add dropdown menus with a little extra HTML and the [dropdowns JavaScript plugin
<a class="nav-link active" href="#">Active</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">Dropdown</a>
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-expanded="false">Dropdown</a>
<div class="dropdown-menu">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
@@ -296,7 +296,7 @@ Add dropdown menus with a little extra HTML and the [dropdowns JavaScript plugin
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
<a class="nav-link disabled">Disabled</a>
</li>
</ul>
{{< /example >}}
@@ -307,20 +307,22 @@ Use the tab JavaScript plugin—include it individually or through the compiled
If you're building our JavaScript from source, it [requires `util.js`]({{< docsref "/getting-started/javascript#util" >}}).
Dynamic tabbed interfaces, as described in the [<abbr title="Web Accessibility Initiative">WAI</abbr> <abbr title="Accessible Rich Internet Applications">ARIA</abbr> Authoring Practices](https://www.w3.org/TR/wai-aria-practices/#tabpanel), require `role="tablist"`, `role="tab"`, `role="tabpanel"`, and additional `aria-` attributes in order to convey their structure, functionality and current state to users of assistive technologies (such as screen readers).
Dynamic tabbed interfaces, as described in the [ARIA Authoring Practices Guide tabs pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tabpanel/), require `role="tablist"`, `role="tab"`, `role="tabpanel"`, and additional `aria-` attributes in order to convey their structure, functionality and current state to users of assistive technologies (such as screen readers). As a best practice, we recommend using `<button>` elements for the tabs, as these are controls that trigger a dynamic change, rather than links that navigate to a new page or location.
Note that dynamic tabbed interfaces should <em>not</em> contain dropdown menus, as this causes both usability and accessibility issues. From a usability perspective, the fact that the currently displayed tab's trigger element is not immediately visible (as it's inside the closed dropdown menu) can cause confusion. From an accessibility point of view, there is currently no sensible way to map this sort of construct to a standard WAI ARIA pattern, meaning that it cannot be easily made understandable to users of assistive technologies.
{{< callout danger >}}
Note that the tab JavaScript plugin **does not** support tabbed interfaces that contain dropdown menus, as these cause both usability and accessibility issues. From a usability perspective, the fact that the currently displayed tab's trigger element is not immediately visible (as it's inside the closed dropdown menu) can cause confusion. From an accessibility point of view, there is currently no sensible way to map this sort of construct to a standard WAI ARIA pattern, meaning that it cannot be easily made understandable to users of assistive technologies.
{{< /callout >}}
<div class="bd-example bd-example-tabs">
<ul class="nav nav-tabs" id="myTab" role="tablist">
<li class="nav-item" role="presentation">
<a class="nav-link active" id="home-tab" data-toggle="tab" href="#home" role="tab" aria-controls="home" aria-selected="true">Home</a>
<button class="nav-link active" id="home-tab" data-toggle="tab" data-target="#home" type="button" role="tab" aria-controls="home" aria-selected="true">Home</button>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="profile-tab" data-toggle="tab" href="#profile" role="tab" aria-controls="profile" aria-selected="false">Profile</a>
<button class="nav-link" id="profile-tab" data-toggle="tab" data-target="#profile" type="button" role="tab" aria-controls="profile" aria-selected="false">Profile</button>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="contact-tab" data-toggle="tab" href="#contact" role="tab" aria-controls="contact" aria-selected="false">Contact</a>
<button class="nav-link" id="contact-tab" data-toggle="tab" data-target="#contact" type="button" role="tab" aria-controls="contact" aria-selected="false">Contact</button>
</li>
</ul>
<div class="tab-content" id="myTabContent">
@@ -339,13 +341,13 @@ Note that dynamic tabbed interfaces should <em>not</em> contain dropdown menus,
```html
<ul class="nav nav-tabs" id="myTab" role="tablist">
<li class="nav-item" role="presentation">
<a class="nav-link active" id="home-tab" data-toggle="tab" href="#home" role="tab" aria-controls="home" aria-selected="true">Home</a>
<button class="nav-link active" id="home-tab" data-toggle="tab" data-target="#home" type="button" role="tab" aria-controls="home" aria-selected="true">Home</button>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="profile-tab" data-toggle="tab" href="#profile" role="tab" aria-controls="profile" aria-selected="false">Profile</a>
<button class="nav-link" id="profile-tab" data-toggle="tab" data-target="#profile" type="button" role="tab" aria-controls="profile" aria-selected="false">Profile</button>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="contact-tab" data-toggle="tab" href="#contact" role="tab" aria-controls="contact" aria-selected="false">Contact</a>
<button class="nav-link" id="contact-tab" data-toggle="tab" data-target="#contact" type="button" role="tab" aria-controls="contact" aria-selected="false">Contact</button>
</li>
</ul>
<div class="tab-content" id="myTabContent">
@@ -360,9 +362,9 @@ To help fit your needs, this works with `<ul>`-based markup, as shown above, or
<div class="bd-example bd-example-tabs">
<nav>
<div class="nav nav-tabs" id="nav-tab" role="tablist">
<a class="nav-link active" id="nav-home-tab" data-toggle="tab" href="#nav-home" role="tab" aria-controls="nav-home" aria-selected="true">Home</a>
<a class="nav-link" id="nav-profile-tab" data-toggle="tab" href="#nav-profile" role="tab" aria-controls="nav-profile" aria-selected="false">Profile</a>
<a class="nav-link" id="nav-contact-tab" data-toggle="tab" href="#nav-contact" role="tab" aria-controls="nav-contact" aria-selected="false">Contact</a>
<button class="nav-link active" id="nav-home-tab" data-toggle="tab" data-target="#nav-home" type="button" role="tab" aria-controls="nav-home" aria-selected="true">Home</button>
<button class="nav-link" id="nav-profile-tab" data-toggle="tab" data-target="#nav-profile" type="button" role="tab" aria-controls="nav-profile" aria-selected="false">Profile</button>
<button class="nav-link" id="nav-contact-tab" data-toggle="tab" data-target="#nav-contact" type="button" role="tab" aria-controls="nav-contact" aria-selected="false">Contact</button>
</div>
</nav>
<div class="tab-content" id="nav-tabContent">
@@ -381,9 +383,9 @@ To help fit your needs, this works with `<ul>`-based markup, as shown above, or
```html
<nav>
<div class="nav nav-tabs" id="nav-tab" role="tablist">
<a class="nav-link active" id="nav-home-tab" data-toggle="tab" href="#nav-home" role="tab" aria-controls="nav-home" aria-selected="true">Home</a>
<a class="nav-link" id="nav-profile-tab" data-toggle="tab" href="#nav-profile" role="tab" aria-controls="nav-profile" aria-selected="false">Profile</a>
<a class="nav-link" id="nav-contact-tab" data-toggle="tab" href="#nav-contact" role="tab" aria-controls="nav-contact" aria-selected="false">Contact</a>
<button class="nav-link active" id="nav-home-tab" data-toggle="tab" data-target="#nav-home" type="button" role="tab" aria-controls="nav-home" aria-selected="true">Home</button>
<button class="nav-link" id="nav-profile-tab" data-toggle="tab" data-target="#nav-profile" type="button" role="tab" aria-controls="nav-profile" aria-selected="false">Profile</button>
<button class="nav-link" id="nav-contact-tab" data-toggle="tab" data-target="#nav-contact" type="button" role="tab" aria-controls="nav-contact" aria-selected="false">Contact</button>
</div>
</nav>
<div class="tab-content" id="nav-tabContent">
@@ -398,13 +400,13 @@ The tabs plugin also works with pills.
<div class="bd-example bd-example-tabs">
<ul class="nav nav-pills mb-3" id="pills-tab" role="tablist">
<li class="nav-item" role="presentation">
<a class="nav-link active" id="pills-home-tab" data-toggle="pill" href="#pills-home" role="tab" aria-controls="pills-home" aria-selected="true">Home</a>
<button class="nav-link active" id="pills-home-tab" data-toggle="pill" data-target="#pills-home" type="button" role="tab" aria-controls="pills-home" aria-selected="true">Home</button>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="pills-profile-tab" data-toggle="pill" href="#pills-profile" role="tab" aria-controls="pills-profile" aria-selected="false">Profile</a>
<button class="nav-link" id="pills-profile-tab" data-toggle="pill" data-target="#pills-profile" type="button" role="tab" aria-controls="pills-profile" aria-selected="false">Profile</button>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="pills-contact-tab" data-toggle="pill" href="#pills-contact" role="tab" aria-controls="pills-contact" aria-selected="false">Contact</a>
<button class="nav-link" id="pills-contact-tab" data-toggle="pill" data-target="#pills-contact" type="button" role="tab" aria-controls="pills-contact" aria-selected="false">Contact</button>
</li>
</ul>
<div class="tab-content" id="pills-tabContent">
@@ -423,13 +425,13 @@ The tabs plugin also works with pills.
```html
<ul class="nav nav-pills mb-3" id="pills-tab" role="tablist">
<li class="nav-item" role="presentation">
<a class="nav-link active" id="pills-home-tab" data-toggle="pill" href="#pills-home" role="tab" aria-controls="pills-home" aria-selected="true">Home</a>
<button class="nav-link active" id="pills-home-tab" data-toggle="pill" data-target="#pills-home" type="button" role="tab" aria-controls="pills-home" aria-selected="true">Home</button>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="pills-profile-tab" data-toggle="pill" href="#pills-profile" role="tab" aria-controls="pills-profile" aria-selected="false">Profile</a>
<button class="nav-link" id="pills-profile-tab" data-toggle="pill" data-target="#pills-profile" type="button" role="tab" aria-controls="pills-profile" aria-selected="false">Profile</button>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="pills-contact-tab" data-toggle="pill" href="#pills-contact" role="tab" aria-controls="pills-contact" aria-selected="false">Contact</a>
<button class="nav-link" id="pills-contact-tab" data-toggle="pill" data-target="#pills-contact" type="button" role="tab" aria-controls="pills-contact" aria-selected="false">Contact</button>
</li>
</ul>
<div class="tab-content" id="pills-tabContent">
@@ -445,10 +447,10 @@ And with vertical pills.
<div class="row">
<div class="col-3">
<div class="nav flex-column nav-pills" id="v-pills-tab" role="tablist" aria-orientation="vertical">
<a class="nav-link active" id="v-pills-home-tab" data-toggle="pill" href="#v-pills-home" role="tab" aria-controls="v-pills-home" aria-selected="true">Home</a>
<a class="nav-link" id="v-pills-profile-tab" data-toggle="pill" href="#v-pills-profile" role="tab" aria-controls="v-pills-profile" aria-selected="false">Profile</a>
<a class="nav-link" id="v-pills-messages-tab" data-toggle="pill" href="#v-pills-messages" role="tab" aria-controls="v-pills-messages" aria-selected="false">Messages</a>
<a class="nav-link" id="v-pills-settings-tab" data-toggle="pill" href="#v-pills-settings" role="tab" aria-controls="v-pills-settings" aria-selected="false">Settings</a>
<button class="nav-link active" id="v-pills-home-tab" data-toggle="pill" data-target="#v-pills-home" type="button" role="tab" aria-controls="v-pills-home" aria-selected="true">Home</button>
<button class="nav-link" id="v-pills-profile-tab" data-toggle="pill" data-target="#v-pills-profile" type="button" role="tab" aria-controls="v-pills-profile" aria-selected="false">Profile</button>
<button class="nav-link" id="v-pills-messages-tab" data-toggle="pill" data-target="#v-pills-messages" type="button" role="tab" aria-controls="v-pills-messages" aria-selected="false">Messages</button>
<button class="nav-link" id="v-pills-settings-tab" data-toggle="pill" data-target="#v-pills-settings" type="button" role="tab" aria-controls="v-pills-settings" aria-selected="false">Settings</button>
</div>
</div>
<div class="col-9">
@@ -474,10 +476,10 @@ And with vertical pills.
<div class="row">
<div class="col-3">
<div class="nav flex-column nav-pills" id="v-pills-tab" role="tablist" aria-orientation="vertical">
<a class="nav-link active" id="v-pills-home-tab" data-toggle="pill" href="#v-pills-home" role="tab" aria-controls="v-pills-home" aria-selected="true">Home</a>
<a class="nav-link" id="v-pills-profile-tab" data-toggle="pill" href="#v-pills-profile" role="tab" aria-controls="v-pills-profile" aria-selected="false">Profile</a>
<a class="nav-link" id="v-pills-messages-tab" data-toggle="pill" href="#v-pills-messages" role="tab" aria-controls="v-pills-messages" aria-selected="false">Messages</a>
<a class="nav-link" id="v-pills-settings-tab" data-toggle="pill" href="#v-pills-settings" role="tab" aria-controls="v-pills-settings" aria-selected="false">Settings</a>
<button class="nav-link active" id="v-pills-home-tab" data-toggle="pill" data-target="#v-pills-home" type="button" role="tab" aria-controls="v-pills-home" aria-selected="true">Home</button>
<button class="nav-link" id="v-pills-profile-tab" data-toggle="pill" data-target="#v-pills-profile" type="button" role="tab" aria-controls="v-pills-profile" aria-selected="false">Profile</button>
<button class="nav-link" id="v-pills-messages-tab" data-toggle="pill" data-target="#v-pills-messages" type="button" role="tab" aria-controls="v-pills-messages" aria-selected="false">Messages</button>
<button class="nav-link" id="v-pills-settings-tab" data-toggle="pill" data-target="#v-pills-settings" type="button" role="tab" aria-controls="v-pills-settings" aria-selected="false">Settings</button>
</div>
</div>
<div class="col-9">
@@ -499,16 +501,16 @@ You can activate a tab or pill navigation without writing any JavaScript by simp
<!-- Nav tabs -->
<ul class="nav nav-tabs" id="myTab" role="tablist">
<li class="nav-item" role="presentation">
<a class="nav-link active" id="home-tab" data-toggle="tab" href="#home" role="tab" aria-controls="home" aria-selected="true">Home</a>
<button class="nav-link active" id="home-tab" data-toggle="tab" data-target="#home" type="button" role="tab" aria-controls="home" aria-selected="true">Home</button>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="profile-tab" data-toggle="tab" href="#profile" role="tab" aria-controls="profile" aria-selected="false">Profile</a>
<button class="nav-link" id="profile-tab" data-toggle="tab" data-target="#profile" type="button" role="tab" aria-controls="profile" aria-selected="false">Profile</button>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="messages-tab" data-toggle="tab" href="#messages" role="tab" aria-controls="messages" aria-selected="false">Messages</a>
<button class="nav-link" id="messages-tab" data-toggle="tab" data-target="#messages" type="button" role="tab" aria-controls="messages" aria-selected="false">Messages</button>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="settings-tab" data-toggle="tab" href="#settings" role="tab" aria-controls="settings" aria-selected="false">Settings</a>
<button class="nav-link" id="settings-tab" data-toggle="tab" data-target="#settings" type="button" role="tab" aria-controls="settings" aria-selected="false">Settings</button>
</li>
</ul>
@@ -526,7 +528,7 @@ You can activate a tab or pill navigation without writing any JavaScript by simp
Enable tabbable tabs via JavaScript (each tab needs to be activated individually):
```js
$('#myTab a').on('click', function (event) {
$('#myTab button').on('click', function (event) {
event.preventDefault()
$(this).tab('show')
})
@@ -535,10 +537,10 @@ $('#myTab a').on('click', function (event) {
You can activate individual tabs in several ways:
```js
$('#myTab a[href="#profile"]').tab('show') // Select tab by name
$('#myTab li:first-child a').tab('show') // Select first tab
$('#myTab li:last-child a').tab('show') // Select last tab
$('#myTab li:nth-child(3) a').tab('show') // Select third tab
$('#myTab button[data-target="#profile"]').tab('show') // Select tab by name
$('#myTab li:first-child button').tab('show') // Select first tab
$('#myTab li:last-child button').tab('show') // Select last tab
$('#myTab li:nth-child(3) button').tab('show') // Select third tab
```
### Fade effect
@@ -562,21 +564,21 @@ To make tabs fade in, add `.fade` to each `.tab-pane`. The first tab pane must a
#### $().tab
Activates a tab element and content container. Tab should have either a `data-target` or an `href` targeting a container node in the DOM.
Activates a tab element and content container. Tab should have either a `data-target` or, if using a link, an `href` attribute targeting a container node in the DOM.
```html
<ul class="nav nav-tabs" id="myTab" role="tablist">
<li class="nav-item" role="presentation">
<a class="nav-link active" id="home-tab" data-toggle="tab" href="#home" role="tab" aria-controls="home" aria-selected="true">Home</a>
<button class="nav-link active" id="home-tab" data-toggle="tab" data-target="#home" type="button" role="tab" aria-controls="home" aria-selected="true">Home</button>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="profile-tab" data-toggle="tab" href="#profile" role="tab" aria-controls="profile" aria-selected="false">Profile</a>
<button class="nav-link" id="profile-tab" data-toggle="tab" data-target="#profile" type="button" role="tab" aria-controls="profile" aria-selected="false">Profile</button>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="messages-tab" data-toggle="tab" href="#messages" role="tab" aria-controls="messages" aria-selected="false">Messages</a>
<button class="nav-link" id="messages-tab" data-toggle="tab" data-target="#messages" type="button" role="tab" aria-controls="messages" aria-selected="false">Messages</button>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="settings-tab" data-toggle="tab" href="#settings" role="tab" aria-controls="settings" aria-selected="false">Settings</a>
<button class="nav-link" id="settings-tab" data-toggle="tab" data-target="#settings" type="button" role="tab" aria-controls="settings" aria-selected="false">Settings</button>
</li>
</ul>
@@ -589,7 +591,7 @@ Activates a tab element and content container. Tab should have either a `data-ta
<script>
$(function () {
$('#myTab li:last-child a').tab('show')
$('#myTab li:last-child button').tab('show')
})
</script>
```
@@ -645,7 +647,7 @@ If no tab was already active, then the `hide.bs.tab` and `hidden.bs.tab` events
</table>
```js
$('a[data-toggle="tab"]').on('shown.bs.tab', function (event) {
$('button[data-toggle="tab"]').on('shown.bs.tab', function (event) {
event.target // newly activated tab
event.relatedTarget // previous active tab
})
@@ -58,7 +58,7 @@ While the `.disabled` class uses `pointer-events: none` to _try_ to disable the
<nav aria-label="...">
<ul class="pagination">
<li class="page-item disabled">
<a class="page-link" href="#" tabindex="-1" aria-disabled="true">Previous</a>
<a class="page-link">Previous</a>
</li>
<li class="page-item"><a class="page-link" href="#">1</a></li>
<li class="page-item active" aria-current="page">
@@ -128,7 +128,7 @@ Change the alignment of pagination components with [flexbox utilities]({{< docsr
<nav aria-label="Page navigation example">
<ul class="pagination justify-content-center">
<li class="page-item disabled">
<a class="page-link" href="#" tabindex="-1" aria-disabled="true">Previous</a>
<a class="page-link">Previous</a>
</li>
<li class="page-item"><a class="page-link" href="#">1</a></li>
<li class="page-item"><a class="page-link" href="#">2</a></li>
@@ -144,7 +144,7 @@ Change the alignment of pagination components with [flexbox utilities]({{< docsr
<nav aria-label="Page navigation example">
<ul class="pagination justify-content-end">
<li class="page-item disabled">
<a class="page-link" href="#" tabindex="-1" aria-disabled="true">Previous</a>
<a class="page-link">Previous</a>
</li>
<li class="page-item"><a class="page-link" href="#">1</a></li>
<li class="page-item"><a class="page-link" href="#">2</a></li>
@@ -22,6 +22,10 @@ Things to know when using the popover plugin:
- Popovers must be hidden before their corresponding elements have been removed from the DOM.
- Popovers can be triggered thanks to an element inside a shadow DOM.
{{< callout info >}}
{{< partial "callout-info-sanitizer.md" >}}
{{< /callout >}}
{{< callout info >}}
{{< partial "callout-info-prefersreducedmotion.md" >}}
{{< /callout >}}
@@ -268,7 +272,7 @@ Note that for security reasons the `sanitize`, `sanitizeFn` and `whiteList` opti
<td>sanitize</td>
<td>boolean</td>
<td>true</td>
<td>Enable or disable the sanitization. If activated <code>'template'</code>, <code>'content'</code> and <code>'title'</code> options will be sanitized.</td>
<td>Enable or disable the sanitization. If activated <code>'template'</code>, <code>'content'</code> and <code>'title'</code> options will be sanitized. See the <a href="{{< docsref "/getting-started/javascript#sanitizer" >}}">sanitizer section in our JavaScript documentation</a>.</td>
</tr>
<tr>
<td>whiteList</td>
@@ -33,7 +33,7 @@ Scroll the area below the navbar and watch the active class change. The dropdown
<a class="nav-link" href="#mdo">@mdo</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">Dropdown</a>
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-expanded="false">Dropdown</a>
<div class="dropdown-menu">
<a class="dropdown-item" href="#one">one</a>
<a class="dropdown-item" href="#two">two</a>
@@ -69,7 +69,7 @@ Scroll the area below the navbar and watch the active class change. The dropdown
<a class="nav-link" href="#mdo">@mdo</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">Dropdown</a>
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-expanded="false">Dropdown</a>
<div class="dropdown-menu">
<a class="dropdown-item" href="#one">one</a>
<a class="dropdown-item" href="#two">two</a>
@@ -47,7 +47,7 @@ Toasts are as flexible as you need and have very little required markup. At a mi
### Live
Click the button the below to show as toast (positioning with our utilities in the lower right corner) that has been hidden by default with `.hide`.
Click the button below to show a toast (positioned with our utilities in the lower right corner) that has been hidden by default with `.hide`.
<div class="position-fixed bottom-0 right-0 p-3" style="z-index: 5; right: 0; bottom: 0;">
<div id="liveToast" class="toast hide" role="alert" aria-live="assertive" aria-atomic="true" data-delay="2000">
@@ -91,7 +91,7 @@ Click the button the below to show as toast (positioning with our utilities in t
### Translucent
Toasts are slightly translucent, too, so they blend over whatever they might appear over. For browsers that support the `backdrop-filter` CSS property, we'll also attempt to blur the elements under a toast.
Toasts are slightly translucent to blend in with what's below them.
{{< example class="bg-dark" >}}
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
@@ -229,13 +229,13 @@ You can also get fancy with flexbox utilities to align toasts horizontally and/o
## Accessibility
Toasts are intended to be small interruptions to your visitors or users, so to help those with screen readers and similar assistive technologies, you should wrap your toasts in an [`aria-live` region](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions). Changes to live regions (such as injecting/updating a toast component) are automatically announced by screen readers without needing to move the user's focus or otherwise interrupt the user. Additionally, include `aria-atomic="true"` to ensure that the entire toast is always announced as a single (atomic) unit, rather than announcing what was changed (which could lead to problems if you only update part of the toast's content, or if displaying the same toast content at a later point in time). If the information needed is important for the process, e.g. for a list of errors in a form, then use the [alert component]({{< docsref "/components/alerts" >}}) instead of toast.
Toasts are intended to be small interruptions to your visitors or users, so to help those with screen readers and similar assistive technologies, you should wrap your toasts in an [`aria-live` region](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions). Changes to live regions (such as injecting/updating a toast component) are automatically announced by screen readers without needing to move the user's focus or otherwise interrupt the user. Additionally, include `aria-atomic="true"` to ensure that the entire toast is always announced as a single (atomic) unit, rather than just announcing what was changed (which could lead to problems if you only update part of the toast's content, or if displaying the same toast content at a later point in time). If the information needed is important for the process, e.g. for a list of errors in a form, then use the [alert component]({{< docsref "/components/alerts" >}}) instead of toast.
Note that the live region needs to be present in the markup *before* the toast is generated or updated. If you dynamically generate both at the same time and inject them into the page, they will generally not be announced by assistive technologies.
You also need to adapt the `role` and `aria-live` level depending on the content. If it's an important message like an error, use `role="alert" aria-live="assertive"`, otherwise use `role="status" aria-live="polite"` attributes.
As the content you're displaying changes, be sure to update the [`delay` timeout](#options) to ensure people have enough time to read the toast.
As the content you're displaying changes, be sure to update the [`delay` timeout](#options) so that users have enough time to read the toast.
```html
<div class="toast" role="alert" aria-live="polite" aria-atomic="true" data-delay="10000">
@@ -261,6 +261,8 @@ When using `autohide: false`, you must add a close button to allow users to dism
</div>
{{< /example >}}
While technically it's possible to add focusable/actionable controls (such as additional buttons or links) in your toast, you should avoid doing this for autohiding toasts. Even if you give the toast a long [`delay` timeout](#options), keyboard and assistive technology users may find it difficult to reach the toast in time to take action (since toasts don't receive focus when they are displayed). If you absolutely must have further controls, we recommend using a toast with `autohide: false`.
## JavaScript behavior
### Usage
@@ -21,6 +21,10 @@ Things to know when using the tooltip plugin:
- Tooltips must be hidden before their corresponding elements have been removed from the DOM.
- Tooltips can be triggered thanks to an element inside a shadow DOM.
{{< callout info >}}
{{< partial "callout-info-sanitizer.md" >}}
{{< /callout >}}
{{< callout info >}}
{{< partial "callout-info-prefersreducedmotion.md" >}}
{{< /callout >}}
@@ -269,7 +273,7 @@ Note that for security reasons the `sanitize`, `sanitizeFn` and `whiteList` opti
<td>sanitize</td>
<td>boolean</td>
<td>true</td>
<td>Enable or disable the sanitization. If activated <code>'template'</code> and <code>'title'</code> options will be sanitized.</td>
<td>Enable or disable the sanitization. If activated <code>'template'</code> and <code>'title'</code> options will be sanitized. See the <a href="{{< docsref "/getting-started/javascript#sanitizer" >}}">sanitizer section in our JavaScript documentation</a>.</td>
</tr>
<tr>
<td>whiteList</td>
@@ -53,7 +53,7 @@ Align images with the [helper float classes]({{< docsref "/utilities/float" >}})
If you are using the `<picture>` element to specify multiple `<source>` elements for a specific `<img>`, make sure to add the `.img-*` classes to the `<img>` and not to the `<picture>` tag.
```html
<picture>
<picture>
<source srcset="..." type="image/svg+xml">
<img src="..." class="img-fluid img-thumbnail" alt="...">
</picture>
@@ -52,7 +52,7 @@ $font-family-sans-serif:
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" !default;
```
Note that because the font stack includes emoji fonts, many common symbol/dingbat unicode characters will be rendered as multi-colored pictographs. Their appearance will vary, depending on the style used in the browser/platform's native emoji font, and they won't be affected by any CSS `color` styles.
Note that because the font stack includes emoji fonts, many common symbol/dingbat Unicode characters will be rendered as multi-colored pictographs. Their appearance will vary, depending on the style used in the browser/platform's native emoji font, and they won't be affected by any CSS `color` styles.
This `font-family` is applied to the `<body>` and automatically inherited globally throughout Bootstrap. To switch the global `font-family`, update `$font-family-base` and recompile Bootstrap.
@@ -201,7 +201,7 @@ Use text utilities as needed to change the alignment of your blockquote.
{{< example >}}
<blockquote class="blockquote text-center">
<p class="mb-0">>A well-known quote, contained in a blockquote element.</p>
<p class="mb-0">A well-known quote, contained in a blockquote element.</p>
<footer class="blockquote-footer">Someone famous in <cite title="Source Title">Source Title</cite></footer>
</blockquote>
{{< /example >}}
@@ -283,4 +283,4 @@ Align terms and descriptions horizontally by using our grid system's predefined
As of v4.3.0, Bootstrap ships with the option to enable responsive font sizes, allowing text to scale more naturally across device and viewport sizes. <abbr title="Responsive font sizes">RFS</abbr> can be enabled by changing the `$enable-responsive-font-sizes` Sass variable to `true` and recompiling Bootstrap.
To support <abbr title="Responsive font sizes">RFS</abbr>, we use a Sass mixin to replace our normal `font-size` properties. Responsive font sizes will be compiled into `calc()` functions with a mix of `rem` and viewport units to enable the responsive scaling behavior. More about <abbr title="Responsive font sizes">RFS</abbr> and its configuration can be found on its [GitHub repository](https://github.com/twbs/rfs/tree/v8.0.4).
To support <abbr title="Responsive font sizes">RFS</abbr>, we use a Sass mixin to replace our normal `font-size` properties. Responsive font sizes will be compiled into `calc()` functions with a mix of `rem` and viewport units to enable the responsive scaling behavior. More about <abbr title="Responsive font sizes">RFS</abbr> and its configuration can be found on its [GitHub repository](https://github.com/twbs/rfs/tree/v8.1.0).
@@ -606,6 +606,7 @@ Responsive variations also exist for `align-content`.
- `.align-content{{ .abbr }}-start`
- `.align-content{{ .abbr }}-end`
- `.align-content{{ .abbr }}-center`
- `.align-content{{ .abbr }}-between`
- `.align-content{{ .abbr }}-around`
- `.align-content{{ .abbr }}-stretch`
{{- end -}}
+48 -124
View File
@@ -10,7 +10,6 @@
.bd-content > h4[id] {
pointer-events: none;
}
.bd-content > h2[id]::before,
.bd-content > h3[id]::before,
.bd-content > h4[id]::before {
@@ -19,12 +18,10 @@
margin-top: -6rem;
content: "";
}
.bd-content > .table {
max-width: 100%;
margin-bottom: 1.5rem;
}
@media (max-width: 991.98px) {
.bd-content > .table {
display: block;
@@ -34,17 +31,14 @@
border: 0;
}
}
.bd-content > .table th:first-child,
.bd-content > .table td:first-child {
padding-left: 0;
}
.bd-content > .table th:not(:last-child),
.bd-content > .table td:not(:last-child) {
padding-right: 1.5rem;
}
.bd-content > .table td:first-child > code {
white-space: nowrap;
}
@@ -57,18 +51,16 @@
.bd-content > h2:not(:first-child) {
margin-top: 3rem;
}
.bd-content > h3 {
margin-top: 1.5rem;
}
.bd-content > ul li,
.bd-content > ol li {
margin-bottom: .25rem;
margin-bottom: 0.25rem;
}
.bd-title {
margin-bottom: .5rem;
margin-bottom: 0.5rem;
font-size: 3rem;
}
@@ -93,13 +85,12 @@
background-color: #563d7c;
outline: 0;
}
.skippy:hover {
color: #fff;
}
.skippy-text {
padding: .5em;
padding: 0.5em;
outline: 1px dotted;
}
@@ -114,7 +105,6 @@
border-bottom: 1px solid #dee2e6;
}
}
.sidebar .dropdown-menu .dropdown-item {
white-space: normal;
text-transform: capitalize;
@@ -136,10 +126,9 @@
align-items: center;
color: #212529;
text-decoration: none;
padding: .3rem 0.5rem;
padding: 0.3rem 0.5rem;
border-top: solid 1px #dee2e6;
}
.docs-sidebar a.dropdown-toggle::after {
margin-left: auto;
font-size: 14px;
@@ -165,7 +154,6 @@
border-bottom: solid 1px #dee2e6;
}
}
.docs-sidebar .active > a {
background: #dee2e6;
font-weight: bold;
@@ -190,46 +178,38 @@
padding-top: 1rem;
border-left: 1px solid #dee2e6;
}
#TableOfContents > ul ul {
padding-left: 1rem;
}
#TableOfContents > ul li {
display: block;
}
#TableOfContents > ul li a {
display: block;
padding: .125rem 1.5rem;
padding: 0.125rem 1.5rem;
color: #212529;
}
#TableOfContents > ul li a:hover {
color: #007bff;
text-decoration: none;
}
.bd-toc nav {
padding-top: .125em;
padding-bottom: .125em;
padding-top: 0.125em;
padding-bottom: 0.125em;
border-left: 0.25em solid #e9ecef;
}
.bd-toc nav > ul {
padding-left: .5rem;
padding-left: 0.5rem;
margin-bottom: 0;
}
.bd-toc nav > ul > li {
display: block;
}
.bd-toc nav li ul li {
margin-bottom: .25rem;
margin-bottom: 0.25rem;
list-style-type: disc;
}
.bd-toc nav a code {
font: inherit;
}
@@ -238,16 +218,13 @@
font-size: 0.875rem;
color: #63707c;
}
.bd-footer a {
font-weight: 600;
color: #495057;
}
.bd-footer a:hover, .bd-footer a:focus {
color: #007bff;
}
.bd-footer p {
margin-bottom: 0;
}
@@ -256,23 +233,20 @@
padding-left: 0;
margin-bottom: 1rem;
}
.bd-footer-links li {
display: inline-block;
}
.bd-footer-links li + li {
margin-left: 1rem;
}
.bd-example-row .row > .col,
.bd-example-row .row > [class^="col-"] {
padding-top: .75rem;
padding-bottom: .75rem;
.bd-example-row .row > [class^=col-] {
padding-top: 0.75rem;
padding-bottom: 0.75rem;
background-color: rgba(86, 61, 124, 0.15);
border: 1px solid rgba(86, 61, 124, 0.2);
}
.bd-example-row .row + .row {
margin-top: 1rem;
}
@@ -287,9 +261,9 @@
border: 1px solid rgba(86, 61, 124, 0.15);
}
.bd-example-responsive-containers [class^="container"] {
padding-top: .75rem;
padding-bottom: .75rem;
.bd-example-responsive-containers [class^=container] {
padding-top: 0.75rem;
padding-bottom: 0.75rem;
background-color: rgba(86, 61, 124, 0.15);
border: 1px solid rgba(86, 61, 124, 0.2);
}
@@ -316,18 +290,16 @@
padding-right: 15px;
padding-left: 15px;
}
@media (min-width: 576px) {
.example-content-main {
flex: 0 0 50%;
max-width: 50%;
}
}
@media (min-width: 992px) {
.example-content-main {
flex: 0 0 66.666667%;
max-width: 66.666667%;
flex: 0 0 66.66666667%;
max-width: 66.66666667%;
}
}
@@ -337,18 +309,16 @@
padding-right: 15px;
padding-left: 15px;
}
@media (min-width: 576px) {
.example-content-secondary {
flex: 0 0 50%;
max-width: 50%;
}
}
@media (min-width: 992px) {
.example-content-secondary {
flex: 0 0 33.333333%;
max-width: 33.333333%;
flex: 0 0 33.33333333%;
max-width: 33.33333333%;
}
}
@@ -357,44 +327,37 @@
padding: 1rem;
margin: 1rem -15px 0;
border: solid #f8f9fa;
border-width: .2rem 0 0;
border-width: 0.2rem 0 0;
}
.bd-example::after {
display: block;
clear: both;
content: "";
}
@media (min-width: 576px) {
.bd-example {
padding: 1.5rem;
margin-right: 0;
margin-left: 0;
border-width: .2rem;
border-width: 0.2rem;
}
}
.bd-example + .highlight,
.bd-example + .clipboard + .highlight {
margin-top: 0;
}
.bd-example + p {
margin-top: 2rem;
}
.bd-example > .form-control + .form-control {
margin-top: .5rem;
margin-top: 0.5rem;
}
.bd-example > .nav + .nav,
.bd-example > .alert + .alert,
.bd-example > .navbar + .navbar,
.bd-example > .progress + .progress {
margin-top: 1rem;
}
.bd-example > .dropdown-menu {
position: static;
display: block;
@@ -402,24 +365,23 @@
.bd-example > svg + svg,
.bd-example > img + img {
margin-left: .5rem;
margin-left: 0.5rem;
}
.bd-example > .btn,
.bd-example > .btn-group {
margin-top: .25rem;
margin-bottom: .25rem;
margin-top: 0.25rem;
margin-bottom: 0.25rem;
}
.bd-example > .btn-toolbar + .btn-toolbar {
margin-top: .5rem;
margin-top: 0.5rem;
}
.bd-example > .list-group {
max-width: 400px;
}
.bd-example > [class*="list-group-horizontal"] {
.bd-example > [class*=list-group-horizontal] {
max-width: 100%;
}
@@ -428,12 +390,10 @@
position: static;
margin: -1rem -1rem 1rem;
}
.bd-example .fixed-bottom {
position: static;
margin: 1rem -1rem -1rem;
}
@media (min-width: 576px) {
.bd-example .fixed-top,
.bd-example .sticky-top {
@@ -445,14 +405,13 @@
}
.bd-example .pagination {
margin-top: .5rem;
margin-bottom: .5rem;
margin-top: 0.5rem;
margin-bottom: 0.5rem;
}
.bd-example-modal {
background-color: #fafafa;
}
.bd-example-modal .modal {
position: static;
display: block;
@@ -465,7 +424,7 @@
.scrollspy-example {
position: relative;
height: 200px;
margin-top: .5rem;
margin-top: 0.5rem;
overflow: auto;
}
@@ -475,15 +434,15 @@
overflow: auto;
}
.bd-example-border-utils [class^="border"] {
.bd-example-border-utils [class^=border] {
display: inline-block;
width: 5rem;
height: 5rem;
margin: .25rem;
margin: 0.25rem;
background-color: #f5f5f5;
}
.bd-example-border-utils-0 [class^="border"] {
.bd-example-border-utils-0 [class^=border] {
border: 1px solid #dee2e6;
}
@@ -494,7 +453,6 @@
background-color: #f8f9fa;
-ms-overflow-style: -ms-autohiding-scrollbar;
}
@media (min-width: 576px) {
.highlight {
padding: 1.5rem;
@@ -505,7 +463,6 @@
margin-right: -15px;
margin-left: -15px;
}
@media (min-width: 576px) {
.bd-content .highlight {
margin-right: 0;
@@ -520,7 +477,6 @@
background-color: transparent;
border: 0;
}
.highlight pre code {
font-size: inherit;
color: #212529;
@@ -531,30 +487,25 @@
margin-top: 1.25rem;
margin-bottom: 1.25rem;
border: 1px solid #e9ecef;
border-left-width: .25rem;
border-left-width: 0.25rem;
border-radius: 0.25rem;
}
.bd-callout h4 {
margin-bottom: .25rem;
margin-bottom: 0.25rem;
}
.bd-callout p:last-child {
margin-bottom: 0;
}
.bd-callout code {
border-radius: 0.25rem;
}
.bd-callout + .bd-callout {
margin-top: -.25rem;
margin-top: -0.25rem;
}
.bd-callout-info {
border-left-color: #5bc0de;
}
.bd-callout-info h4 {
color: #5bc0de;
}
@@ -562,7 +513,6 @@
.bd-callout-warning {
border-left-color: #f0ad4e;
}
.bd-callout-warning h4 {
color: #f0ad4e;
}
@@ -570,7 +520,6 @@
.bd-callout-danger {
border-left-color: #d9534f;
}
.bd-callout-danger h4 {
color: #d9534f;
}
@@ -690,11 +639,9 @@
display: none;
float: right;
}
.bd-clipboard + .highlight {
margin-top: 0;
}
@media (min-width: 768px) {
.bd-clipboard {
display: block;
@@ -703,18 +650,17 @@
.btn-clipboard {
position: absolute;
top: .5rem;
right: .5rem;
top: 0.5rem;
right: 0.5rem;
z-index: 10;
display: block;
padding: .25rem .5rem;
padding: 0.25rem 0.5rem;
font-size: 0.75em;
color: #343a40;
background-color: transparent;
border: 0;
border-radius: 0.25rem;
}
.btn-clipboard:hover {
color: #fff;
background-color: #007bff;
@@ -740,9 +686,7 @@
/* LiteralStringDelimiter */
.chroma .dl {
color: #4070a0;
}
/* NEW */
} /* NEW */
/* Comment */
.chroma .c {
color: #727272;
@@ -757,9 +701,7 @@
.chroma .ch {
font-style: italic;
color: #60a0b0;
}
/* NEW */
} /* NEW */
/* CommentMultiline */
.chroma .cm {
color: #727272;
@@ -773,9 +715,7 @@
/* CommentPreprocFile */
.chroma .cpf {
color: #007020;
}
/* NEW */
} /* NEW */
/* CommentSpecial */
.chroma .cs {
color: #727272;
@@ -806,9 +746,7 @@
/* GenericUnderline */
.chroma .gl {
text-decoration: underline;
}
/* NEW */
} /* NEW */
/* GenericOutput */
.chroma .go {
color: #aaa;
@@ -827,9 +765,7 @@
/* GenericStrong */
.chroma .gs {
font-weight: 700;
}
/* NEW */
} /* NEW */
/* GenericTraceback */
.chroma .gt {
color: #9c6;
@@ -893,9 +829,7 @@
/* LiteralNumberBin */
.chroma .mb {
color: #40a070;
}
/* NEW */
} /* NEW */
/* LiteralNumberHex */
.chroma .mh {
color: #c24f19;
@@ -999,9 +933,7 @@
/* LiteralStringAffix */
.chroma .sa {
color: #4070a0;
}
/* NEW */
} /* NEW */
/* LiteralStringBacktick */
.chroma .sb {
color: #c30;
@@ -1059,12 +991,10 @@
content: "$ ";
user-select: none;
}
.chroma .language-bash .m,
.chroma .language-sh .m {
color: inherit;
}
.chroma .language-powershell::before {
color: #009;
content: "PM> ";
@@ -1076,13 +1006,11 @@
color: rgba(0, 123, 255, 0.5);
transition: color 0.15s ease-in-out;
}
@media (prefers-reduced-motion: reduce) {
.anchorjs-link {
transition: none;
}
}
.anchorjs-link:hover {
color: #007bff;
text-decoration: none;
@@ -1092,26 +1020,22 @@
padding-left: 0;
padding-right: 0;
}
.path-admin-tool-componentlibrary #page.drawers .main-inner {
margin: 0;
padding: 0;
border-radius: 0;
}
.path-admin-tool-componentlibrary #page.drawers div[role="main"] {
.path-admin-tool-componentlibrary #page.drawers div[role=main] {
padding: 0;
}
.path-admin-tool-componentlibrary #page-header,
.path-admin-tool-componentlibrary #maincontent,
.path-admin-tool-componentlibrary .secondarynavigation {
display: none;
}
.path-admin-tool-componentlibrary #region-main {
padding: 0;
border: 0;
}
/*# sourceMappingURL=docs.css.map */
/*# sourceMappingURL=docs.css.map */
File diff suppressed because one or more lines are too long
@@ -82,7 +82,7 @@
.bd-example {
position: relative;
padding: 1rem;
margin: 1rem (-$grid-gutter-width / 2) 0;
margin: 1rem (-$grid-gutter-width * 0.5) 0;
border: solid $gray-100;
border-width: .2rem 0 0;
@include clearfix();
@@ -248,8 +248,8 @@
}
.bd-content .highlight {
margin-right: (-$grid-gutter-width / 2);
margin-left: (-$grid-gutter-width / 2);
margin-right: (-$grid-gutter-width * 0.5);
margin-left: (-$grid-gutter-width * 0.5);
@include media-breakpoint-up(sm) {
margin-right: 0;
@@ -1,16 +1,26 @@
- name: primary
- name: blue
hex: "#007bff"
- name: secondary
hex: "#868e96"
- name: success
hex: "#28a745"
- name: danger
- name: indigo
hex: "#6610f2"
- name: purple
hex: "#6f42c1"
- name: pink
hex: "#e83e8c"
- name: red
hex: "#dc3545"
- name: warning
- name: orange
hex: "#fd7e14"
- name: yellow
hex: "#ffc107"
- name: info
- name: green
hex: "#28a745"
- name: teal
hex: "#20c997"
- name: cyan
hex: "#17a2b8"
- name: light
hex: "#f8f9fa"
- name: dark
- name: white
hex: "#fff"
- name: gray
hex: "#6c757d"
- name: gray-dark
hex: "#343a40"
@@ -9,7 +9,7 @@
- name: 500
hex: "#adb5bd"
- name: 600
hex: "#868e96"
hex: "#6c757d"
- name: 700
hex: "#495057"
- name: 800
@@ -1,7 +1,7 @@
- name: primary
hex: "#007bff"
- name: secondary
hex: "#868e96"
hex: "#6c757d"
- name: success
hex: "#28a745"
- name: danger
@@ -1,5 +1,5 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="{{ .Page.Params.description | default .Site.Params.description | markdownify }}">
<meta name="author" content="{{ .Site.Params.authors }}">
<meta name="generator" content="Hugo {{ hugo.Version }}">
@@ -1,6 +1,5 @@
{{- /*
Usage: `callout "type"`,
where type is one of info (default), danger, warning
Usage: `callout "type"`, where `type` is one of info (default), danger, or warning
*/ -}}
{{- $css_class := .Get 0 | default "info" -}}
@@ -1,27 +1,28 @@
{{- /*
Usage: `example [args]`
Usage: `example args`
`args` are optional and can be one of the following:
id: the `div`'s id - default: ""
class: any extra class(es) to be added to the `div` - default ""
show_preview: if the preview should be output in the HTML - default: `true`
show_markup: if the markup should be output in the HTML - default: `true`
`args` are all optional and can be one of the following:
* id: the `div`'s id - default: ""
* class: any extra class(es) to be added to the `div` - default: ""
* show_preview: if the preview should be output in the HTML - default: `true`
* show_markup: if the markup should be output in the HTML - default: `true`
*/ -}}
{{- $id := .Get "id" -}}
{{- $class := .Get "class" -}}
{{- $lang := .Get "lang" | default "html" -}}
{{- $show_preview := .Get "show_preview" | default true -}}
{{- $show_markup := .Get "show_markup" | default true -}}
{{- $input := .Inner -}}
{{- $inputrendered := replaceRE `{{#js}}` "<div class='hidden' data-action='runjs'>" $input -}}
{{- $inputrendered = replaceRE `{{/js}}` "</div>" $inputrendered -}}
{{- if eq $show_preview true -}}
<div{{ with .Get "id" }} id="{{ . }}"{{ end }} class="bd-example{{ with .Get "class" }} {{ . }}{{ end }}">
{{- $inputrendered | safeHTML -}}
<div{{ with $id }} id="{{ . }}"{{ end }} class="bd-example{{ with $class }} {{ . }}{{ end }}">
{{- $input -}}
</div>
{{- end -}}
{{- if eq $show_markup true -}}
{{- $content := replaceRE `<svg class="bd\-placeholder\-img(?:\-lg)?(?: *?bd\-placeholder\-img\-lg)? ?(.*?)".*?<\/svg>\n` `<img src="..." class="$1" alt="...">` $input -}}
{{- $content = replaceRE `(class=" *?")` "" $content -}}
{{- highlight (trim $content "\n") "html" "" -}}
{{- $content := replaceRE `<svg class="bd-placeholder-img(?:-lg)?(?: *?bd-placeholder-img-lg)? ?(.*?)".*?<\/svg>\n` `<img src="..." class="$1" alt="...">` $input -}}
{{- $content = replaceRE ` (class=" *?")` "" $content -}}
{{- highlight (trim $content "\n") $lang "" -}}
{{- end -}}
@@ -0,0 +1,14 @@
{{- /*
Work around wrong escapes in integrity attributes.
Original: https://github.com/gohugoio/hugo/blob/master/tpl/tplimpl/embedded/templates/shortcodes/param.html
*/ -}}
{{- $name := .Get 0 -}}
{{- with $name -}}
{{- $value := $.Page.Param . -}}
{{- /* If any parameter ends with `_hash`, mark the string as safe HTML */ -}}
{{- if (strings.HasSuffix $name "_hash") -}}
{{- $value = $value | safeHTML -}}
{{- end -}}
{{- with $value }}{{ . }}{{ else }}{{ errorf "Param %q not found: %s" $name $.Position }}{{ end -}}
{{- else }}{{ errorf "Missing param key: %s" $.Position }}{{ end -}}
@@ -1,21 +1,24 @@
{{- /*
Usage: `placeholder args`
args can be one of the following:
title: Used in the SVG `title` tag, default "Placeholder"
text: The text to show in the image - default: "width x height"
class: default: "bd-placeholder-img"
color: The text color (foreground) - default: "#dee2e6"
background: The background color - default: "#868e96"
width: default: 100%
height: default: 180px
`args` are all optional and can be one of the following:
* title: Used in the SVG `title` tag - default: "Placeholder"
* text: The text to show in the image - default: "width x height"
* class: Class to add to the `svg` - default: "bd-placeholder-img"
* color: The text color (foreground) - default: "#dee2e6"
* background: The background color - default: "#868e96"
* width: default: "100%"
* height: default: "180px"
*/ -}}
{{- $grays := $.Site.Data.grays -}}
{{- $default_color := (index $grays 2).hex -}}
{{- $default_background := (index $grays 5).hex -}}
{{- $title := .Get "title" | default "Placeholder" -}}
{{- $class := .Get "class" -}}
{{- $color := .Get "color" | default (index $grays 2).hex -}}
{{- $background := .Get "background" | default (index $grays 5).hex -}}
{{- $color := .Get "color" | default $default_color -}}
{{- $background := .Get "background" | default $default_background -}}
{{- $width := .Get "width" | default "100%" -}}
{{- $height := .Get "height" | default "180" -}}
{{- $text := .Get "text" | default (printf "%sx%s" $width $height) -}}
@@ -23,8 +26,8 @@
{{- $show_title := not (eq $title "false") -}}
{{- $show_text := not (eq $text "false") -}}
<svg class="bd-placeholder-img{{ with $class }} {{ . }}{{ end }}" width="{{ $width }}" height="{{ $height }}" xmlns="http://www.w3.org/2000/svg"{{ if (or $show_title $show_text) }} aria-label="{{ if $show_title }}{{ $title }}{{ if $show_text }}: {{ end }}{{ end }}{{ if ($show_text) }}{{ $text }}{{ end }}"{{ end }} preserveAspectRatio="xMidYMid slice" role="img">
{{- if $show_title -}}<title>{{ $title }}</title>{{- end -}}
<svg class="bd-placeholder-img{{ with $class }} {{ . }}{{ end }}" width="{{ $width }}" height="{{ $height }}" xmlns="http://www.w3.org/2000/svg"{{ if (or $show_title $show_text) }} role="img" aria-label="{{ if $show_title }}{{ $title }}{{ if $show_text }}: {{ end }}{{ end }}{{ if ($show_text) }}{{ $text }}{{ end }}"{{ else }} aria-hidden="true"{{ end }} preserveAspectRatio="xMidYMid slice" focusable="false">
{{- if $show_title }}<title>{{ $title }}</title>{{ end -}}
<rect width="100%" height="100%" fill="{{ $background }}"/>
{{- if $show_text -}}<text x="50%" y="50%" fill="{{ $color }}" dy=".3em">{{ $text }}</text>{{- end -}}
{{- if $show_text }}<text x="50%" y="50%" fill="{{ $color }}" dy=".3em">{{ $text }}</text>{{ end -}}
</svg>
@@ -1,5 +1,3 @@
{{- /*
Output the current year
*/ -}}
{{- /* Outputs the current year */ -}}
{{- now.Format "2006" -}}
@@ -147,9 +147,7 @@ Feature: Manage plan workflow
| name | user | description | status | reviewer |
| Test-Plan3 | user1 | Description of plan 3 for user 1 | active | manager1 |
| Test-Plan4 | user1 | Description of plan 4 for user 1 | active | manager1 |
And I log in as "manager1"
And I navigate to "Users > Accounts > Browse list of users" in site administration
And I follow "User 1"
And I am on the "user1" "user > profile" page logged in as "manager1"
And I follow "Learning plans"
And I should see "List of learning plans"
When I click on "Send back to draft" of edit menu in the "Test-Plan3" row
@@ -165,9 +163,7 @@ Feature: Manage plan workflow
| name | user | description | status | reviewer |
| Test-Plan3 | user1 | Description of plan 3 for user 1 | active | manager1 |
| Test-Plan4 | user1 | Description of plan 4 for user 1 | active | manager1 |
And I log in as "manager1"
And I navigate to "Users > Accounts > Browse list of users" in site administration
And I follow "User 1"
And I am on the "user1" "user > profile" page logged in as "manager1"
And I follow "Learning plans"
And I should see "List of learning plans"
When I click on "Complete this learning plan" of edit menu in the "Test-Plan3" row
@@ -186,9 +182,7 @@ Feature: Manage plan workflow
| name | user | description | status | reviewer |
| Test-Plan3 | user1 | Description of plan 3 for user 1 | complete | manager1 |
| Test-Plan4 | user1 | Description of plan 4 for user 1 | complete | manager1 |
And I log in as "manager1"
And I navigate to "Users > Accounts > Browse list of users" in site administration
And I follow "User 1"
And I am on the "user1" "user > profile" page logged in as "manager1"
And I follow "Learning plans"
And I should see "List of learning plans"
When I click on "Reopen this learning plan" of edit menu in the "Test-Plan3" row
@@ -71,8 +71,8 @@
</div>
<div class="box py-3 modal-footer">
<div class="buttons">
<input class="btn btn-secondary" type="submit" name="cancel" value="Cancel">
<input class="btn btn-primary" type="submit" name="import" value="Continue">
<input class="btn btn-secondary" type="submit" name="cancel" value="{{#str}}cancel, core{{/str}}">
<input class="btn btn-primary" type="submit" name="import" value="{{#str}}continue, core{{/str}}">
</div>
</div>
</form>
@@ -60,7 +60,7 @@ class user_field_mapping extends persistent {
// Internal.
$choices = $userfieldmapping->get_internalfield_list();
$mform->addElement('select', 'internalfield', get_string('userfieldinternalfield', 'tool_oauth2'), $choices);
$mform->addElement('selectgroups', 'internalfield', get_string('userfieldinternalfield', 'tool_oauth2'), $choices);
$mform->addHelpButton('internalfield', 'userfieldinternalfield', 'tool_oauth2');
$mform->addElement('hidden', 'action', 'edit');
@@ -0,0 +1,43 @@
@tool @tool_oauth2 @javascript
Feature: OAuth2 user profile fields functionality
In order to use them later for authentication or repository plugins
As an administrator
I need to be able to map data fields provided by an Oauth2 provider
to custom user profile fields defined by an administrator.
Background:
Given the following "custom profile fields" exist:
| datatype | shortname | name | locked |
| text | unlocked_field | Unlocked field | 0 |
| text | locked_field | Locked field | 1 |
And I log in as "admin"
And I navigate to "Server > OAuth 2 services" in site administration
Scenario: Verify custom user profile field mapping
Given I press "Microsoft"
And I should see "Create new service: Microsoft"
And I set the following fields to these values:
| Name | Testing service |
| Client ID | thisistheclientid |
| Client secret | supersecret |
When I press "Save changes"
Then I should see "Changes saved"
And I should see "Testing service"
And I click on "Configure user field mappings" "link" in the "Testing service" "table_row"
# Create unlocked field
And I click on "Create new user field mapping for issuer \"Testing service\"" "button"
And I set the following fields to these values:
| External field name | External unlocked |
| Internal field name | Unlocked field |
And I click on "Save changes" "button"
And I should see "unlocked_field"
# Create locked field
And I click on "Create new user field mapping for issuer \"Testing service\"" "button"
And I set the following fields to these values:
| External field name | External locked |
| Internal field name | Locked field |
And I click on "Save changes" "button"
And I should see "locked_field"
@@ -241,9 +241,7 @@ Feature: Viewing acceptances reports and accepting on behalf of other users
And I set the field "I agree to the This site policy" to "1"
And I press "Next"
And I log out
And I log in as "admin"
And I navigate to "Users > Accounts > Browse list of users" in site administration
And I follow "Manager"
And I am on the "manager" "user > profile" page logged in as "admin"
And I follow "Log in as"
And I press "Continue"
And I navigate to "Users > Privacy and policies > Manage policies" in site administration
@@ -563,9 +563,7 @@ Feature: User must accept policy managed by this plugin when logging in and sign
And the following "users" exist:
| username | firstname | lastname | email |
| user1 | User | 1 | user1@example.com |
When I log in as "admin"
And I navigate to "Users > Accounts > Browse list of users" in site administration
And I follow "User 1"
When I am on the "user1" "user > profile" page logged in as "admin"
And I follow "Log in as"
Then I should see "You are logged in as User 1"
And I press "Continue"
@@ -604,8 +602,7 @@ Feature: User must accept policy managed by this plugin when logging in and sign
And I set the field "I agree to the This site policy" to "1"
And I set the field "I agree to the This privacy policy" to "1"
And I press "Next"
And I navigate to "Users > Accounts > Browse list of users" in site administration
And I follow "User 1"
And I am on the "user1" "user > profile" page
And I follow "Log in as"
Then I should see "You are logged in as User 1"
And I press "Continue"
+4 -1
View File
@@ -111,7 +111,10 @@ class cli_helper {
$form = new \admin_uploaduser_form1();
[$elements, $defaults] = $form->get_form_for_cli();
$options += $this->prepare_form_elements_for_cli($elements, $defaults);
$form = new \admin_uploaduser_form2(null, ['columns' => ['type1'], 'data' => []]);
// Specify pseudo-column 'type1' to force the form to populate the legacy role mapping selector
// but only if user is allowed to assign roles in courses (otherwise form validation will fail).
$columns = uu_allowed_roles() ? ['type1'] : [];
$form = new \admin_uploaduser_form2(null, ['columns' => $columns, 'data' => []]);
[$elements, $defaults] = $form->get_form_for_cli();
$options += $this->prepare_form_elements_for_cli($elements, $defaults);
return $options;
+19 -8
View File
@@ -126,7 +126,6 @@ class process {
$today = make_timestamp(date('Y', $today), date('m', $today), date('d', $today), 0, 0, 0);
$this->today = $today;
$this->rolecache = uu_allowed_roles_cache(); // Course roles lookup cache.
$this->sysrolecache = uu_allowed_sysroles_cache(); // System roles lookup cache.
$this->supportedauths = uu_supported_auths(); // Officially supported plugins that are enabled.
@@ -1209,14 +1208,18 @@ class process {
}
}
if (!array_key_exists($courseid, $this->rolecache)) {
$this->rolecache[$courseid] = uu_allowed_roles_cache(null, (int)$courseid);
}
if ($courseid == SITEID) {
// Technically frontpage does not have enrolments, but only role assignments,
// let's not invent new lang strings here for this rarely used feature.
if (!empty($user->{'role'.$i})) {
$rolename = $user->{'role'.$i};
if (array_key_exists($rolename, $this->rolecache)) {
$roleid = $this->rolecache[$rolename]->id;
if (array_key_exists($rolename, $this->rolecache[$courseid]) ) {
$roleid = $this->rolecache[$courseid][$rolename]->id;
} else {
$this->upt->track('enrolments', get_string('unknownrole', 'error', s($rolename)), 'error');
continue;
@@ -1226,7 +1229,7 @@ class process {
$a = new \stdClass();
$a->course = $shortname;
$a->role = $this->rolecache[$roleid]->name;
$a->role = $this->rolecache[$courseid][$roleid]->name;
$this->upt->track('enrolments', get_string('enrolledincourserole', 'enrol_manual', $a), 'info');
}
@@ -1236,8 +1239,8 @@ class process {
$roleid = false;
if (!empty($user->{'role'.$i})) {
$rolename = $user->{'role'.$i};
if (array_key_exists($rolename, $this->rolecache)) {
$roleid = $this->rolecache[$rolename]->id;
if (array_key_exists($rolename, $this->rolecache[$courseid])) {
$roleid = $this->rolecache[$courseid][$rolename]->id;
} else {
$this->upt->track('enrolments', get_string('unknownrole', 'error', s($rolename)), 'error');
continue;
@@ -1256,7 +1259,15 @@ class process {
}
} else {
// No role specified, use the default from manual enrol plugin.
$roleid = $this->manualcache[$courseid]->roleid;
$defaultenrolroleid = (int)$this->manualcache[$courseid]->roleid;
// Validate the current user can assign this role.
if (array_key_exists($defaultenrolroleid, $this->rolecache[$courseid]) ) {
$roleid = $defaultenrolroleid;
} else {
$role = $DB->get_record('role', ['id' => $defaultenrolroleid]);
$this->upt->track('enrolments', get_string('unknownrole', 'error', s($role->shortname)), 'error');
continue;
}
}
if ($roleid) {
@@ -1299,7 +1310,7 @@ class process {
$a = new \stdClass();
$a->course = $shortname;
$a->role = $this->rolecache[$roleid]->name;
$a->role = $this->rolecache[$courseid][$roleid]->name;
$this->upt->track('enrolments', get_string('enrolledincourserole', 'enrol_manual', $a), 'info');
}
}
+16 -7
View File
@@ -384,23 +384,32 @@ function uu_allowed_roles() {
}
/**
* Returns mapping of roles using short role name as index.
* Returns assignable roles for current user using short role name and role ID as index.
* This function is no longer called without parameters.
*
* @param int|null $categoryid Id of the category to get roles for. Null means all roles.
* @param int|null $categoryid Id of the category to get roles for.
* @param int|null $courseid Id of the course to get roles for.
* @return array
*/
function uu_allowed_roles_cache(?int $categoryid = null): array {
if (is_null($categoryid)) {
$allowedroles = get_assignable_roles(context_course::instance(SITEID), ROLENAME_SHORT);
} else {
function uu_allowed_roles_cache(?int $categoryid = null, ?int $courseid = null): array {
if (!is_null($categoryid) && !is_null($courseid)) {
return [];
} else if (is_null($categoryid) && !is_null($courseid)) {
$allowedroles = get_assignable_roles(context_course::instance($courseid), ROLENAME_SHORT);
} else if (is_null($courseid) && !is_null($categoryid)) {
$allowedroles = get_assignable_roles(context_coursecat::instance($categoryid), ROLENAME_SHORT);
} else {
$allowedroles = get_assignable_roles(context_course::instance(SITEID), ROLENAME_SHORT);
}
$rolecache = [];
// A role can be searched for by its ID or by its shortname.
foreach ($allowedroles as $rid=>$rname) {
$rolecache[$rid] = new stdClass();
$rolecache[$rid]->id = $rid;
$rolecache[$rid]->name = $rname;
if (!is_numeric($rname)) { // only non-numeric shortnames are supported!!!
// Since numeric short names are allowed, to avoid replacement of another role, we only accept non-numeric values.
if (!is_numeric($rname)) {
$rolecache[$rname] = new stdClass();
$rolecache[$rname]->id = $rid;
$rolecache[$rname]->name = $rname;
+2 -1
View File
@@ -143,7 +143,8 @@ class cli_test extends \advanced_testcase {
// Created users have data in the profile fields.
$user1 = \core_user::get_user_by_username('reznort');
$profilefields1 = profile_user_record($user1->id);
$this->assertEquals((object)['superfield' => 'Loves cats'], $profilefields1);
$this->assertObjectHasAttribute('superfield', $profilefields1);
$this->assertEquals('Loves cats', $profilefields1->superfield);
}
/**
@@ -0,0 +1,221 @@
<?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/>.
namespace tool_uploaduser;
use advanced_testcase;
use context_system;
use context_course;
use context_coursecat;
use stdClass;
use tool_uploaduser\cli_helper;
use tool_uploaduser\local\text_progress_tracker;
/**
* Class upload_users_test
*
* @package tool_uploaduser
* @copyright 2020 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class upload_users_test extends advanced_testcase {
/**
* Load required test libraries
*/
public static function setUpBeforeClass(): void {
global $CFG;
require_once("{$CFG->dirroot}/{$CFG->admin}/tool/uploaduser/locallib.php");
}
/**
* Test upload users, enrol and role assignation
* @covers \tool_uploadusers::process
*/
public function test_user_can_upload_with_course_enrolment(): void {
$this->resetAfterTest();
set_config('passwordpolicy', 0);
$this->setAdminUser();
// Create category and course.
$coursecat = $this->getDataGenerator()->create_category();
$coursecatcontext = context_coursecat::instance($coursecat->id);
$course = $this->getDataGenerator()->create_course(['shortname' => 'course01', 'category' => $coursecat->id]);
$coursecontext = context_course::instance($course->id);
// Create user.
$user = $this->getDataGenerator()->create_user();
// Create role with capability to upload CSV files, and assign this role to user.
$uploadroleid = create_role('upload role', 'uploadrole', '');
set_role_contextlevels($uploadroleid, [CONTEXT_SYSTEM]);
$systemcontext = context_system::instance();
assign_capability('moodle/site:uploadusers', CAP_ALLOW, $uploadroleid, $systemcontext->id);
$this->getDataGenerator()->role_assign($uploadroleid, $user->id, $systemcontext->id);
// Create role with some of allowed capabilities to enrol users, and assign this role to user.
$enrolroleid = create_role('enrol role', 'enrolrole', '');
set_role_contextlevels($enrolroleid, [CONTEXT_COURSECAT]);
assign_capability('enrol/manual:enrol', CAP_ALLOW, $enrolroleid, $coursecatcontext->id);
assign_capability('moodle/course:enrolreview', CAP_ALLOW, $enrolroleid, $coursecatcontext->id);
assign_capability('moodle/role:assign', CAP_ALLOW, $enrolroleid, $coursecatcontext->id);
$this->getDataGenerator()->role_assign($enrolroleid, $user->id, $coursecatcontext->id);
// User makes assignments.
$studentarch = get_archetype_roles('student');
$studentrole = array_shift($studentarch);
core_role_set_assign_allowed($enrolroleid, $studentrole->id);
// Flush accesslib.
accesslib_clear_all_caches_for_unit_testing();
// Process CSV file as user.
$csv = <<<EOF
username,firstname,lastname,email,course1,role1
student1,Student,One,s1@example.com,{$course->shortname},{$studentrole->shortname}
student2,Student,Two,s2@example.com,{$course->shortname},teacher
EOF;
$this->setUser($user);
$output = $this->process_csv_upload($csv, ['--uutype=' . UU_USER_ADDNEW]);
$this->assertStringContainsString('Enrolled in "course01" as "student"', $output);
$this->assertStringContainsString('Unknown role "teacher"', $output);
// Check user creation, enrolment and role assignation.
$this->assertEquals(1, count_enrolled_users($coursecontext));
$usersasstudent = get_role_users($studentrole->id, $coursecontext);
$this->assertCount(1, $usersasstudent);
$this->assertEquals('student1', reset($usersasstudent)->username);
}
/**
* Test upload users, enrol and assign default role from manual enrol plugin.
* @covers \tool_uploadusers::process
*/
public function test_user_can_upload_with_course_enrolment_default_role(): void {
$this->resetAfterTest();
set_config('passwordpolicy', 0);
$this->setAdminUser();
// Create category and courses.
$coursecat = $this->getDataGenerator()->create_category();
$coursecatcontext = context_coursecat::instance($coursecat->id);
$course1 = $this->getDataGenerator()->create_course(['shortname' => 'course01', 'category' => $coursecat->id]);
$course1context = context_course::instance($course1->id);
// Change the default role to 'teacher'.
set_config('roleid', 4, 'enrol_manual');
$course2 = $this->getDataGenerator()->create_course(['shortname' => 'course02', 'category' => $coursecat->id]);
$course2context = context_course::instance($course2->id);
// Create user.
$user = $this->getDataGenerator()->create_user();
// Create role with capability to upload CSV files, and assign this role to user.
$uploadroleid = create_role('upload role', 'uploadrole', '');
set_role_contextlevels($uploadroleid, [CONTEXT_SYSTEM]);
$systemcontext = context_system::instance();
assign_capability('moodle/site:uploadusers', CAP_ALLOW, $uploadroleid, $systemcontext->id);
$this->getDataGenerator()->role_assign($uploadroleid, $user->id, $systemcontext->id);
// Create role with some of allowed capabilities to enrol users, and assign this role to user.
$enrolroleid = create_role('enrol role', 'enrolrole', '');
set_role_contextlevels($enrolroleid, [CONTEXT_COURSECAT]);
assign_capability('enrol/manual:enrol', CAP_ALLOW, $enrolroleid, $coursecatcontext->id);
assign_capability('moodle/course:enrolreview', CAP_ALLOW, $enrolroleid, $coursecatcontext->id);
assign_capability('moodle/role:assign', CAP_ALLOW, $enrolroleid, $coursecatcontext->id);
$this->getDataGenerator()->role_assign($enrolroleid, $user->id, $coursecatcontext->id);
// User makes assignments.
$studentarch = get_archetype_roles('student');
$studentrole = array_shift($studentarch);
core_role_set_assign_allowed($enrolroleid, $studentrole->id);
// Flush accesslib.
accesslib_clear_all_caches_for_unit_testing();
// Process CSV file (no roles specified) as user.
$csv = <<<EOF
username,firstname,lastname,email,course1,role1
student1,Student,One,s1@example.com,{$course1->shortname},
student2,Student,Two,s2@example.com,{$course2->shortname},
EOF;
$this->setUser($user);
$output = $this->process_csv_upload($csv, ['--uutype=' . UU_USER_ADDNEW]);
$this->assertStringContainsString('Enrolled in "course01" as "student"', $output);
// This $user cannot assign teacher role.
$this->assertStringContainsString('Unknown role "teacher"', $output);
// Check user creation, enrolment and role assignation.
$this->assertEquals(1, count_enrolled_users($course1context));
// This $user cannot enrol anyone as teacher.
$this->assertEquals(0, count_enrolled_users($course2context));
// Test user is enrolled as default-manual-enrol-plugin role.
$manualenrolinstance = new stdClass;
$enrolinstances = enrol_get_instances($course1->id, true);
foreach ($enrolinstances as $courseenrolinstance) {
if ($courseenrolinstance->enrol === 'manual') {
$manualenrolinstance = $courseenrolinstance;
break;
}
}
$defaulroleidexpected = $manualenrolinstance->roleid ?? 0;
// The default role of course01 is student, id 5.
$this->assertEquals(5, $defaulroleidexpected);
$usersasdefaultrole = get_role_users($defaulroleidexpected, $course1context);
$this->assertCount(1, $usersasdefaultrole);
$this->assertEquals('student1', reset($usersasdefaultrole)->username);
}
/**
* Generate cli_helper and mock $_SERVER['argv']
*
* @param string $filecontent
* @param array $mockargv
* @return string
*/
protected function process_csv_upload(string $filecontent, array $mockargv = []): string {
$filepath = make_request_directory() . '/upload.csv';
file_put_contents($filepath, $filecontent);
$mockargv[] = "--file={$filepath}";
if (array_key_exists('argv', $_SERVER)) {
$oldservervars = $_SERVER['argv'];
}
$_SERVER['argv'] = array_merge([''], $mockargv);
$clihelper = new cli_helper(text_progress_tracker::class);
if (isset($oldservervars)) {
$_SERVER['argv'] = $oldservervars;
} else {
unset($_SERVER['argv']);
}
ob_start();
$clihelper->process();
$output = ob_get_contents();
ob_end_clean();
return $output;
}
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+23 -1
View File
@@ -14,6 +14,8 @@ import {eventTypes} from './events';
let currentTour = null;
let tourId = null;
let restartTourAndKeepProgress = false;
let currentStepNo = null;
/**
* Find the first matching tour.
@@ -75,6 +77,20 @@ export const init = async(tourDetails, filters) => {
resetTourState(tourId);
}
});
// Watch for the resize event.
window.addEventListener("resize", () => {
// Only listen for the running tour.
if (currentTour && currentTour.tourRunning) {
clearTimeout(window.resizedFinished);
window.resizedFinished = setTimeout(() => {
// Wait until the resize event has finished.
currentStepNo = currentTour.getCurrentStepNumber();
restartTourAndKeepProgress = true;
resetTourState(tourId);
}, 250);
}
});
};
/**
@@ -187,7 +203,13 @@ const startBootstrapTour = (tourId, template, tourConfig) => {
});
currentTour = new BootstrapTour(tourConfig);
return currentTour.startTour();
let startAt = 0;
if (restartTourAndKeepProgress && currentStepNo) {
startAt = currentStepNo;
restartTourAndKeepProgress = false;
currentStepNo = null;
}
return currentTour.startTour(startAt);
};
/**
+34 -18
View File
@@ -258,15 +258,7 @@ class api {
$user->password = '';
$user->confirmed = 1; // Set the user to confirmed.
// Map supplied issuer user info to Moodle user fields.
$userfieldmapping = new \core\oauth2\user_field_mapping();
foreach ($userfieldmapping->get_internalfield_list() as $field) {
if (isset($userinfo[$field]) && $userinfo[$field]) {
$user->$field = $userinfo[$field];
}
}
$user->id = user_create_user($user, false, true);
$user = self::save_user($userinfo, $user);
// The linked account is pre-confirmed.
$record = new stdClass();
@@ -307,15 +299,7 @@ class api {
$user->password = '';
$user->confirmed = 0; // The user is not yet confirmed.
// Map supplied issuer user info to Moodle user fields.
$userfieldmapping = new \core\oauth2\user_field_mapping();
foreach ($userfieldmapping->get_internalfield_list() as $field) {
if (isset($userinfo[$field]) && $userinfo[$field]) {
$user->$field = $userinfo[$field];
}
}
$user->id = user_create_user($user, false, true);
$user = self::save_user($userinfo, $user);
// The linked account is pre-confirmed.
$record = new stdClass();
@@ -404,4 +388,36 @@ class api {
public static function is_enabled() {
return is_enabled_auth('oauth2');
}
/**
* Create a new user & update the profile fields
*
* @param array $userinfo
* @param object $user
* @return object
*/
private static function save_user(array $userinfo, object $user): object {
// Map supplied issuer user info to Moodle user fields.
$userfieldmapping = new \core\oauth2\user_field_mapping();
$userfieldlist = $userfieldmapping->get_internalfields();
$hasprofilefield = false;
foreach ($userfieldlist as $field) {
if (isset($userinfo[$field]) && $userinfo[$field]) {
$user->$field = $userinfo[$field];
// Check whether the profile fields exist or not.
$hasprofilefield = $hasprofilefield || strpos($field, \core_user\fields::PROFILE_FIELD_PREFIX) === 0;
}
}
// Create a new user.
$user->id = user_create_user($user, false, true);
// If profile fields exist then save custom profile fields data.
if ($hasprofilefield) {
profile_save_data($user);
}
return $user;
}
}
+21 -5
View File
@@ -63,6 +63,7 @@ class auth extends \auth_plugin_base {
public function __construct() {
$this->authtype = 'oauth2';
$this->config = get_config('auth_oauth2');
$this->customfields = $this->get_custom_user_profile_fields();
}
/**
@@ -309,23 +310,35 @@ class auth extends \auth_plugin_base {
return $userdata;
}
$allfields = array_merge($this->userfields, $this->customfields);
// Go through each field from the external data.
foreach ($externaldata as $fieldname => $value) {
if (!in_array($fieldname, $this->userfields)) {
if (!in_array($fieldname, $allfields)) {
// Skip if this field doesn't belong to the list of fields that can be synced with the OAuth2 issuer.
continue;
}
if (!property_exists($userdata, $fieldname)) {
// Just in case this field is on the list, but not part of the user data. This shouldn't happen though.
$userhasfield = property_exists($userdata, $fieldname);
// Find out if it is a profile field.
$isprofilefield = strpos($fieldname, 'profile_field_') === 0;
$profilefieldname = str_replace('profile_field_', '', $fieldname);
$userhasprofilefield = $isprofilefield && array_key_exists($profilefieldname, $userdata->profile);
// Just in case this field is on the list, but not part of the user data. This shouldn't happen though.
if (!($userhasfield || $userhasprofilefield)) {
continue;
}
// Get the old value.
$oldvalue = (string)$userdata->$fieldname;
$oldvalue = $isprofilefield ? (string) $userdata->profile[$profilefieldname] : (string) $userdata->$fieldname;
// Get the lock configuration of the field.
$lockvalue = $this->config->{'field_lock_' . $fieldname};
if (!empty($this->config->{'field_lock_' . $fieldname})) {
$lockvalue = $this->config->{'field_lock_' . $fieldname};
} else {
$lockvalue = 'unlocked';
}
// We should update fields that meet the following criteria:
// - Lock value set to 'unlocked'; or 'unlockedifempty', given the current value is empty.
@@ -525,6 +538,9 @@ class auth extends \auth_plugin_base {
exit();
} else {
\auth_oauth2\api::link_login($userinfo, $issuer, $moodleuser->id, true);
// We dont have profile loaded on $moodleuser, so load it.
require_once($CFG->dirroot.'/user/profile/lib.php');
profile_load_custom_fields($moodleuser);
$userinfo = $this->update_user($userinfo, $moodleuser);
// No redirect, we will complete this login.
}
+2 -1
View File
@@ -31,5 +31,6 @@ if ($ADMIN->fulltree) {
$authplugin = get_auth_plugin('oauth2');
display_auth_lock_options($settings, $authplugin->authtype, $authplugin->userfields,
get_string('auth_fieldlocks_help', 'auth'), false, false);
get_string('auth_fieldlocks_help', 'auth'), false, false,
$authplugin->customfields);
}
+7 -8
View File
@@ -18,17 +18,16 @@ Feature: Test if the login form provides the correct feedback
Scenario: Test login language selector
Given remote langimport tests are enabled
And I log in as "admin"
And I navigate to "Language > Language packs" in site administration
And I set the field "Available language packs" to "nl,es"
And I press "Install selected language pack(s)"
And I trigger cron
And I am on homepage
And I log out
And the following "language packs" exist:
| language |
| nl |
| es |
And the following config values are set as admin:
| langmenu | 1 |
And I follow "Log in"
And I open the action menu in "region-main" "region"
# The line below contains the unicode character U+200E before and after the brackets, please be very careful editing this line.
And I choose "Nederlands (nl)" in the open action menu
When I choose "Nederlands (nl)" in the open action menu
Then I should see "Gebruikersnaam"
@_file_upload
@@ -59,9 +59,10 @@ Feature: The my overview block allows users to hide their courses
And I click on "Removed from view" "link" in the "Course overview" "block"
And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
And I click on "Restore to view" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
And I reload the page
And I should not see "Course 2" in the "Course overview" "block"
And I click on "Removed from view" "button" in the "Course overview" "block"
When I click on "All" "link" in the "Course overview" "block"
And I reload the page
Then I should see "Course 2" in the "Course overview" "block"
Scenario: Test show toggle functionality w/ favorites
@@ -146,6 +146,7 @@ Feature: My overview block pagination
| student1 | C12 | student |
| student1 | C13 | student |
When I am on the "My courses" page logged in as "student1"
And I wait until ".block_myoverview [data-control='next']" "css_element" exists
And I click on "[data-control='next']" "css_element" in the "Course overview" "block"
Then the "class" attribute of ".block_myoverview [data-control='next']" "css_element" should contain "disabled"
And I log out
@@ -51,17 +51,18 @@ class recentlyaccesseditems_item_exporter extends \core\external\exporter {
protected function get_other_values(renderer_base $output) {
global $CFG;
require_once($CFG->libdir.'/modinfolib.php');
$iconurl = get_fast_modinfo($this->data->courseid)->cms[$this->data->cmid]->get_icon_url();
$iconclass = $iconurl->get_param('filtericon') ? '' : 'nofilter';
return array(
'viewurl' => (new moodle_url('/mod/'.$this->data->modname.'/view.php',
array('id' => $this->data->cmid)))->out(false),
'courseviewurl' => (new moodle_url('/course/view.php', array('id' => $this->data->courseid)))->out(false),
'icon' => \html_writer::img(
get_fast_modinfo($this->data->courseid)->cms[$this->data->cmid]->get_icon_url(),
$iconurl,
get_string('pluginname', $this->data->modname),
['title' => get_string('pluginname', $this->data->modname), 'class' => 'icon']
['title' => get_string('pluginname', $this->data->modname), 'class' => "icon $iconclass"]
),
'purpose' => plugin_supports('mod', $this->data->modname, FEATURE_MOD_PURPOSE, MOD_PURPOSE_OTHER)
'purpose' => plugin_supports('mod', $this->data->modname, FEATURE_MOD_PURPOSE, MOD_PURPOSE_OTHER),
);
}
@@ -57,7 +57,7 @@
<div class="activityiconcontainer small {{purpose}} courseicon align-self-top align-self-center mx-3 mb-1 mb-sm-0 text-nowrap">
{{#icon}}
{{#iconurl}}
<img alt="{{alttext}}" title="{{alttext}}" src="{{{ iconurl }}}" class="icon ">
<img alt="{{alttext}}" title="{{alttext}}" src="{{{ iconurl }}}" class="icon {{iconclass}}">
{{/iconurl}}
{{^iconurl}}
{{#pix}} {{key}}, {{component}}, {{alttext}} {{/pix}}
+6 -1
View File
@@ -62,12 +62,15 @@ class event_icon_exporter extends exporter {
$isgroupevent = ($group && !empty($groupid));
$isuserevent = ($user && !empty($userid));
$iconurl = '';
$iconclass = '';
if ($isactivityevent) {
$key = 'monologo';
$component = $coursemodule->get('modname');
$iconurl = get_fast_modinfo($courseid)->get_cm($coursemodule->get('id'))->get_icon_url()->out(false);
$iconurl = get_fast_modinfo($courseid)->get_cm($coursemodule->get('id'))->get_icon_url();
$iconclass = $iconurl->get_param('filtericon') ? '' : 'nofilter';
$iconurl = $iconurl->out(false);
if (get_string_manager()->string_exists($event->get_type(), $component)) {
$alttext = get_string($event->get_type(), $component);
} else {
@@ -121,6 +124,7 @@ class event_icon_exporter extends exporter {
$data->component = $component;
$data->alttext = $alttext;
$data->iconurl = $iconurl;
$data->iconclass = $iconclass;
parent::__construct($data, $related);
}
@@ -136,6 +140,7 @@ class event_icon_exporter extends exporter {
'component' => ['type' => PARAM_TEXT],
'alttext' => ['type' => PARAM_TEXT],
'iconurl' => ['type' => PARAM_TEXT],
'iconclass' => ['type' => PARAM_TEXT],
];
}
@@ -179,6 +179,9 @@ class raw_event_retrieval_strategy implements raw_event_retrieval_strategy_inter
$whereclause = implode(' AND ', $whereconditions);
// Build SQL subquery and conditions for filtered events based on priorities.
$subquerytimeconditions = array_filter($whereconditions, function($condition) {
return (strpos($condition, 'time') !== false);
});
$subquerywhere = '';
$subqueryconditions = [];
$subqueryparams = [];
@@ -194,7 +197,12 @@ class raw_event_retrieval_strategy implements raw_event_retrieval_strategy_inter
// Set calendar filters.
list($usercourses, $usergroups, $user) = calendar_set_filters($usercourses, true, $userrecord);
$allusercourses = array_merge($allusercourses, $usercourses);
$filteredcourses = is_array($courses) ? $courses : [$courses];
$filteredcourses = array_filter($usercourses, function($course) use ($filteredcourses) {
return in_array($course, $filteredcourses);
});
$allusercourses = array_merge($allusercourses, $filteredcourses);
// Flag to indicate whether the query needs to exclude group overrides.
$viewgroupsonly = false;
@@ -203,7 +211,8 @@ class raw_event_retrieval_strategy implements raw_event_retrieval_strategy_inter
// Set filter condition for the user's events.
// Even though $user is a single scalar, we still use get_in_or_equal() because we are inside a loop.
list($inusers, $inuserparams) = $DB->get_in_or_equal($user, SQL_PARAMS_NAMED);
$subqueryconditions[] = "(ev.userid $inusers AND ev.courseid = 0 AND ev.groupid = 0 AND ev.categoryid = 0)";
$condition = "(ev.userid $inusers AND ev.courseid = 0 AND ev.groupid = 0 AND ev.categoryid = 0)";
$subqueryconditions[] = $condition;
$subqueryparams = array_merge($subqueryparams, $inuserparams);
foreach ($usercourses as $courseid) {
@@ -220,11 +229,11 @@ class raw_event_retrieval_strategy implements raw_event_retrieval_strategy_inter
// Set filter condition for the user's group events.
if ($usergroups === true || $viewgroupsonly) {
// Fetch group events, but not group overrides.
$subqueryconditions[] = "(ev.groupid != 0 AND ev.eventtype = 'group')";
$groupconditions = "(ev.groupid != 0 AND ev.eventtype = 'group')";
} else if (!empty($usergroups)) {
// Fetch group events and group overrides.
list($inusergroups, $inusergroupparams) = $DB->get_in_or_equal($usergroups, SQL_PARAMS_NAMED);
$subqueryconditions[] = "(ev.groupid $inusergroups)";
$groupconditions = "(ev.groupid $inusergroups)";
$subqueryparams = array_merge($subqueryparams, $inusergroupparams);
}
}
@@ -263,8 +272,20 @@ class raw_event_retrieval_strategy implements raw_event_retrieval_strategy_inter
// Set subquery filter condition for the courses.
if (!empty($subquerycourses)) {
list($incourses, $incoursesparams) = $DB->get_in_or_equal($subquerycourses, SQL_PARAMS_NAMED);
$subqueryconditions[] = "(ev.groupid = 0 AND ev.courseid $incourses AND ev.categoryid = 0)";
$subqueryparams = array_merge($subqueryparams, $incoursesparams);
if (isset($groupconditions)) {
$groupconditions = $groupconditions." OR ";
} else {
$groupconditions = '';
}
$condition = "($groupconditions(ev.groupid = 0 AND ev.courseid $incourses AND ev.categoryid = 0))";
$subtimesparams = [];
if (!empty($subquerytimeconditions)) {
$subtimes = $this->subquerytimeconditions("courses", $subquerytimeconditions, $whereparams);
$condition .= $subtimes['where'];
$subtimesparams = $subtimes['params'];
}
$subqueryconditions[] = $condition;
$subqueryparams = array_merge($subqueryparams, $incoursesparams, $subtimesparams);
}
// Set subquery filter condition for the categories.
@@ -272,8 +293,15 @@ class raw_event_retrieval_strategy implements raw_event_retrieval_strategy_inter
$subqueryconditions[] = "(ev.categoryid != 0 AND ev.eventtype = 'category')";
} else if (!empty($categories)) {
list($incategories, $incategoriesparams) = $DB->get_in_or_equal($categories, SQL_PARAMS_NAMED);
$subqueryconditions[] = "(ev.groupid = 0 AND ev.courseid = 0 AND ev.categoryid $incategories)";
$subqueryparams = array_merge($subqueryparams, $incategoriesparams);
$condition = "(ev.groupid = 0 AND ev.courseid = 0 AND ev.categoryid $incategories)";
$subtimesparams = [];
if (!empty($subquerytimeconditions)) {
$subtimes = $this->subquerytimeconditions("cats", $subquerytimeconditions, $whereparams);
$condition .= $subtimes['where'];
$subtimesparams = $subtimes['params'];
}
$subqueryconditions[] = $condition;
$subqueryparams = array_merge($subqueryparams, $incategoriesparams, $subtimesparams);
}
// Build the WHERE condition for the sub-query.
@@ -322,4 +350,36 @@ class raw_event_retrieval_strategy implements raw_event_retrieval_strategy_inter
return $events === false ? [] : $events;
}
/**
* Returns a query fragment and params, with time constraints applied
*
* @param string $prefix
* @param array $conditions
* @param array $params
* @return array [<where>, <params>]
*/
protected function subquerytimeconditions(string $prefix, array $conditions, array $params): array {
$outwhere = '';
$outparams = [];
// Most specific to least specific.
$timeparams = ['timefromid', 'timefrom3', 'timefrom2', 'timefrom1', 'timefrom', 'timetoid', 'timeto2', 'timeto1', 'timeto'];
$whereconditions = [];
foreach ($conditions as $condition) {
$where = $condition;
// This query has been borrowed from the main WHERE clause, so the alias needs to be renamed to match the union.
$where = str_replace('e.id', 'ev.id', $where);
foreach ($timeparams as $timeparam) {
if (isset($params[$timeparam])) {
$where = str_replace(":{$timeparam}", ":{$prefix}{$timeparam}", $where);
$outparams["{$prefix}{$timeparam}"] = $params[$timeparam];
}
}
$whereconditions[] = $where;
}
if (count($whereconditions) > 0) {
$outwhere = ' AND ' . implode(' AND ', $whereconditions);
}
return ['where' => $outwhere, 'params' => $outparams];
}
}
+2 -2
View File
@@ -2580,7 +2580,7 @@ function calendar_format_event_location(calendar_event $event): string {
function calendar_show_event_type($type, $user = null) {
$default = CALENDAR_EVENT_SITE + CALENDAR_EVENT_COURSE + CALENDAR_EVENT_GROUP + CALENDAR_EVENT_USER;
if (get_user_preferences('calendar_persistflt', 0, $user) === 0) {
if ((int)get_user_preferences('calendar_persistflt', 0, $user) === 0) {
global $SESSION;
if (!isset($SESSION->calendarshoweventtype)) {
$SESSION->calendarshoweventtype = $default;
@@ -2603,7 +2603,7 @@ function calendar_show_event_type($type, $user = null) {
* @param stdClass|int $user moodle user object or id, null means current user
*/
function calendar_set_event_type_display($type, $display = null, $user = null) {
$persist = get_user_preferences('calendar_persistflt', 0, $user);
$persist = (int)get_user_preferences('calendar_persistflt', 0, $user);
$default = CALENDAR_EVENT_SITE + CALENDAR_EVENT_COURSE + CALENDAR_EVENT_GROUP
+ CALENDAR_EVENT_USER + CALENDAR_EVENT_COURSECAT;
if ($persist === 0) {
+1 -1
View File
@@ -47,7 +47,7 @@
<div class="activityiconcontainer small {{{purpose}}} courseicon mr-3">
{{#icon}}
{{#iconurl}}
<img alt="{{alttext}}" title="{{alttext}}" src="{{{ iconurl }}}" class="icon ">
<img alt="{{alttext}}" title="{{alttext}}" src="{{{ iconurl }}}" class="icon {{iconclass}}">
{{/iconurl}}
{{^iconurl}}
{{#pix}} {{key}}, {{component}}, {{alttext}} {{/pix}}
+3 -5
View File
@@ -8,11 +8,9 @@ Feature: Verify that the day and month names are displayed using UTF-8
Given remote langimport tests are enabled
Scenario Outline: View the calendar for December 2012 with correct UTF-8
Given I log in as "admin"
And I navigate to "Language > Language packs" in site administration
And I set the field "Available language packs" to "<lang>"
And I press "Install selected language pack(s)"
And I should see "Language pack '<lang>' was successfully installed"
Given the following "language pack" exists:
| language | <lang> |
And I log in as "admin"
And I follow "Preferences" in the user menu
And I follow "Preferred language"
And I set the field "Preferred language" to "<lang>"
@@ -0,0 +1,75 @@
@core @core_calendar @javascript
Feature: Perform calendar filter actions
In order to test the calendar filters with preselected options
As a user
I need to filter calendar events by certain criteria
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "categories" exist:
| name | idnumber | category |
| Year | year | |
| Department C1 | department-c1 | year |
And the following "courses" exist:
| fullname | shortname | format | category |
| Course 1 | C1 | topics | department-c1 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And the following "groups" exist:
| name | course | idnumber |
| Group 1 | C1 | G1 |
And the following "group members" exist:
| user | group |
| teacher1 | G1 |
And the following "events" exist:
| name | eventtype |
| Site event | site |
And the following "events" exist:
| name | eventtype | course |
| C1 event | course | C1 |
And the following "events" exist:
| name | eventtype | category |
| Dep1a event | category | department-c1 |
And the following "events" exist:
| name | eventtype | group | course |
| Group1 event | group | G1 | C1 |
Scenario: Teacher of a Course can see his events and filter them
Given I log in as "teacher1"
When I follow "Calendar" in the user menu
Then I should see "C1 event"
And I should see "Dep1a event"
And I should see "Group1 event"
And I click on "Hide category events" "link"
And I should see "C1 event"
And I should not see "Dep1a event"
And I should see "Group1 event"
And I click on "Hide course events" "link"
And I should not see "C1 event"
And I should not see "Dep1a event"
And I should see "Group1 event"
Scenario: Teacher of a Course can see only non filtered events from user preferences calendar_savedflt
Given I log in as "teacher1"
And the following "user preferences" exist:
| user | preference | value |
| teacher1 | calendar_persistflt | 1 |
| teacher1 | calendar_savedflt | 13 |
When I follow "Calendar" in the user menu
Then I should not see "C1 event"
And I should see "Dep1a event"
And I should not see "Group1 event"
Scenario: Teacher of a Course can see all events because session is used and not user preference calendar_savedflt
Given I log in as "teacher1"
And the following "user preferences" exist:
| user | preference | value |
| teacher1 | calendar_persistflt | 0 |
| teacher1 | calendar_savedflt | 13 |
When I follow "Calendar" in the user menu
Then I should see "C1 event"
And I should see "Dep1a event"
And I should see "Group1 event"
+4
View File
@@ -70,6 +70,10 @@ $CFG->dboptions = array(
// can be removed for MySQL (by default it will
// use 'utf8mb4_unicode_ci'. This option should
// be removed for all other databases.
// 'extrainfo' => [], // Extra information for the DB driver, e.g. SQL Server,
// has additional configuration according to its environment,
// which the administrator can specify to alter and
// override any connection options.
// 'fetchbuffersize' => 100000, // On PostgreSQL, this option sets a limit
// on the number of rows that are fetched into
// memory when doing a large recordset query
+9 -13
View File
@@ -14,26 +14,20 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* core_contentbank specific renderers
*
* @package core_contentbank
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_contentbank\output;
use context_coursecat;
use core_contentbank\content;
use core_contentbank\contentbank;
use renderable;
use templatable;
use renderer_base;
use stdClass;
use core_contentbank\content;
/**
* Class containing data for bank content
*
* @package core_contentbank
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -138,7 +132,8 @@ class bankcontent implements renderable, templatable {
}
$options = [];
foreach ($this->allowedcategories as $allowedcategory) {
$options[$allowedcategory->ctxid] = $allowedcategory->name;
$options[$allowedcategory->ctxid] = format_string($allowedcategory->name, true,
context_coursecat::instance($allowedcategory->ctxinstance));
}
if (!empty($options)) {
$allowedcontexts['categories'] = [get_string('coursecategories') => $options];
@@ -154,14 +149,15 @@ class bankcontent implements renderable, templatable {
$allowedcontexts['courses'] = [get_string('courses') => $options];
}
if (!empty($allowedcontexts)) {
$url = new \moodle_url('/contentbank/index.php');
$strchoosecontext = get_string('choosecontext', 'core_contentbank');
$singleselect = new \single_select(
$url,
new \moodle_url('/contentbank/index.php'),
'contextid',
$allowedcontexts,
$this->context->id,
get_string('choosecontext', 'core_contentbank')
$strchoosecontext
);
$singleselect->set_label($strchoosecontext, ['class' => 'sr-only']);
$data->allowedcontexts = $singleselect->export_for_template($output);
}
+8 -9
View File
@@ -14,16 +14,10 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Class containing data for a content view.
*
* @package core_contentbank
* @copyright 2020 Victor Deniz <victor@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_contentbank\output;
use context;
use core_contentbank\content;
use core_contentbank\contenttype;
use moodle_url;
@@ -180,8 +174,13 @@ class viewcontent implements renderable, templatable {
$data->editcontenturl = $editcontenturl->out(false);
}
$closeurl = new moodle_url('/contentbank/index.php', ['contextid' => $this->content->get_contextid()]);
$data->closeurl = $closeurl->out(false);
// Close/exit link for those users who can access that context.
$context = context::instance_by_id($this->content->get_contextid());
if (has_capability('moodle/contentbank:access', $context)) {
$closeurl = new moodle_url('/contentbank/index.php', ['contextid' => $context->id]);
$data->closeurl = $closeurl->out(false);
}
$data->actionmenu = $this->get_edit_actions_dropdown();
$data->heading = $this->content->get_name();
if ($this->content->get_visibility() == content::VISIBILITY_UNLISTED) {
@@ -54,31 +54,28 @@ along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}
}}
{{#heading}}<h2>{{heading}}</h2>{{/heading}}
{{#usercanedit}}
<div class="cb-toolbar-container container-fluid my-2">
<div class="row">
<div class="d-flex">
{{#usercanedit}}
<div class="d-flex mr-3">
<a href="{{editcontenturl}}" class="btn btn-primary" data-action="edit-content">
{{#str}}edit{{/str}}
</a>
</div>
{{/usercanedit}}
{{#actionmenu}}
<div class="ml-3">
{{> core_contentbank/contentbankmenu }}
</div>
{{/actionmenu}}
{{#actionmenu}}
<div>
{{> core_contentbank/contentbankmenu }}
</div>
{{/actionmenu}}
{{#closeurl}}
<div class="ml-auto">
<a href="{{closeurl}}" class="btn btn-secondary" data-action="close-content">
{{#str}}exit, core_contentbank{{/str}}
</a>
</div>
{{/closeurl}}
</div>
</div>
{{/usercanedit}}
{{^usercanedit}}
{{#actionmenu}}
{{> core_contentbank/contentbankmenu }}
{{/actionmenu}}
{{/usercanedit}}
@@ -34,21 +34,20 @@ Feature: Navigate to different contexts in the content bank
And I add the "Navigation" block if not present
And I expand "Site pages" node
When I click on "Content bank" "link"
And "contextid" "select" should exist
And the "contextid" select box should contain "System"
And the "contextid" select box should contain "Cat 1"
And the "contextid" select box should contain "Cat 2"
And the "contextid" select box should contain "C0"
And the "contextid" select box should contain "C1"
And the "contextid" select box should contain "C2"
And the "Choose course or category" select box should contain "System"
And the "Choose course or category" select box should contain "Cat 1"
And the "Choose course or category" select box should contain "Cat 2"
And the "Choose course or category" select box should contain "C0"
And the "Choose course or category" select box should contain "C1"
And the "Choose course or category" select box should contain "C2"
And I should see "santjordi.h5p"
And I should not see "santjordi_rose.h5p"
And I should not see "Dragon.h5p"
And I set the field "contextid" to "Cat 1"
And I set the field "Choose course or category" to "Cat 1"
Then I should not see "santjordi.h5p"
And I should see "santjordi_rose.h5p"
And I should not see "Dragon.h5p"
And I set the field "contextid" to "C0"
And I set the field "Choose course or category" to "C0"
And I should not see "santjordi.h5p"
And I should not see "santjordi_rose.h5p"
And I should see "Dragon.h5p"
@@ -69,18 +68,17 @@ Feature: Navigate to different contexts in the content bank
And I add the "Navigation" block if not present
And I expand "Site pages" node
When I click on "Content bank" "link"
And "contextid" "select" should exist
And the "contextid" select box should contain "C0"
And the "contextid" select box should contain "C1"
And the "contextid" select box should not contain "System"
And the "contextid" select box should not contain "Cat 1"
And the "contextid" select box should not contain "Cat 2"
And the "contextid" select box should not contain "C2"
And the "Choose course or category" select box should contain "C0"
And the "Choose course or category" select box should contain "C1"
And the "Choose course or category" select box should not contain "System"
And the "Choose course or category" select box should not contain "Cat 1"
And the "Choose course or category" select box should not contain "Cat 2"
And the "Choose course or category" select box should not contain "C2"
And I should see "Dragon.h5p"
And I should not see "princess.h5p"
And I should not see "santjordi.h5p"
And I should not see "santjordi_rose.h5p"
And I set the field "contextid" to "C1"
And I set the field "Choose course or category" to "C1"
Then I should not see "Dragon.h5p"
And I should see "princess.h5p"
And I should not see "santjordi.h5p"
@@ -91,14 +89,13 @@ Feature: Navigate to different contexts in the content bank
And I am on the "C0" "Course" page logged in as "teacher"
And I expand "Site pages" node
When I click on "Content bank" "link"
And "contextid" "select" should exist
And the "contextid" select box should contain "C0"
And the "contextid" select box should contain "C1"
And the "contextid" select box should contain "Cat 1"
And the "contextid" select box should not contain "System"
And the "contextid" select box should not contain "Cat 2"
And the "contextid" select box should not contain "C2"
And the "Choose course or category" select box should contain "C0"
And the "Choose course or category" select box should contain "C1"
And the "Choose course or category" select box should contain "Cat 1"
And the "Choose course or category" select box should not contain "System"
And the "Choose course or category" select box should not contain "Cat 2"
And the "Choose course or category" select box should not contain "C2"
And I should see "Dragon.h5p"
And I set the field "contextid" to "Cat 1"
And I set the field "Choose course or category" to "Cat 1"
And I should not see "Dragon.h5p"
And I should see "santjordi_rose.h5p"
@@ -11,7 +11,7 @@ Feature: Store the content bank view preference
And I log in as "admin"
And I follow "Manage private files..."
And I upload "h5p/tests/fixtures/filltheblanks.h5p" file to "Files" filemanager
And I upload "h5p/tests/fixtures/greeting-card-887.h5p" file to "Files" filemanager
And I upload "h5p/tests/fixtures/greeting-card.h5p" file to "Files" filemanager
And I click on "Save changes" "button"
And I am on site homepage
And I turn editing mode on
@@ -35,7 +35,7 @@ Feature: Store the content bank view preference
And I click on "Upload" "link"
And I click on "Choose a file..." "button"
And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
And I click on "greeting-card-887.h5p" "link"
And I click on "greeting-card.h5p" "link"
And I set the field "Save as" to "greetingcard.h5p"
And I click on "Select this file" "button"
And I click on "Save changes" "button"
+27 -61
View File
@@ -28,9 +28,12 @@ namespace core_contentbank;
defined('MOODLE_INTERNAL') || die();
use advanced_testcase;
use context_block;
use context_course;
use context_coursecat;
use context_module;
use context_system;
use context_user;
use Exception;
global $CFG;
@@ -364,7 +367,7 @@ class contentbank_test extends advanced_testcase {
$this->resetAfterTest();
$this->setAdminUser();
$systemcontext = \context_system::instance();
$name = 'greeting-card-887.h5p';
$name = 'greeting-card.h5p';
// Create a dummy H5P file.
$dummyh5p = array(
@@ -637,72 +640,35 @@ class contentbank_test extends advanced_testcase {
/**
* Test the behaviour of is_context_allowed().
*
* @dataProvider context_provider
* @param \Closure $getcontext Get the context to check.
* @param bool $expectedresult Expected result.
*
* @covers ::is_context_allowed
*/
public function test_is_context_allowed(\Closure $getcontext, bool $expectedresult): void {
public function test_is_context_allowed(): void {
$this->resetAfterTest();
$cb = new contentbank();
$context = $getcontext();
$this->assertEquals($expectedresult, $cb->is_context_allowed($context));
}
/**
* Data provider for test_is_context_allowed().
*
* @return array
*/
public function context_provider(): array {
// System context.
$this->assertTrue($cb->is_context_allowed(context_system::instance()));
return [
'System context' => [
function (): \context {
return \context_system::instance();
},
true,
],
'User context' => [
function (): \context {
$user = $this->getDataGenerator()->create_user();
return \context_user::instance($user->id);
},
false,
],
'Course category context' => [
function (): \context {
$coursecat = $this->getDataGenerator()->create_category();
return \context_coursecat::instance($coursecat->id);
},
true,
],
'Course context' => [
function (): \context {
$course = $this->getDataGenerator()->create_course();
return \context_course::instance($course->id);
},
true,
],
'Module context' => [
function (): \context {
$course = $this->getDataGenerator()->create_course();
$module = $this->getDataGenerator()->create_module('page', ['course' => $course->id]);
return \context_module::instance($module->cmid);
},
false,
],
'Block context' => [
function (): \context {
$course = $this->getDataGenerator()->create_course();
$coursecontext = context_course::instance($course->id);
$block = $this->getDataGenerator()->create_block('online_users', ['parentcontextid' => $coursecontext->id]);
return \context_block::instance($block->id);
},
false,
],
];
// User context.
$user = $this->getDataGenerator()->create_user();
$this->assertFalse($cb->is_context_allowed(context_user::instance($user->id)));
// Category context.
$category = $this->getDataGenerator()->create_category();
$this->assertTrue($cb->is_context_allowed(context_coursecat::instance($category->id)));
// Course context.
$course = $this->getDataGenerator()->create_course(['category' => $category->id]);
$coursecontext = context_course::instance($course->id);
$this->assertTrue($cb->is_context_allowed($coursecontext));
// Module context.
$module = $this->getDataGenerator()->create_module('page', ['course' => $course->id]);
$this->assertFalse($cb->is_context_allowed(context_module::instance($module->cmid)));
// Block context.
$block = $this->getDataGenerator()->create_block('online_users', ['parentcontextid' => $coursecontext->id]);
$this->assertFalse($cb->is_context_allowed(context_block::instance($block->id)));
}
}
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+3 -2
View File
@@ -60,7 +60,7 @@ define(
// Meanwhile, we filter the migrated actions.
const componentActions = [
'moveSection', 'moveCm', 'addSection', 'deleteSection', 'sectionHide', 'sectionShow',
'cmHide', 'cmShow', 'cmStealth',
'cmHide', 'cmShow', 'cmStealth', 'cmMoveRight', 'cmMoveLeft',
];
// The course reactive instance.
@@ -82,6 +82,7 @@ define(
TOGGLE: '.toggle-display,.dropdown-toggle',
SECTIONLI: 'li.section',
SECTIONACTIONMENU: '.section_action_menu',
SECTIONITEM: '[data-for="section_title"]',
ADDSECTIONS: '.changenumsections [data-add-sections]',
SECTIONBADGES: '[data-region="sectionbadges"]',
};
@@ -981,7 +982,7 @@ define(
// The section and activity names are edited using inplace editable.
// The "update" jQuery event must be captured in order to update the course state.
$('body').on('updated', `${SELECTOR.SECTIONLI} [data-inplaceeditable]`, function(e) {
$('body').on('updated', `${SELECTOR.SECTIONLI} ${SELECTOR.SECTIONITEM} [data-inplaceeditable]`, function(e) {
if (e.ajaxreturn && e.ajaxreturn.itemid) {
const state = courseeditor.state;
const section = state.section.get(e.ajaxreturn.itemid);
@@ -26,6 +26,7 @@ namespace core_course\local\repository;
defined('MOODLE_INTERNAL') || die();
use core_component;
use core_course\local\entity\content_item;
use core_course\local\entity\lang_string_title;
use core_course\local\entity\string_title;
@@ -195,12 +196,20 @@ class content_item_readonly_repository implements content_item_readonly_reposito
$archetype = plugin_supports('mod', $mod->name, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER);
$purpose = plugin_supports('mod', $mod->name, FEATURE_MOD_PURPOSE, MOD_PURPOSE_OTHER);
$icon = 'monologo';
// Quick check for monologo icons.
// Plugins that don't have monologo icons will be displayed as is and CSS filter will not be applied.
$hasmonologoicons = core_component::has_monologo_icon('mod', $mod->name);
$iconclass = '';
if (!$hasmonologoicons) {
$iconclass = 'nofilter';
}
$contentitem = new content_item(
$mod->id,
$mod->name,
new lang_string_title("modulename", $mod->name),
new \moodle_url('/course/mod.php', ['id' => $course->id, 'add' => $mod->name]),
$OUTPUT->pix_icon('monologo', '', $mod->name, ['class' => 'icon activityicon']),
$OUTPUT->pix_icon($icon, '', $mod->name, ['class' => "activityicon $iconclass"]),
$help,
$archetype,
'mod_' . $mod->name,
@@ -81,7 +81,7 @@ class content_notification_task extends adhoc_task {
// Get module names in the user's language.
$modnames = get_module_types_names();
$a = [
'coursename' => get_course_display_name_for_list($course),
'coursename' => format_string(get_course_display_name_for_list($course), true, ['context' => $modcontext]),
'courselink' => (new \moodle_url('/course/view.php', ['id' => $course->id]))->out(false),
'modulename' => format_string($cm->name, $modcontext->id),
'moduletypename' => $modnames[$cm->modname],
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -8,6 +8,6 @@ define("core_courseformat/local/courseindex/cm",["exports","core_courseformat/lo
* @class core_courseformat/local/courseindex/cm
* @copyright 2021 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_dndcmitem=_interopRequireDefault(_dndcmitem),_templates=_interopRequireDefault(_templates),_prefetch=_interopRequireDefault(_prefetch),_config=_interopRequireDefault(_config);_prefetch.default.prefetchTemplate("core_courseformat/local/courseindex/cmcompletion");class Component extends _dndcmitem.default{create(){this.name="courseindex_cm",this.selectors={CM_NAME:"[data-for='cm_name']",CM_COMPLETION:"[data-for='cm_completion']"},this.classes={CMHIDDEN:"dimmed",LOCKED:"editinprogress",RESTRICTIONS:"restrictions",PAGEITEM:"pageitem"},this.id=this.element.dataset.id}static init(target,selectors){return new Component({element:document.getElementById(target),selectors:selectors})}stateReady(state){this.configDragDrop(this.id);const cm=state.cm.get(this.id),course=state.course;this._refreshCompletion({state:state,element:cm}),window.location.href!=cm.url&&window.location.href!="".concat(course.baseurl,"#").concat(cm.anchor)||(this.reactive.dispatch("setPageItem","cm",this.id),this.element.scrollIntoView({block:"center"})),_config.default.contextid!=_config.default.courseContextId&&_config.default.contextInstanceId==this.id&&(this.reactive.dispatch("setPageItem","cm",this.id,!0),this.element.scrollIntoView({block:"center"})),cm.uservisible||this.addEventListener(this.getElement(this.selectors.CM_NAME),"click",this._activityAnchor)}getWatchers(){return[{watch:"cm[".concat(this.id,"]:deleted"),handler:this.remove},{watch:"cm[".concat(this.id,"]:updated"),handler:this._refreshCm},{watch:"cm[".concat(this.id,"].completionstate:updated"),handler:this._refreshCompletion},{watch:"course.pageItem:updated",handler:this._refreshPageItem}]}_refreshCm(_ref){var _element$dragging,_element$locked,_element$hascmrestric;let{element:element}=_ref;this.element.classList.toggle(this.classes.CMHIDDEN,!element.visible),this.getElement(this.selectors.CM_NAME).innerHTML=element.name,this.element.classList.toggle(this.classes.DRAGGING,null!==(_element$dragging=element.dragging)&&void 0!==_element$dragging&&_element$dragging),this.element.classList.toggle(this.classes.LOCKED,null!==(_element$locked=element.locked)&&void 0!==_element$locked&&_element$locked),this.element.classList.toggle(this.classes.RESTRICTIONS,null!==(_element$hascmrestric=element.hascmrestrictions)&&void 0!==_element$hascmrestric&&_element$hascmrestric),this.locked=element.locked}_refreshPageItem(_ref2){let{element:element}=_ref2;if(!element.pageItem)return;const isPageId="cm"==element.pageItem.type&&element.pageItem.id==this.id;this.element.classList.toggle(this.classes.PAGEITEM,isPageId),isPageId&&!this.reactive.isEditing&&this.element.scrollIntoView({block:"nearest"})}async _refreshCompletion(_ref3){let{state:state,element:element}=_ref3;if(this.reactive.isEditing||!element.istrackeduser)return;const completionElement=this.getElement(this.selectors.CM_COMPLETION);if(completionElement.dataset.value==element.completionstate)return;const data=this.reactive.getExporter().cmCompletion(state,element);try{const{html:html,js:js}=await _templates.default.renderForPromise("core_courseformat/local/courseindex/cmcompletion",data);_templates.default.replaceNode(completionElement,html,js)}catch(error){throw error}}_activityAnchor(event){const cm=this.reactive.get("cm",this.id);if(document.getElementById(cm.anchor))return void setTimeout((()=>{this.reactive.dispatch("setPageItem","cm",cm.id)}),50);const course=this.reactive.get("course"),section=this.reactive.get("section",cm.sectionid);if(!section)return;const url="".concat(course.baseurl,"&section=").concat(section.number,"#").concat(cm.anchor);event.preventDefault(),window.location=url}}return _exports.default=Component,_exports.default}));
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_dndcmitem=_interopRequireDefault(_dndcmitem),_templates=_interopRequireDefault(_templates),_prefetch=_interopRequireDefault(_prefetch),_config=_interopRequireDefault(_config);_prefetch.default.prefetchTemplate("core_courseformat/local/courseindex/cmcompletion");class Component extends _dndcmitem.default{create(){this.name="courseindex_cm",this.selectors={CM_NAME:"[data-for='cm_name']",CM_COMPLETION:"[data-for='cm_completion']"},this.classes={CMHIDDEN:"dimmed",LOCKED:"editinprogress",RESTRICTIONS:"restrictions",PAGEITEM:"pageitem",INDENTED:"indented"},this.id=this.element.dataset.id}static init(target,selectors){return new Component({element:document.getElementById(target),selectors:selectors})}stateReady(state){this.configDragDrop(this.id);const cm=state.cm.get(this.id),course=state.course;this._refreshCompletion({state:state,element:cm});const anchor=new URL(window.location.href).hash.replace("#","");(window.location.href==cm.url||window.location.href.includes(course.baseurl)&&anchor==cm.anchor)&&(this.reactive.dispatch("setPageItem","cm",this.id),this.element.scrollIntoView({block:"center"})),_config.default.contextid!=_config.default.courseContextId&&_config.default.contextInstanceId==this.id&&(this.reactive.dispatch("setPageItem","cm",this.id,!0),this.element.scrollIntoView({block:"center"})),cm.uservisible||this.addEventListener(this.getElement(this.selectors.CM_NAME),"click",this._activityAnchor)}getWatchers(){return[{watch:"cm[".concat(this.id,"]:deleted"),handler:this.remove},{watch:"cm[".concat(this.id,"]:updated"),handler:this._refreshCm},{watch:"cm[".concat(this.id,"].completionstate:updated"),handler:this._refreshCompletion},{watch:"course.pageItem:updated",handler:this._refreshPageItem}]}_refreshCm(_ref){var _element$dragging,_element$locked,_element$hascmrestric;let{element:element}=_ref;this.element.classList.toggle(this.classes.CMHIDDEN,!element.visible),this.getElement(this.selectors.CM_NAME).innerHTML=element.name,this.element.classList.toggle(this.classes.DRAGGING,null!==(_element$dragging=element.dragging)&&void 0!==_element$dragging&&_element$dragging),this.element.classList.toggle(this.classes.LOCKED,null!==(_element$locked=element.locked)&&void 0!==_element$locked&&_element$locked),this.element.classList.toggle(this.classes.RESTRICTIONS,null!==(_element$hascmrestric=element.hascmrestrictions)&&void 0!==_element$hascmrestric&&_element$hascmrestric),this.element.classList.toggle(this.classes.INDENTED,element.indent),this.locked=element.locked}_refreshPageItem(_ref2){let{element:element}=_ref2;if(!element.pageItem)return;const isPageId="cm"==element.pageItem.type&&element.pageItem.id==this.id;this.element.classList.toggle(this.classes.PAGEITEM,isPageId),isPageId&&!this.reactive.isEditing&&this.element.scrollIntoView({block:"nearest"})}async _refreshCompletion(_ref3){let{state:state,element:element}=_ref3;if(this.reactive.isEditing||!element.istrackeduser)return;const completionElement=this.getElement(this.selectors.CM_COMPLETION);if(completionElement.dataset.value==element.completionstate)return;const data=this.reactive.getExporter().cmCompletion(state,element);try{const{html:html,js:js}=await _templates.default.renderForPromise("core_courseformat/local/courseindex/cmcompletion",data);_templates.default.replaceNode(completionElement,html,js)}catch(error){throw error}}_activityAnchor(event){const cm=this.reactive.get("cm",this.id);if(document.getElementById(cm.anchor))return void setTimeout((()=>{this.reactive.dispatch("setPageItem","cm",cm.id)}),50);const course=this.reactive.get("course"),section=this.reactive.get("section",cm.sectionid);if(!section)return;const url="".concat(course.baseurl,"&section=").concat(section.number,"#").concat(cm.anchor);event.preventDefault(),window.location=url}}return _exports.default=Component,_exports.default}));
//# sourceMappingURL=cm.min.js.map
File diff suppressed because one or more lines are too long
+1
View File
@@ -221,6 +221,7 @@ export default class Component extends BaseComponent {
// State changes that require to reload some course modules.
{watch: `cm.visible:updated`, handler: this._reloadCm},
{watch: `cm.stealth:updated`, handler: this._reloadCm},
{watch: `cm.indent:updated`, handler: this._reloadCm},
// Update section number and title.
{watch: `section.number:updated`, handler: this._refreshSectionNumber},
// Collapse and expand sections.
@@ -50,6 +50,8 @@ const directMutations = {
cmHide: 'cmHide',
cmShow: 'cmShow',
cmStealth: 'cmStealth',
cmMoveRight: 'cmMoveRight',
cmMoveLeft: 'cmMoveLeft',
};
export default class extends BaseComponent {
@@ -190,6 +192,8 @@ export default class extends BaseComponent {
event.preventDefault();
const pendingModalReady = new Pending(`courseformat/actions:prepareMoveSectionModal`);
// The section edit menu to refocus on end.
const editTools = this._getClosestActionMenuToogler(target);
@@ -242,6 +246,8 @@ export default class extends BaseComponent {
this.reactive.dispatch('sectionMove', [sectionId], target.dataset.id);
this._destroyModal(modal, editTools);
});
pendingModalReady.resolve();
}
/**
@@ -260,6 +266,8 @@ export default class extends BaseComponent {
event.preventDefault();
const pendingModalReady = new Pending(`courseformat/actions:prepareMoveCmModal`);
// The section edit menu to refocus on end.
const editTools = this._getClosestActionMenuToogler(target);
@@ -335,6 +343,8 @@ export default class extends BaseComponent {
this.reactive.dispatch('cmMove', [cmId], targetSectionId, targetCmId);
this._destroyModal(modal, editTools);
});
pendingModalReady.resolve();
}
/**
@@ -282,6 +282,24 @@ export default class {
this._setElementsValue(stateManager, 'cm', cmIds, 'completionstate', newValue);
}
/**
* Move cms to the right: indent = 1.
* @param {StateManager} stateManager the current state manager
* @param {array} cmIds the list of cm ids
*/
async cmMoveRight(stateManager, cmIds) {
await this._cmBasicAction(stateManager, 'cm_moveright', cmIds);
}
/**
* Move cms to the left: indent = 0.
* @param {StateManager} stateManager the current state manager
* @param {array} cmIds the list of cm ids
*/
async cmMoveLeft(stateManager, cmIds) {
await this._cmBasicAction(stateManager, 'cm_moveleft', cmIds);
}
/**
* Lock or unlock course modules.
*
@@ -52,6 +52,7 @@ export default class Component extends DndCmItem {
LOCKED: 'editinprogress',
RESTRICTIONS: 'restrictions',
PAGEITEM: 'pageitem',
INDENTED: 'indented',
};
// We need our id to watch specific events.
this.id = this.element.dataset.id;
@@ -85,8 +86,12 @@ export default class Component extends DndCmItem {
state,
element: cm,
});
const url = new URL(window.location.href);
const anchor = url.hash.replace('#', '');
// Check if the current url is the cm url.
if (window.location.href == cm.url || window.location.href == `${course.baseurl}#${cm.anchor}`) {
if (window.location.href == cm.url
|| (window.location.href.includes(course.baseurl) && anchor == cm.anchor)
) {
this.reactive.dispatch('setPageItem', 'cm', this.id);
this.element.scrollIntoView({block: "center"});
}
@@ -132,6 +137,7 @@ export default class Component extends DndCmItem {
this.element.classList.toggle(this.classes.DRAGGING, element.dragging ?? false);
this.element.classList.toggle(this.classes.LOCKED, element.locked ?? false);
this.element.classList.toggle(this.classes.RESTRICTIONS, element.hascmrestrictions ?? false);
this.element.classList.toggle(this.classes.INDENTED, element.indent);
this.locked = element.locked;
}
-8
View File
@@ -456,14 +456,6 @@ abstract class base {
/**
* Returns true if this course format uses activity indentation.
*
* Indentation is not supported by core formats anymore and may be deprecated in the future.
* This method will keep a default return "true" for legacy reasons but new formats should override
* it with a return false to prevent future deprecations.
*
* A message in a bottle: if indentation is finally deprecated, both behat steps i_indent_right_activity
* and i_indent_left_activity should be removed as well. Right now no core behat uses them but indentation
* is not officially deprecated so they are still available for the contrib formats.
*
* @return bool if the course format uses indentation.
*/
public function uses_indentation(): bool {
@@ -102,9 +102,12 @@ class cmname implements named_templatable, renderable {
return [];
}
$iconurl = $mod->get_icon_url();
$iconclass = $iconurl->get_param('filtericon') ? '' : 'nofilter';
$data = [
'url' => $mod->url,
'icon' => $mod->get_icon_url(),
'icon' => $iconurl,
'iconclass' => $iconclass,
'modname' => $mod->modname,
'textclasses' => $displayoptions['textclasses'] ?? '',
'purpose' => plugin_supports('mod', $mod->modname, FEATURE_MOD_PURPOSE, MOD_PURPOSE_OTHER),
@@ -106,6 +106,7 @@ class cmitem implements named_templatable, renderable {
'extraclasses' => $mod->extraclasses,
'cmformat' => $item->export_for_template($output),
'hasinfo' => $hasinfo,
'indent' => ($format->uses_indentation()) ? $mod->indent : 0,
];
}
}
@@ -90,6 +90,9 @@ class cm implements renderable {
'sectionnumber' => $section->section,
'uservisible' => $cm->uservisible,
'hascmrestrictions' => $this->get_has_restrictions(),
'module' => $cm->modname,
'plugin' => 'mod_' . $cm->modname,
'indent' => ($format->uses_indentation()) ? $cm->indent : 0,
];
// Check the user access type to this cm.
+73
View File
@@ -422,6 +422,79 @@ class stateactions {
}
}
/**
* Move course cms to the right. Indent = 1.
*
* @param stateupdates $updates the affected course elements track
* @param stdClass $course the course object
* @param int[] $ids cm ids
* @param int $targetsectionid not used
* @param int $targetcmid not used
*/
public function cm_moveright(
stateupdates $updates,
stdClass $course,
array $ids = [],
?int $targetsectionid = null,
?int $targetcmid = null
): void {
$this->set_cm_indentation($updates, $course, $ids, 1);
}
/**
* Move course cms to the left. Indent = 0.
*
* @param stateupdates $updates the affected course elements track
* @param stdClass $course the course object
* @param int[] $ids cm ids
* @param int $targetsectionid not used
* @param int $targetcmid not used
*/
public function cm_moveleft(
stateupdates $updates,
stdClass $course,
array $ids = [],
?int $targetsectionid = null,
?int $targetcmid = null
): void {
$this->set_cm_indentation($updates, $course, $ids, 0);
}
/**
* Internal method to define the cm indentation level.
*
* @param stateupdates $updates the affected course elements track
* @param stdClass $course the course object
* @param int[] $ids cm ids
* @param int $indent new value for indentation
*/
protected function set_cm_indentation(
stateupdates $updates,
stdClass $course,
array $ids,
int $indent
): void {
global $DB;
$this->validate_cms($course, $ids, __FUNCTION__);
// Check capabilities on every activity context.
foreach ($ids as $cmid) {
$modcontext = context_module::instance($cmid);
require_capability('moodle/course:manageactivities', $modcontext);
}
$modinfo = get_fast_modinfo($course);
$cms = $this->get_cm_info($modinfo, $ids);
list($insql, $inparams) = $DB->get_in_or_equal(array_keys($cms), SQL_PARAMS_NAMED);
$DB->set_field_select('course_modules', 'indent', $indent, "id $insql", $inparams);
rebuild_course_cache($course->id, false, true);
foreach ($cms as $cm) {
$modcontext = context_module::instance($cm->id);
course_module_updated::create_from_cm($cm, $modcontext)->trigger();
$updates->add_cm_put($cm->id);
}
}
/**
* Extract several cm_info from the course_modinfo.
*

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