Compare commits
744 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b2d0712748 | |||
| f70a6d32cd | |||
| f691ccf8c6 | |||
| ceb2f856b0 | |||
| 955d89710e | |||
| 526e1af88b | |||
| b142840de1 | |||
| 8fb9177b74 | |||
| 6e15a26dce | |||
| b5a65e56fd | |||
| a6a0e98e53 | |||
| c28cd3215b | |||
| 39084a098b | |||
| 9290627da9 | |||
| bb5bd2eed8 | |||
| 12195684b4 | |||
| f01ad40145 | |||
| 1d64897a3c | |||
| 339dfec420 | |||
| 1d16c049f3 | |||
| 4d4635228d | |||
| ce9cb1156c | |||
| c0429c1c90 | |||
| be8c02fc2c | |||
| 99b5e1059d | |||
| ee519b690c | |||
| 814c63fcc3 | |||
| 799e962f1f | |||
| c0bea2a82d | |||
| e9fcdec98b | |||
| fc6764324a | |||
| b71f01981a | |||
| 5d1fcb942a | |||
| 24335d67bb | |||
| 1b828701a6 | |||
| f8e468c41a | |||
| b07df211ce | |||
| 4c289966d3 | |||
| 0d0fbec616 | |||
| ca751bdc1f | |||
| bc85007834 | |||
| dd553c1406 | |||
| 76ab8a1c51 | |||
| 242ecbf7f3 | |||
| cbfde8c11a | |||
| df4f8dd322 | |||
| 60ab09ac87 | |||
| 40fad4d6b7 | |||
| 1265b0ad84 | |||
| faa121ad30 | |||
| 797cbcc3da | |||
| 6bac910434 | |||
| a9b4b297ac | |||
| 073deca0e1 | |||
| 2cc1182b16 | |||
| 82a7b26648 | |||
| 2db1ddd290 | |||
| 96f0478ec1 | |||
| 38f5c818cb | |||
| 38c35247e9 | |||
| 242dcfbc55 | |||
| 2f601f1e51 | |||
| 31a3c8cda4 | |||
| 39cf556111 | |||
| 4c4be40c77 | |||
| c8f105800c | |||
| 3502ad3da7 | |||
| 15a5a938b6 | |||
| ec4eacd1da | |||
| bc34e3fef3 | |||
| 6aef92c24a | |||
| 4b7c58febf | |||
| 4ed6c8fbce | |||
| af585fc755 | |||
| 6ae5ee18a0 | |||
| ead7958219 | |||
| 8dc9cd6cdc | |||
| 69749509ab | |||
| 59eac981eb | |||
| ab0038da84 | |||
| 2cde957897 | |||
| a8eb481980 | |||
| 4c533b8204 | |||
| 26aaa7486a | |||
| f336875b1a | |||
| 6381daf52a | |||
| 5b8ad6ae30 | |||
| 3a38791d62 | |||
| 5563e99f2c | |||
| 8bc1c231d6 | |||
| 1226208a64 | |||
| 725277eb8b | |||
| b631966a70 | |||
| b796cbd03c | |||
| 1066f5fe68 | |||
| 202718f968 | |||
| 44d734147a | |||
| 346cb39cff | |||
| eb12428324 | |||
| 8b31922739 | |||
| 127174088a | |||
| f8e2445513 | |||
| cb8bf1e001 | |||
| 1f7063cc09 | |||
| 60954253d4 | |||
| d49ebb79c5 | |||
| 85a7c17414 | |||
| c9b7b94ed4 | |||
| 0bc265e900 | |||
| 6417d795b9 | |||
| c55473ad2e | |||
| ddaf4b7a58 | |||
| be58d68f20 | |||
| 131d80441a | |||
| 8a588f963c | |||
| 4e32a42415 | |||
| aa9a462a4b | |||
| 571c59b60e | |||
| 360f9e37f8 | |||
| 65ba7df04b | |||
| 02aed7f6fc | |||
| 2a4f86051b | |||
| 701541f35b | |||
| 839681dc77 | |||
| b33e5466df | |||
| fbbdfefdb6 | |||
| 8656271a63 | |||
| e1f3e7f232 | |||
| f6c4303c58 | |||
| 134dc470c7 | |||
| 45cd887f3b | |||
| b65ec774c5 | |||
| 215e9f0688 | |||
| 035ebed95b | |||
| a78f3a02c6 | |||
| 2890546f44 | |||
| a1df38dfc6 | |||
| 82f9585f55 | |||
| 0f092b86b5 | |||
| 74f3c34cbf | |||
| 1f337e7665 | |||
| fbb6dc4ca3 | |||
| ab3a244568 | |||
| 7b13a543ad | |||
| dd10dead0f | |||
| b84a48d1ee | |||
| 3794210019 | |||
| cca75ca956 | |||
| 93c91af6a9 | |||
| 8755738360 | |||
| 4f8c5ef086 | |||
| 5d10e53ba1 | |||
| cc0c62d564 | |||
| 48fb5b6e88 | |||
| bc4b6cf8f7 | |||
| f17b006dcb | |||
| d50bb07b2b | |||
| b85f6a660b | |||
| dd584ab981 | |||
| ab9b09908a | |||
| c99abd65ca | |||
| 436ed2c4cb | |||
| d0ddc7692b | |||
| 449a8b0b31 | |||
| 0f251ce165 | |||
| 5df8e8fa5f | |||
| 7f6888bd53 | |||
| 95d5b0aab0 | |||
| 9a05e393dd | |||
| 110ec6a703 | |||
| 714764d966 | |||
| ae63139985 | |||
| e144ff8a43 | |||
| cd528df093 | |||
| 6b96cf8676 | |||
| 2b0a0cecdf | |||
| 7b614d6f51 | |||
| e7cda153a2 | |||
| 0d6eaef200 | |||
| f32c46bf0c | |||
| eeae99afc7 | |||
| 8c9b54cc5d | |||
| da42c76300 | |||
| c2714ff1af | |||
| f2cbacbd24 | |||
| bc1d7e854b | |||
| 99bc8f2b30 | |||
| 67f739499b | |||
| 538e17e199 | |||
| 28d8c9c1e7 | |||
| 18703b71ac | |||
| f59220a76d | |||
| 84198e03c7 | |||
| f5d445a68f | |||
| 30b0b0cf40 | |||
| 69948eda10 | |||
| 1f4daa7402 | |||
| c1c1ca6e4d | |||
| 50d1671a54 | |||
| fd9b8bf4d0 | |||
| e405e6fd6a | |||
| 5898c3e5dd | |||
| d2bc54fba9 | |||
| 9b07f56add | |||
| 7c1c1071e7 | |||
| d2d49e150b | |||
| a7d71e0e2d | |||
| b924f6a355 | |||
| 3fa384384e | |||
| 66e88ace0f | |||
| fe07140e27 | |||
| fad4e118a4 | |||
| 0d80eea9af | |||
| 0b973b52e9 | |||
| b158354710 | |||
| ee3924702f | |||
| 0d4d201a8c | |||
| 0b2085648e | |||
| 939a416308 | |||
| 28d1db2f1d | |||
| 084c120c79 | |||
| 29a1cf86a3 | |||
| ea915c29e5 | |||
| deced83b06 | |||
| fac1828222 | |||
| 502536c16e | |||
| 3fee9d949f | |||
| 1bfc0a1d14 | |||
| f8301fb4bc | |||
| 9e97a353ad | |||
| 6e27e9e481 | |||
| e8cbc0b653 | |||
| b296667ebf | |||
| 231c25e498 | |||
| 3ff1293c12 | |||
| eda00e2c73 | |||
| 85b4e2312e | |||
| 5707e7d40f | |||
| 71e751565c | |||
| b3adaaf47f | |||
| 282df774a4 | |||
| 9f5492df71 | |||
| 0920bd0810 | |||
| a89cba84a1 | |||
| f0b3ff2134 | |||
| dbd7cedd24 | |||
| d5331827c4 | |||
| 91f503c3f6 | |||
| 4ee053a278 | |||
| 4bc3782c4c | |||
| 5a7ad7b284 | |||
| 34c452afb4 | |||
| 6c260e6ae7 | |||
| 09e74578a1 | |||
| f1a9181a77 | |||
| 8ee689f612 | |||
| a003b3b882 | |||
| 9617555887 | |||
| a5238d8c94 | |||
| 34e2d74921 | |||
| 76cb263244 | |||
| 4ce5755a63 | |||
| a509b37926 | |||
| 7e96a1c887 | |||
| 033858e249 | |||
| 83c3ac53bf | |||
| 1e5f8ee1a2 | |||
| d5d64e1c8b | |||
| f8b1b4092f | |||
| ca01668e32 | |||
| 70108cf09b | |||
| b9dedcd53e | |||
| aca52fa5f0 | |||
| 65cb8e0792 | |||
| 9bdbff4f3a | |||
| 47a1789f12 | |||
| 394a0520d0 | |||
| 79491b8a90 | |||
| 68460cad9b | |||
| 23e2b0358c | |||
| 98f95845b1 | |||
| 69a91b30cc | |||
| 2ff317ac8a | |||
| 8112ce8376 | |||
| 5feea19e46 | |||
| d5b43c03a5 | |||
| f262b2223f | |||
| 3c1c543c53 | |||
| 370c67e7dc | |||
| ab34f215a9 | |||
| 002f9eb7da | |||
| bb61d8392b | |||
| bdd1708455 | |||
| 3c86170a8d | |||
| 155f08fc68 | |||
| 3a8b5523d5 | |||
| fbc3f7f245 | |||
| 42f41fc174 | |||
| ed6b43b4f2 | |||
| 6a25c5c8d0 | |||
| 3a363d0eec | |||
| 783c542d31 | |||
| cd48ae56c7 | |||
| d5cf25faf5 | |||
| 787278601c | |||
| 880532c71a | |||
| 03b549f225 | |||
| 7b6ff446f4 | |||
| 9ddbc2d7f0 | |||
| fbffd9b148 | |||
| 2ad0871a82 | |||
| 5847b0026e | |||
| 6c3f1d55a8 | |||
| 04787fbbc9 | |||
| 8c13a1b817 | |||
| fefd6f2bd8 | |||
| d209e2cc57 | |||
| a96fcc22ca | |||
| 230f8247e8 | |||
| 91eb06347e | |||
| 63ecbc57d6 | |||
| f483380e01 | |||
| 0da20453f7 | |||
| c90a6263bc | |||
| df1dc82fee | |||
| 34cae8d339 | |||
| 733702e72f | |||
| 7db1da61ee | |||
| 6aafa1eea6 | |||
| ed08a4ee52 | |||
| 4eb1b67d68 | |||
| 598b18417d | |||
| cd609bd915 | |||
| 5e4d3c4d5b | |||
| 311eb0ddd2 | |||
| c90c9b04f6 | |||
| 6d70b125a8 | |||
| 38cc21db3c | |||
| 3caeef44d2 | |||
| 9a6fdc2aeb | |||
| 5e57bfffac | |||
| fac46a19f7 | |||
| 01fc104779 | |||
| e7e5b659e8 | |||
| b9f8373e3f | |||
| 0292427f51 | |||
| 0518f875cf | |||
| c2d0c7359d | |||
| d5ec540381 | |||
| 3c00ca0bb2 | |||
| 3800bd6cfd | |||
| 9fbd5fb219 | |||
| bdcf29ab29 | |||
| 795e8ac70a | |||
| 550d9c51ca | |||
| e3066f3efd | |||
| acc250a824 | |||
| 602061bf21 | |||
| 7a5f859f08 | |||
| 7f4d60b38a | |||
| ca6167bd2d | |||
| 99a6ea40ac | |||
| f7ac8b3ff9 | |||
| 1818a6841b | |||
| f46fe1e78e | |||
| 35ea7d00c0 | |||
| 859f2c9d5d | |||
| 91ae0abd0a | |||
| f9ec44d858 | |||
| 98f10149d6 | |||
| 192bad5ba2 | |||
| 633ad42d6e | |||
| 21ef5c5f84 | |||
| a00d72c622 | |||
| f49f934786 | |||
| 74fe22032b | |||
| 2169252d26 | |||
| 889290257d | |||
| 8422698a06 | |||
| 05cdfa4da4 | |||
| a377bf30c4 | |||
| 288620b0b4 | |||
| b963401b8f | |||
| 364458e36a | |||
| ccec093aaa | |||
| 3b9a4c5e69 | |||
| 74fe0b04be | |||
| 4e8cd1c7da | |||
| 841f61af08 | |||
| 0792cbf3c8 | |||
| 1eed4bca84 | |||
| d784e19d9b | |||
| 1e26496cdd | |||
| 23915a35ae | |||
| 3ebd25ff48 | |||
| 7e3d9db579 | |||
| b9ee80aae7 | |||
| 746580aa68 | |||
| a31b0acaa5 | |||
| b4944cadca | |||
| 4dda4e69fe | |||
| 044e573ea1 | |||
| 12843909d1 | |||
| ba428c3255 | |||
| a3b8e7867a | |||
| 58d27b5e04 | |||
| d35affa0fd | |||
| 2c290f4bb8 | |||
| 73f5e82992 | |||
| 855839d195 | |||
| d07d010288 | |||
| 576afc3ba6 | |||
| 3df3b9dfb2 | |||
| 61f38c0663 | |||
| 4ade597a20 | |||
| 9e79eff429 | |||
| 35a59dd032 | |||
| bac48ffee3 | |||
| 2a0980837d | |||
| cfc2fca94a | |||
| be32856374 | |||
| 5ab061ca7d | |||
| 8045e5e3dd | |||
| 03035dcf71 | |||
| 93525ebdd9 | |||
| 21afd4548b | |||
| 1ca40fa664 | |||
| 3c262bb46f | |||
| 9d5e363b41 | |||
| 73181c1310 | |||
| c2c1727dd5 | |||
| 89f7402fc2 | |||
| 8960a6ee91 | |||
| 0a0266ae5e | |||
| bf81d5f0ba | |||
| 644e9da8bb | |||
| ca4614c299 | |||
| 456446215e | |||
| 3416ee9890 | |||
| b8a85811ec | |||
| bef0d516b4 | |||
| cf25f53743 | |||
| 53c2d0ad15 | |||
| 72f7128665 | |||
| c91c4b39e9 | |||
| 4e5ed8fdcd | |||
| 76b8862518 | |||
| 7aeb3a8b6e | |||
| a8bb233a56 | |||
| c7d9fa690d | |||
| a31f5830bd | |||
| 47db6bc3f9 | |||
| 3258f4cf3e | |||
| 96ca10cc73 | |||
| 6c97d7ba5b | |||
| 01cdda182c | |||
| a47e5311af | |||
| 296ed92898 | |||
| 1e28db3e01 | |||
| 1f88fcdb09 | |||
| 352db923e1 | |||
| a0c79271fc | |||
| 9d97d0c519 | |||
| 352affa056 | |||
| f8d2c0207b | |||
| 506604c911 | |||
| 183b705fe4 | |||
| f559c0ad59 | |||
| 2781ef309c | |||
| 58d85f21da | |||
| c4de73821a | |||
| 291d241981 | |||
| d6f0ad4980 | |||
| 40d6ba9505 | |||
| 32bf205db3 | |||
| 30cd583d5d | |||
| 45877b9c1b | |||
| c5916d6303 | |||
| c3504d5e4c | |||
| fa9ef9baf8 | |||
| fe9bc4d5c0 | |||
| dadceea8f5 | |||
| 20d103f014 | |||
| 6e3242d677 | |||
| d79fbf80a2 | |||
| 2bf6bfcac8 | |||
| de4739338a | |||
| bc30c47ad7 | |||
| d227c27ca7 | |||
| 9829e77a6d | |||
| f6d38b13a2 | |||
| ddcd60552b | |||
| cc4b43a57b | |||
| 4ca23b917b | |||
| 507306a6ca | |||
| efe9ac66be | |||
| 3312a6814a | |||
| 995d6a3301 | |||
| 0d7642b8e8 | |||
| 3da0488fbc | |||
| 9be10c0dd1 | |||
| 3ff714acbe | |||
| 75f729911e | |||
| 854f67cf65 | |||
| 476abf4610 | |||
| 09534ee3f9 | |||
| fc3aaf9c67 | |||
| 1b1e1801b7 | |||
| 5f61f7f3e7 | |||
| 41c1731431 | |||
| 9cb0e85cf9 | |||
| dd9073a232 | |||
| de285364c5 | |||
| 8d585d0c79 | |||
| 960bff3227 | |||
| eed3f09aa0 | |||
| a001500897 | |||
| 8c16eb18f6 | |||
| 927b0fc34d | |||
| a457f1b1cd | |||
| 946504b58d | |||
| ae1447060e | |||
| e9e2c481fd | |||
| 16e62aa2ad | |||
| 5432766411 | |||
| 2a77c6f1d5 | |||
| e9a32f3463 | |||
| 42c58dce39 | |||
| be6cb6ab13 | |||
| 51254db574 | |||
| cffc56a17c | |||
| ff449751d9 | |||
| a869fbf33e | |||
| bdcc5b7a59 | |||
| 8d9a898bb5 | |||
| 35792ea06a | |||
| 9f7219844e | |||
| 21986200d7 | |||
| 94bb9b2b73 | |||
| fde83c33a8 | |||
| 301fa85c26 | |||
| 7bee650a24 | |||
| 46d8e08a16 | |||
| eb84455a05 | |||
| 3da5ef8e9c | |||
| 2b568f1c93 | |||
| ec02c39dbb | |||
| 0dc15ee619 | |||
| 1cddc8edcb | |||
| fbe9dc4ecf | |||
| 7906636a3a | |||
| 58814af9f1 | |||
| df96d98c39 | |||
| d3040a763a | |||
| c3b36c6a34 | |||
| 1ee066cecd | |||
| 72c8793ae7 | |||
| 130f5c1dd6 | |||
| 96aaae3f19 | |||
| 52ed4a47f7 | |||
| daa7e5b4e2 | |||
| ab619ae38d | |||
| 6cb71e3e8b | |||
| 3b4246cd89 | |||
| d17117ab02 | |||
| 2158f5c2e6 | |||
| dbe40db138 | |||
| 53ad452d6c | |||
| a574be993c | |||
| d9d9d42c81 | |||
| 0b586e839d | |||
| 22141c2f8d | |||
| 61f1700fbf | |||
| 0ce350e78a | |||
| d2c0dd4f6b | |||
| 5f5355065a | |||
| b9dece93d8 | |||
| b84ad66fbe | |||
| 7b787cd99f | |||
| 6e72a4ed50 | |||
| d69579a060 | |||
| 5f3524f3df | |||
| bdd380ffea | |||
| cd0d9b910e | |||
| 177f619b12 | |||
| b21e31b016 | |||
| 6ce00e289f | |||
| f5e6601ab5 | |||
| f23463f8d6 | |||
| a41a3281de | |||
| f24f47992a | |||
| ec9f3e8976 | |||
| e8e1faad78 | |||
| a8ff0f7271 | |||
| 79b9e6f36e | |||
| 9fe490b715 | |||
| ae355c5749 | |||
| f93fa35ecf | |||
| cb6dc699b8 | |||
| 9cc3769ba6 | |||
| 562d62474e | |||
| 226bfb0348 | |||
| 39e58054b1 | |||
| c0c478acb4 | |||
| d7e9b065cb | |||
| aa1b0c5ebd | |||
| 1780d6a5e8 | |||
| c4840c6b0c | |||
| f0d9073159 | |||
| b90bccd3d8 | |||
| 7e7628b806 | |||
| 920ac6dbe5 | |||
| 662d2e1f0f | |||
| e247320845 | |||
| e87a6484bf | |||
| 0a66a77054 | |||
| 4bd7c57be5 | |||
| 6f073ebe6d | |||
| 885d191df2 | |||
| 3ef9c4c5d0 | |||
| e4e7ec5efb | |||
| a3c9194bef | |||
| c125d5f3f2 | |||
| 4b9e614eaf | |||
| 858b26feb2 | |||
| a6a6fbc5ec | |||
| 242060661a | |||
| e7f9631b7e | |||
| f37f6e3439 | |||
| b59bb37819 | |||
| 03a1ca582b | |||
| e3dd47671c | |||
| fc2a1536f7 | |||
| bdd3e5efb1 | |||
| 1753e85f1a | |||
| 725af41225 | |||
| 5a6481006d | |||
| d350fcc040 | |||
| 2ea379e0b0 | |||
| 514b6f13d9 | |||
| b20ca4b471 | |||
| 421ae9cb83 | |||
| f5dc8c4f9f | |||
| 7dfb4c229c | |||
| df1d34bea8 | |||
| 6acc34a097 | |||
| 0338a74dfb | |||
| 06c3d068a6 | |||
| ed544dd0f3 | |||
| 5cd9e69b6a | |||
| a6bd5ae245 | |||
| cd0935a33e | |||
| db861ee6d3 | |||
| 2307060239 | |||
| d78d372a1e | |||
| 8b31b8e8d0 | |||
| 62118364f2 | |||
| 0a4f551fff | |||
| d8f7fb803f | |||
| 64293da947 | |||
| dcd48987f7 | |||
| 67bbf6c416 | |||
| d238db5f36 | |||
| 522ee5ae4c | |||
| deed4fe251 | |||
| a360fd0601 | |||
| 5e75da7598 | |||
| 928633a33e | |||
| 6bf5c32402 | |||
| 6d90fc7c5b | |||
| 0a86c6a123 | |||
| 4b43da100c | |||
| addbbfdaae | |||
| 0006557b59 | |||
| 0813b938a3 | |||
| 710d0ef8f2 | |||
| fa51e1a149 | |||
| 066740a127 | |||
| 26754aacd1 | |||
| e0358d2eca | |||
| 3fd5c885a1 | |||
| 40081f5342 | |||
| 1aa113fd1f | |||
| 36b4f13e9b | |||
| 03bc275093 | |||
| e6dfa9ff3c | |||
| 8f4bd4ce11 | |||
| 77a0a535b3 | |||
| a4ea607c24 | |||
| dbfb5eaa45 | |||
| 788d86d7a3 | |||
| a1f4f7bac5 | |||
| d08319ddf7 | |||
| de4de9cec5 | |||
| ddf0f08cc9 | |||
| 2250ab07e6 | |||
| 71c1fa0d8e | |||
| 5fbd2eac32 | |||
| 12b36d2a32 | |||
| 5f412b3301 | |||
| 4ff3447c8c | |||
| 745080671b | |||
| 5fbbb51882 | |||
| 2dd7290ccb | |||
| d83368a475 | |||
| a68f5c6d37 | |||
| 8bb813e461 | |||
| fb935957b8 | |||
| 8de12b7997 | |||
| 1c6f0e8a19 | |||
| 0f28e2b02c | |||
| 396fff3d0b | |||
| e98b3fe183 | |||
| 69fbf97923 | |||
| c4df4906bc | |||
| dd1efc4312 | |||
| 3309157233 | |||
| abd969cb50 | |||
| 888ed072c0 | |||
| 1210c71703 | |||
| 17659466cc | |||
| 61b77b3bb6 | |||
| a74c5f0073 | |||
| 70a5e38b2c | |||
| a2e37371a6 | |||
| 2c7773b365 | |||
| 7c66344323 | |||
| c812f450bb | |||
| 4b2834439e | |||
| 6bd3f18949 | |||
| cc290a84e4 | |||
| 83c1ddc19d | |||
| 66ad660acc | |||
| 959c34de04 | |||
| e50831318e | |||
| 687e276fe3 | |||
| bc574f252d | |||
| e1204dc4f1 | |||
| 33b7115024 | |||
| 0563f72142 | |||
| a93d6e5f2d | |||
| 11721f7ff5 | |||
| 9e1caf81ce | |||
| c55f219d77 |
@@ -9,7 +9,7 @@ on:
|
||||
- v[0-9]+.[0-9]+.[0-9]+*
|
||||
|
||||
env:
|
||||
php: 8.0
|
||||
php: 8.1
|
||||
|
||||
jobs:
|
||||
Grunt:
|
||||
@@ -59,14 +59,13 @@ jobs:
|
||||
extensions:
|
||||
db: mysqli
|
||||
- os: ubuntu-22.04
|
||||
php: 8.0
|
||||
php: 8.1
|
||||
db: pgsql
|
||||
extensions: xmlrpc-beta
|
||||
|
||||
steps:
|
||||
- name: Setting up DB mysql
|
||||
if: ${{ matrix.db == 'mysqli' }}
|
||||
uses: johanmeiring/mysql-action@tmpfs-patch
|
||||
uses: moodlehq/mysql-action@v1
|
||||
with:
|
||||
collation server: utf8mb4_bin
|
||||
mysql version: 5.7
|
||||
@@ -74,6 +73,8 @@ jobs:
|
||||
mysql user: test
|
||||
mysql password: test
|
||||
use tmpfs: true
|
||||
tmpfs size: '1024M'
|
||||
extra conf: --skip-log-bin
|
||||
|
||||
- name: Setting up DB pgsql
|
||||
if: ${{ matrix.db == 'pgsql' }}
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -32,7 +32,7 @@ module.exports = grunt => {
|
||||
}
|
||||
},
|
||||
options: {
|
||||
implementation: require('node-sass'),
|
||||
implementation: require('sass'),
|
||||
includePaths: ["theme/boost/scss/", "theme/classic/scss/"]
|
||||
}
|
||||
},
|
||||
|
||||
+8
-7
@@ -50,20 +50,20 @@ jobs:
|
||||
php: 7.4
|
||||
env: DB=mysqli TASK=PHPUNIT
|
||||
|
||||
# Then, conditionally, all the highest php ones (8.0)
|
||||
# Then, conditionally, all the highest php ones (8.1)
|
||||
- if: env(MOODLE_PHP) = "all"
|
||||
php: 8.0
|
||||
php: 8.1
|
||||
env: DB=none TASK=CITEST
|
||||
- if: env(MOODLE_PHP) = "all"
|
||||
php: 8.0
|
||||
php: 8.1
|
||||
env: DB=none TASK=GRUNT
|
||||
|
||||
- if: env(MOODLE_PHP) = "all" AND (env(MOODLE_DATABASE) = "pgsql" OR env(MOODLE_DATABASE) = "all" OR env(MOODLE_DATABASE) IS NOT present)
|
||||
php: 8.0
|
||||
php: 8.1
|
||||
env: DB=pgsql TASK=PHPUNIT
|
||||
|
||||
- if: env(MOODLE_PHP) = "all" AND (env(MOODLE_DATABASE) = "mysqli" OR env(MOODLE_DATABASE) = "all")
|
||||
php: 8.0
|
||||
php: 8.1
|
||||
env: DB=mysqli TASK=PHPUNIT
|
||||
|
||||
cache:
|
||||
@@ -90,7 +90,8 @@ install:
|
||||
sudo service mysql stop
|
||||
sudo mv /var/lib/mysql /mnt/ramdisk
|
||||
sudo ln -s /mnt/ramdisk/mysql /var/lib/mysql
|
||||
sudo service mysql restart
|
||||
echo -e "[mysqld]\nskip-log-bin" | sudo tee -a /etc/mysql/my.cnf
|
||||
sudo service mysql start
|
||||
fi
|
||||
- >
|
||||
if [ "$DB" = 'pgsql' ];
|
||||
@@ -101,6 +102,7 @@ install:
|
||||
sudo mv /var/lib/postgresql /mnt/ramdisk
|
||||
sudo ln -s /mnt/ramdisk/postgresql /var/lib/postgresql
|
||||
sudo service postgresql start
|
||||
sh -c 'until pg_isready -p $PGPORT; do echo "Waiting for the DB to be up..."; sleep 2; done'
|
||||
fi
|
||||
- >
|
||||
if [ "$TASK" = 'PHPUNIT' ];
|
||||
@@ -127,7 +129,6 @@ install:
|
||||
fi
|
||||
|
||||
before_script:
|
||||
- if [[ ${TRAVIS_PHP_VERSION:0:1} -gt 7 ]]; then pecl install xmlrpc-beta; fi
|
||||
- echo 'max_input_vars=5000' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
|
||||
- phpenv config-rm xdebug.ini
|
||||
- >
|
||||
|
||||
+53
-35
@@ -1,48 +1,66 @@
|
||||
MOODLE TRADEMARKS POLICY
|
||||
========================
|
||||
|
||||
The word "Moodle" is trademarked in many countries around the world. The word
|
||||
was originally coined as an acronym: Modular Object-Oriented Dynamic Learning
|
||||
Environment. The trademark is owned by Martin Dougiamas, Founder and Director
|
||||
of Moodle.
|
||||
The Moodle Trademark
|
||||
--------------------
|
||||
|
||||
The law obligates trademark owners to police their marks and prevent the use of
|
||||
confusingly similar names by third parties. Through this policy we’d like to
|
||||
make it clear how Moodle-related projects, organisations, and people can use
|
||||
the Moodle trademark. We’d also like to be clear about how use of the word is
|
||||
restricted when used to promote commercial Moodle services. We do this to
|
||||
protect the very business model that allows us to continue developing Moodle
|
||||
for you.
|
||||
The brand and word "Moodle" has trade mark protection in many countries around
|
||||
the world. Various Moodle™ trade marks are owned by either Moodle Pty Ltd or
|
||||
one of its associated entities. The law obligates trade mark owners to police
|
||||
their trade marks and prevent the use of confusingly similar names by
|
||||
third parties.
|
||||
|
||||
Allowed uses
|
||||
------------
|
||||
We have created a more concise and thorough set of Trademark Guidelines
|
||||
(https://moodle.com/wp-content/uploads/2023/02/Moodle-Trademark-Guidelines-2023-4.pdf)
|
||||
to help you understand how to comply with Moodle's trade mark requirements.
|
||||
The Trademark Guidelines will also assist our Moodle Certified Partners and the
|
||||
community to understand some of the principles that inform Moodle's stance
|
||||
whenever potential infringement has been detected by us or our community.
|
||||
|
||||
The following uses don’t require any permission at all:
|
||||
We need to be clear about use of the word "Moodle" and how we restrict its use
|
||||
when promoting commercial Moodle™ branded services. The proper use of
|
||||
trade marks reliably assures consumers about the quality of the associated
|
||||
products or services and where they have been derived from. Because the
|
||||
Moodle LMS is made available under the open source ‘GNU General Public License'
|
||||
that permits you to modify the copyrighted software, the distribution of such
|
||||
modified software in combination with Moodle trade marks can potentially
|
||||
mislead others in the community. To be clear, the GNU GPL does not include an
|
||||
implied right or licence to use Moodle's trade marks.
|
||||
|
||||
* Referring to the software or the Moodle project.
|
||||
* Describing your own Moodle implementation (including within corporate
|
||||
settings).
|
||||
We do all this to protect the very business model that allows us to continue
|
||||
developing various Moodle software solutions for you.
|
||||
|
||||
Allowed uses of "Moodle"
|
||||
------------------------
|
||||
|
||||
The following uses don't require any permission at all:
|
||||
|
||||
* Referring to the software or the Moodle project as the "Moodle™ software"
|
||||
or the "Moodle™ project"
|
||||
* Describing your own implementation of the Moodle™ software platform
|
||||
(including within corporate settings).
|
||||
* Describing a Moodle-based community hub.
|
||||
* Describing some software you’ve made that integrates with Moodle
|
||||
(eg a Moodle integration feature on another system).
|
||||
* Describing certain software you've made that integrates with Moodle™
|
||||
branded software (e.g. a Moodle™ integration feature on another system).
|
||||
|
||||
Restricted uses
|
||||
---------------
|
||||
Restricted uses of "Moodle"
|
||||
---------------------------
|
||||
|
||||
The following uses are generally prohibited without explicit and direct
|
||||
permission being granted to you by Moodle Pty Ltd. We do this to protect the
|
||||
Moodle project from software and sites which could confuse people. Please
|
||||
contact us to ask for permission in writing.
|
||||
Moodle™ project from software and sites which could confuse people. If in
|
||||
doubt, reach out for clarification at <trademarks@moodle.com>
|
||||
|
||||
* You can’t use "Moodle" in the name of your software (including Mobile apps)
|
||||
* You can’t use "Moodle" in your company name
|
||||
* You can’t use "Moodle" in your domain name
|
||||
* You can’t use "Moodle" in advertising-related keywords (such as Adsense)
|
||||
* You can’t use "Moodle" to describe services around Moodle (such as hosting,
|
||||
training, support, consulting, course creation services, theme development,
|
||||
customisation, installation, integration and certification). This applies
|
||||
even if you do not charge for the services. Note that usually only Moodle
|
||||
Partners have this permission.
|
||||
|
||||
For information about the Moodle Partner Certification Mark as well as for how
|
||||
to contact us, please see <https://moodle.com/trademarks/>.
|
||||
* You can't use the word "Moodle" or any Moodle logos for commercial purposes
|
||||
without prior written permission from Moodle HQ.
|
||||
* You can't use "Moodle" in the name of your software (including Mobile apps).
|
||||
* You can't use "Moodle" in your company name.
|
||||
* You can't use "Moodle" in your domain name.
|
||||
* You can't use "Moodle" in advertising-related keywords (such as Adsense).
|
||||
* You can't use "Moodle" to describe services around Moodle
|
||||
(such as hosting, training, support, consulting, course creation services,
|
||||
theme development, customisation, installation, integration, analytics
|
||||
and certification) that cause consumers to be confused that you are
|
||||
associated with Moodle HQ if you are not.
|
||||
This applies even if you do not charge for the services.
|
||||
Note that usually only Moodle Partners have this permission.
|
||||
|
||||
@@ -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();
|
||||
@@ -3924,4 +3924,193 @@
|
||||
</CUSTOM_CHECK>
|
||||
</CUSTOM_CHECKS>
|
||||
</MOODLE>
|
||||
<MOODLE version="4.2" requires="3.11.8">
|
||||
<UNICODE level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unicoderequired" />
|
||||
</FEEDBACK>
|
||||
</UNICODE>
|
||||
<DATABASE level="required">
|
||||
<VENDOR name="mariadb" version="10.6.7" />
|
||||
<VENDOR name="mysql" version="8.0" />
|
||||
<VENDOR name="postgres" version="13" />
|
||||
<VENDOR name="mssql" version="14.0" />
|
||||
<VENDOR name="oracle" version="19" />
|
||||
</DATABASE>
|
||||
<PHP version="8.0.0" level="required">
|
||||
</PHP>
|
||||
<PCREUNICODE level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="pcreunicodewarning" />
|
||||
</FEEDBACK>
|
||||
</PCREUNICODE>
|
||||
<PHP_EXTENSIONS>
|
||||
<PHP_EXTENSION name="iconv" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="iconvrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="mbstring" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="mbstringrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="curl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="curlrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="openssl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="opensslrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="tokenizer" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="tokenizerrecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="soap" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="soaprecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="ctype" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="ctyperequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="zip" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="ziprequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="zlib" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="gd" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="gdrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="simplexml" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="simplexmlrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="spl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="splrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="pcre" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="dom" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="xml" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="xmlreader" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="intl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="intlrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="json" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="hash" level="required"/>
|
||||
<PHP_EXTENSION name="fileinfo" level="required"/>
|
||||
<PHP_EXTENSION name="sodium" level="required"/>
|
||||
<PHP_EXTENSION name="exif" level="optional"/>
|
||||
</PHP_EXTENSIONS>
|
||||
<PHP_SETTINGS>
|
||||
<PHP_SETTING name="memory_limit" value="96M" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="settingmemorylimit" />
|
||||
</FEEDBACK>
|
||||
</PHP_SETTING>
|
||||
<PHP_SETTING name="file_uploads" value="1" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="settingfileuploads" />
|
||||
</FEEDBACK>
|
||||
</PHP_SETTING>
|
||||
<PHP_SETTING name="opcache.enable" value="1" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="opcacherecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_SETTING>
|
||||
</PHP_SETTINGS>
|
||||
<CUSTOM_CHECKS>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_storage_engine" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddbstorageengine" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="question/engine/upgrade/upgradelib.php" function="quiz_attempts_upgraded" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="quizattemptsupgradedmessage" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_slasharguments" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="slashargumentswarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_tables_row_format" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="unsupporteddbtablerowformat" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_unoconv_version" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="unoconvwarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_libcurl_version" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="libcurlwarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_file_format" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddbfileformat" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_file_per_table" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddbfilepertable" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_large_prefix" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddblargeprefix" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_is_https" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="ishttpswarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_incomplete_unicode_support" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="incompleteunicodesupport" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_sixtyfour_bits" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="sixtyfourbitsrequired" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_max_input_vars" level="optional">
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_admin_dir_usage" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="iscustomadminwarnings" />
|
||||
</FEEDBACK>
|
||||
</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>
|
||||
|
||||
+10
-19
@@ -31,29 +31,20 @@
|
||||
|
||||
if (!empty($hide) and confirm_sesskey()) {
|
||||
$class = \core_plugin_manager::resolve_plugininfo_class('mod');
|
||||
$class::enable_plugin($hide, false);
|
||||
|
||||
admin_get_root(true, false); // settings not required - only pages
|
||||
if ($class::enable_plugin($hide, false)) {
|
||||
// Settings not required - only pages.
|
||||
admin_get_root(true, false);
|
||||
}
|
||||
redirect(new moodle_url('/admin/modules.php'));
|
||||
}
|
||||
|
||||
if (!empty($show) and confirm_sesskey()) {
|
||||
$canenablemodule = true;
|
||||
$modulename = $show;
|
||||
|
||||
// Invoking a callback function that enables plugins to force additional actions (e.g. displaying notifications,
|
||||
// modals, etc.) and also specify through its returned value (bool) whether the process of enabling the plugin
|
||||
// should continue after these actions or not.
|
||||
if (component_callback_exists("mod_{$modulename}", 'pre_enable_plugin_actions')) {
|
||||
$canenablemodule = component_callback("mod_{$modulename}", 'pre_enable_plugin_actions');
|
||||
}
|
||||
|
||||
if ($canenablemodule) {
|
||||
$class = \core_plugin_manager::resolve_plugininfo_class('mod');
|
||||
$class::enable_plugin($show, true);
|
||||
admin_get_root(true, false); // Settings not required - only pages.
|
||||
redirect(new moodle_url('/admin/modules.php'));
|
||||
if (!empty($show) && confirm_sesskey()) {
|
||||
$class = \core_plugin_manager::resolve_plugininfo_class('mod');
|
||||
if ($class::enable_plugin($show, true)) {
|
||||
// Settings not required - only pages.
|
||||
admin_get_root(true, false);
|
||||
}
|
||||
redirect(new moodle_url('/admin/modules.php'));
|
||||
}
|
||||
|
||||
echo $OUTPUT->header();
|
||||
|
||||
+23
-12
@@ -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);
|
||||
|
||||
@@ -34,7 +34,7 @@ class adminpresets_admin_setting_configselect extends adminpresets_setting {
|
||||
*/
|
||||
protected function set_value($value) {
|
||||
// When we intantiate the class we need the choices.
|
||||
if (empty($this->settindata->choices) && method_exists($this->settingdata, 'load_choices')) {
|
||||
if (empty($this->settingdata->choices) && method_exists($this->settingdata, 'load_choices')) {
|
||||
$this->settingdata->load_choices();
|
||||
}
|
||||
|
||||
|
||||
+17
-29
@@ -24,6 +24,7 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
use core_question\local\bank\question_version_status;
|
||||
|
||||
require_once(__DIR__ . '/../config.php');
|
||||
require_once($CFG->libdir . '/questionlib.php');
|
||||
@@ -42,36 +43,23 @@ $qtypes = question_bank::get_all_qtypes();
|
||||
$pluginmanager = core_plugin_manager::instance();
|
||||
|
||||
// Get some data we will need - question counts and which types are needed.
|
||||
$hiddenstatus = \core_question\local\bank\question_version_status::QUESTION_STATUS_HIDDEN;
|
||||
$draftstatus = \core_question\local\bank\question_version_status::QUESTION_STATUS_DRAFT;
|
||||
// The second JOIN on question_versions (qv2) is to get the latest version of each question.
|
||||
// (Using this sort of JOIN is a known trick for doing this in the fastest possible way.)
|
||||
$counts = $DB->get_records_sql("
|
||||
SELECT q.qtype,
|
||||
COUNT(qv.id) AS numquestions,
|
||||
SUM(CASE WHEN qv.status = :hiddenstatus THEN 1 ELSE 0 END) AS numhidden,
|
||||
SUM(CASE WHEN qv.status = :draftstatus THEN 1 ELSE 0 END) AS numdraft
|
||||
FROM {question} q
|
||||
JOIN {question_versions} qv ON q.id = qv.questionid
|
||||
LEFT JOIN {question_versions} qv2 ON qv.questionbankentryid = qv2.questionbankentryid AND qv.version < qv2.version
|
||||
WHERE qv2.questionbankentryid IS NULL
|
||||
GROUP BY q.qtype
|
||||
", [
|
||||
'hiddenstatus' => question_version_status::QUESTION_STATUS_HIDDEN,
|
||||
'draftstatus' => question_version_status::QUESTION_STATUS_DRAFT,
|
||||
]);
|
||||
|
||||
$sql = "SELECT result.qtype,
|
||||
SUM(result.numquestions) AS numquestions,
|
||||
SUM(result.numhidden) AS numhidden,
|
||||
SUM(result.numdraft) AS numdraft
|
||||
FROM (SELECT data.qtype,
|
||||
data.versionid,
|
||||
COUNT(data.numquestions) AS numquestions,
|
||||
(SELECT COUNT(qv.id)
|
||||
FROM {question_versions} qv
|
||||
WHERE qv.id = data.versionid
|
||||
AND qv.status = :hiddenstatus) AS numhidden,
|
||||
(SELECT COUNT(qv.id)
|
||||
FROM {question_versions} qv
|
||||
WHERE qv.id = data.versionid
|
||||
AND qv.status = :draftstatus) AS numdraft
|
||||
FROM (SELECT q.qtype, qv.id AS versionid, 1 AS numquestions
|
||||
FROM {question} q
|
||||
JOIN {question_versions} qv ON qv.questionid = q.id
|
||||
JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
|
||||
AND qv.version = (SELECT MAX(v.version)
|
||||
FROM {question_versions} v
|
||||
JOIN {question_bank_entries} be ON be.id = v.questionbankentryid
|
||||
WHERE be.id = qbe.id)) data
|
||||
GROUP BY data.qtype, data.versionid) result
|
||||
GROUP BY result.qtype";
|
||||
|
||||
$counts = $DB->get_records_sql($sql, ['hiddenstatus' => $hiddenstatus, 'draftstatus' => $draftstatus]);
|
||||
$needed = [];
|
||||
foreach ($qtypes as $qtypename => $qtype) {
|
||||
if (!isset($counts[$qtypename])) {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -477,7 +477,8 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) {
|
||||
$temp->add(new admin_setting_heading('automatedsettings', new lang_string('automatedsettings','backup'), new lang_string('recyclebin_desc', 'backup')));
|
||||
$temp->add(new admin_setting_configcheckbox('backup/backup_auto_users', new lang_string('generalusers', 'backup'), new lang_string('configgeneralusers', 'backup'), 1));
|
||||
$temp->add(new admin_setting_configcheckbox('backup/backup_auto_role_assignments', new lang_string('generalroleassignments','backup'), new lang_string('configgeneralroleassignments','backup'), 1));
|
||||
$temp->add(new admin_setting_configcheckbox('backup/backup_auto_activities', new lang_string('generalactivities','backup'), new lang_string('configgeneralactivities','backup'), 1));
|
||||
$temp->add(new admin_setting_configcheckbox('backup/backup_auto_activities', new lang_string('generalactivities', 'backup'),
|
||||
new lang_string('backupautoactivitiesdescription', 'backup'), 1));
|
||||
$temp->add(new admin_setting_configcheckbox('backup/backup_auto_blocks', new lang_string('generalblocks','backup'), new lang_string('configgeneralblocks','backup'), 1));
|
||||
$temp->add(new admin_setting_configcheckbox(
|
||||
'backup/backup_auto_files',
|
||||
|
||||
@@ -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 }}"
|
||||
|
||||
@@ -35,9 +35,7 @@ Feature: Allowing multiple accounts to have the same email address
|
||||
| username | firstname | lastname | email |
|
||||
| s1 | John | Doe | s1@example.com |
|
||||
| s2 | Jane | Doe | s2@example.com |
|
||||
When I log in as "admin"
|
||||
And I navigate to "Users > Accounts > Browse list of users" in site administration
|
||||
And I click on "Edit" "link" in the "Jane Doe" "table_row"
|
||||
When I am on the "s2" "user > editing" page logged in as "admin"
|
||||
And I set the field "Email address" to "<email>"
|
||||
And I press "Update profile"
|
||||
Then I should <expect> "This email address is already registered."
|
||||
|
||||
@@ -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 |
|
||||
@@ -11,8 +11,11 @@ Feature: I can add a new preset with current settings
|
||||
And I set the field "Name" to "Starter"
|
||||
And I set the field "Description" to "Non-core starter preset"
|
||||
When I click on "Create preset" "button"
|
||||
Then I should see "Moodle with all of the most popular features" in the "Starter" "table_row"
|
||||
And I should see "Starter" in the "Non-core starter preset" "table_row"
|
||||
Then the following should exist in the "Site admin presets table" table:
|
||||
| Name | Description |
|
||||
| Starter | Moodle with all of the most popular features, including Assignment, Feedback, Forum, H5P, Quiz and Completion tracking. |
|
||||
| Full | All the Starter features plus External (LTI) tool, SCORM, Workshop, Analytics, Badges, Competencies, Learning plans and lots more. |
|
||||
| Starter | Non-core starter preset |
|
||||
|
||||
Scenario: Export current settings
|
||||
Given I click on "Create preset" "button"
|
||||
|
||||
@@ -45,7 +45,7 @@ class helper {
|
||||
// Form field is PARAM_ALPHANUMEXT and we are sending fully qualified class names
|
||||
// as option names, but replacing the backslash for a string that is really unlikely
|
||||
// to ever be part of a class name.
|
||||
return str_replace('\\', '__', $class);
|
||||
return str_replace('\\', '__', $class ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -437,7 +437,7 @@ function print_combined_run_output($processes, $stoponfail = false) {
|
||||
$process->stop(0);
|
||||
}
|
||||
|
||||
$strlentoprint = strlen($update);
|
||||
$strlentoprint = strlen($update ?? '');
|
||||
|
||||
// If not enough dots printed on line then just print.
|
||||
if ($strlentoprint < $remainingprintlen) {
|
||||
|
||||
@@ -360,7 +360,7 @@ function print_combined_drop_output($processes) {
|
||||
$op = $process->getIncrementalOutput();
|
||||
if (trim($op)) {
|
||||
$update = preg_filter('#^\s*([FS\.\-]+)(?:\s+\d+)?\s*$#', '$1', $op);
|
||||
$strlentoprint = strlen($update);
|
||||
$strlentoprint = $update ? strlen($update) : 0;
|
||||
|
||||
// If not enough dots printed on line then just print.
|
||||
if ($strlentoprint < $remainingprintlen) {
|
||||
|
||||
@@ -30,10 +30,9 @@ Feature: Verify that all form fields values can be get and set
|
||||
| student2 | G2 |
|
||||
| student3 | G2 |
|
||||
And the following "activities" exist:
|
||||
| activity | course | idnumber | name | intro | firstpagetitle | wikimode | visible |
|
||||
| wiki | C1 | wiki1 | Test this one | Test this one | Test this one | collaborative | 0 |
|
||||
And I log in as "admin"
|
||||
And I am on the "Course 1" "reset" page
|
||||
| activity | course | idnumber | name | firstpagetitle | wikimode | visible |
|
||||
| wiki | C1 | wiki1 | Test this one | Test this one | collaborative | 0 |
|
||||
And I am on the "Course 1" "reset" page logged in as admin
|
||||
# Select (multi-select) - Checking "the select box should contain".
|
||||
And I expand all fieldsets
|
||||
And the "Unenrol users" select box should contain "No roles"
|
||||
@@ -60,16 +59,14 @@ Feature: Verify that all form fields values can be get and set
|
||||
# Checkbox - Checking "I set the field" and "The field matches value" ticked.
|
||||
And I set the field "Force format" to "1"
|
||||
And I press "Save and return to course"
|
||||
And I am on the "Test this one" "wiki activity" page
|
||||
And I navigate to "Settings" in current page administration
|
||||
And I am on the "Test this one" "wiki activity editing" page
|
||||
And I expand all fieldsets
|
||||
And the field "Force format" matches value "1"
|
||||
And the field "Force format" does not match value ""
|
||||
# Checkbox - Checking "I set the field" and "The field matches value" unticked.
|
||||
And I set the field "Force format" to ""
|
||||
And I press "Save and return to course"
|
||||
And I am on the "Test this one" "wiki activity" page
|
||||
And I navigate to "Settings" in current page administration
|
||||
And I am on the "Test this one" "wiki activity editing" page
|
||||
And I expand all fieldsets
|
||||
And the field "Force format" matches value ""
|
||||
And the field "Force format" does not match value "1"
|
||||
@@ -77,8 +74,7 @@ Feature: Verify that all form fields values can be get and set
|
||||
And I set the following fields to these values:
|
||||
| Force format | 1 |
|
||||
And I press "Save and return to course"
|
||||
And I am on the "Test this one" "wiki activity" page
|
||||
And I navigate to "Settings" in current page administration
|
||||
And I am on the "Test this one" "wiki activity editing" page
|
||||
And I expand all fieldsets
|
||||
And the following fields match these values:
|
||||
| Force format | 1 |
|
||||
@@ -88,8 +84,7 @@ Feature: Verify that all form fields values can be get and set
|
||||
And I set the following fields to these values:
|
||||
| Force format | |
|
||||
And I press "Save and return to course"
|
||||
And I am on the "Test this one" "wiki activity" page
|
||||
And I navigate to "Settings" in current page administration
|
||||
And I am on the "Test this one" "wiki activity editing" page
|
||||
And I expand all fieldsets
|
||||
And the following fields match these values:
|
||||
| Force format | |
|
||||
@@ -142,10 +137,11 @@ Feature: Verify that all form fields values can be get and set
|
||||
And I navigate to "Settings" in current page administration
|
||||
And the field "Course ID number" matches value "Course id number"
|
||||
|
||||
Scenario: with JS disabled all form fields getters and setters works as expected
|
||||
|
||||
@javascript
|
||||
Scenario: with JS enabled all form fields getters and setters works as expected
|
||||
Given the following "activities" exist:
|
||||
| activity | course | name |
|
||||
| lesson | C1 | Test lesson |
|
||||
Then I am on the "Course 1" "groups" page
|
||||
# Select (multi-select & AJAX) - Checking "I set the field" and "select box should contain".
|
||||
And I set the field "groups" to "Group 2"
|
||||
@@ -157,11 +153,8 @@ Feature: Verify that all form fields values can be get and set
|
||||
And the "members" select box should contain "Student 2 (s2@example.com)"
|
||||
And the "members" select box should not contain "Student 3 (s3@example.com)"
|
||||
# Checkbox (AJAX) - Checking "I set the field" and "I set the following fields to these values".
|
||||
And I am on "Course 1" course homepage
|
||||
And I add a "Lesson" to section "1"
|
||||
And I am on the "Test lesson" "lesson activity editing" page
|
||||
And I set the following fields to these values:
|
||||
| Name | Test lesson |
|
||||
| Description | Test lesson description |
|
||||
| available[enabled] | 1 |
|
||||
And I set the field "deadline[enabled]" to "1"
|
||||
# Checkbox (AJAX) - Checking "the field matches value" before saving.
|
||||
|
||||
@@ -48,12 +48,7 @@ class accessibility {
|
||||
public static function is_accessibility_enabled(): bool {
|
||||
global $CFG;
|
||||
|
||||
if (isset($CFG->enableaccessibilitytools)) {
|
||||
return $CFG->enableaccessibilitytools;
|
||||
}
|
||||
|
||||
// Enabled by default.
|
||||
return true;
|
||||
return !empty($CFG->enableaccessibilitytools);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -205,7 +205,7 @@ class manager {
|
||||
* @return string
|
||||
*/
|
||||
public static function get_contenthash(?string $content = null): string {
|
||||
return sha1($content);
|
||||
return sha1($content ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
@@ -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 */
|
||||
|
||||
+1
-110
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" -}}
|
||||
|
||||
@@ -47,7 +47,7 @@ class data_registry {
|
||||
* @return string[]
|
||||
*/
|
||||
public static function var_names_from_context($classname, $pluginname = '') {
|
||||
$pluginname = trim($pluginname);
|
||||
$pluginname = trim($pluginname ?? '');
|
||||
if (!empty($pluginname)) {
|
||||
$categoryvar = $classname . '_' . $pluginname . '_category';
|
||||
$purposevar = $classname . '_' . $pluginname . '_purpose';
|
||||
|
||||
@@ -111,7 +111,7 @@ class purpose_exporter extends persistent_exporter {
|
||||
$values['formattedlawfulbases'] = $formattedbases;
|
||||
|
||||
$formattedsensitivereasons = [];
|
||||
$sensitivereasons = explode(',', $this->persistent->get('sensitivedatareasons'));
|
||||
$sensitivereasons = explode(',', $this->persistent->get('sensitivedatareasons') ?? '');
|
||||
if (!empty($sensitivereasons)) {
|
||||
foreach ($sensitivereasons as $reason) {
|
||||
if (empty(trim($reason))) {
|
||||
|
||||
@@ -276,7 +276,7 @@ $string['requesttypedelete'] = 'Delete all of my personal data';
|
||||
$string['requesttypedeleteshort'] = 'Delete';
|
||||
$string['requesttypeexport'] = 'Export all of my personal data';
|
||||
$string['requesttypeexportshort'] = 'Export';
|
||||
$string['requesttypeothers'] = 'General inquiry';
|
||||
$string['requesttypeothers'] = 'General enquiry';
|
||||
$string['requesttypeothersshort'] = 'Message';
|
||||
$string['requireallenddatesforuserdeletion'] = 'Consider courses without end date as active';
|
||||
$string['requireallenddatesforuserdeletion_desc'] = 'When calculating user expiry, several factors are considered:
|
||||
|
||||
@@ -20,7 +20,7 @@ Feature: Contact the privacy officer
|
||||
And I click on "Send" "button" in the "Contact the privacy officer" "dialogue"
|
||||
Then I should see "Your request has been submitted to the privacy officer"
|
||||
And I click on "Data requests" "link"
|
||||
And I should see "Hello DPO!" in the "General inquiry" "table_row"
|
||||
And I should see "Hello DPO!" in the "General enquiry" "table_row"
|
||||
|
||||
Scenario: Contacting the privacy officer when not enabled
|
||||
When I log in as "student1"
|
||||
|
||||
+1
-1
@@ -5,6 +5,6 @@ define("tool_langimport/search",["exports","core/pending","core/utils"],(functio
|
||||
* @module tool_langimport/search
|
||||
* @copyright 2021 Paul Holden <paulh@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,_pending=(obj=_pending)&&obj.__esModule?obj:{default:obj};const SELECTORS_AVAILABLE_LANG_SELECT="select",SELECTORS_AVAILABLE_LANG_SEARCH='[data-action="search"]';var _default={init:form=>{const availableLangsElement=form.querySelector(SELECTORS_AVAILABLE_LANG_SELECT),availableLangsFilter=event=>{const pendingPromise=new _pending.default("tool_langimport/search:filter");availableLangsElement.querySelectorAll("option").forEach((option=>{option.remove()}));const searchTerm=event.target.value.toLowerCase(),availableLanguages=JSON.parse(availableLangsElement.dataset.availableLanguages),filteredLanguages=Object.keys(availableLanguages).reduce(((matches,langcode)=>(availableLanguages[langcode].toLowerCase().includes(searchTerm)&&(matches[langcode]=availableLanguages[langcode]),matches)),[]);Object.entries(filteredLanguages).forEach((_ref=>{let[langcode,langname]=_ref;const option=document.createElement("option");option.value=langcode,option.innerText=langname,availableLangsElement.append(option)})),pendingPromise.resolve()},availableLanguages={};availableLangsElement.querySelectorAll("option").forEach((option=>{availableLanguages[option.value]=option.text})),availableLangsElement.dataset.availableLanguages=JSON.stringify(availableLanguages);const availableLangsSearch=form.querySelector(SELECTORS_AVAILABLE_LANG_SEARCH);availableLangsSearch.addEventListener("keydown",(event=>{"Enter"===event.key&&event.preventDefault()})),availableLangsSearch.addEventListener("keyup",(event=>{const pendingPromise=new _pending.default("tool_langimport/search:keyup");(0,_utils.debounce)(availableLangsFilter,250)(event),setTimeout((()=>{pendingPromise.resolve()}),250)}))}};return _exports.default=_default,_exports.default}));
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_pending=(obj=_pending)&&obj.__esModule?obj:{default:obj};const SELECTORS_AVAILABLE_LANG_SELECT="select",SELECTORS_AVAILABLE_LANG_SEARCH='[data-action="search"]';_exports.init=form=>{const availableLangsElement=form.querySelector(SELECTORS_AVAILABLE_LANG_SELECT),availableLanguages={};availableLangsElement.querySelectorAll("option").forEach((option=>{availableLanguages[option.value]=option.text})),availableLangsElement.dataset.availableLanguages=JSON.stringify(availableLanguages);const availableLangsSearch=form.querySelector(SELECTORS_AVAILABLE_LANG_SEARCH);availableLangsSearch.addEventListener("keydown",(event=>{"Enter"===event.key&&event.preventDefault()}));const availableLangsSearchDebounce=(0,_utils.debounce)((event=>{const pendingPromise=new _pending.default("tool_langimport/search:filter");availableLangsElement.querySelectorAll("option").forEach((option=>{option.remove()}));const searchTerm=event.target.value.toLowerCase(),availableLanguages=JSON.parse(availableLangsElement.dataset.availableLanguages),filteredLanguages=Object.keys(availableLanguages).reduce(((matches,langcode)=>(availableLanguages[langcode].toLowerCase().includes(searchTerm)&&(matches[langcode]=availableLanguages[langcode]),matches)),[]);Object.entries(filteredLanguages).forEach((_ref=>{let[langcode,langname]=_ref;const option=document.createElement("option");option.value=langcode,option.innerText=langname,availableLangsElement.append(option)})),pendingPromise.resolve()}),250);availableLangsSearch.addEventListener("keyup",(event=>{const pendingPromise=new _pending.default("tool_langimport/search:keyup");availableLangsSearchDebounce(event),setTimeout((()=>{pendingPromise.resolve()}),250)}))}}));
|
||||
|
||||
//# sourceMappingURL=search.min.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -36,7 +36,7 @@ const DEBOUNCE_TIMER = 250;
|
||||
*
|
||||
* @param {Element} form
|
||||
*/
|
||||
const init = (form) => {
|
||||
export const init = form => {
|
||||
const availableLangsElement = form.querySelector(SELECTORS.AVAILABLE_LANG_SELECT);
|
||||
|
||||
const availableLangsFilter = (event) => {
|
||||
@@ -84,16 +84,13 @@ const init = (form) => {
|
||||
});
|
||||
|
||||
// Debounce the event listener to allow the user to finish typing.
|
||||
const availableLangsSearchDebounce = debounce(availableLangsFilter, DEBOUNCE_TIMER);
|
||||
availableLangsSearch.addEventListener('keyup', (event) => {
|
||||
const pendingPromise = new Pending('tool_langimport/search:keyup');
|
||||
|
||||
debounce(availableLangsFilter, DEBOUNCE_TIMER)(event);
|
||||
availableLangsSearchDebounce(event);
|
||||
setTimeout(() => {
|
||||
pendingPromise.resolve();
|
||||
}, DEBOUNCE_TIMER);
|
||||
});
|
||||
};
|
||||
|
||||
export default {
|
||||
init: init,
|
||||
};
|
||||
|
||||
@@ -33,3 +33,26 @@ Feature: Licence manager
|
||||
And "This is the site default licence" "icon" should exist in the "cc" "table_row"
|
||||
And "Enable licence" "icon" should not exist in the "cc" "table_row"
|
||||
And "This is the site default licence" "icon" should not exist in the "public" "table_row"
|
||||
|
||||
@javascript @_file_upload
|
||||
Scenario Outline: User licence preference is remembered depending of setting value
|
||||
Given the following config values are set as admin:
|
||||
| sitedefaultlicense | cc |
|
||||
| rememberuserlicensepref | <rememberuserlicensepref> |
|
||||
And I log in as "admin"
|
||||
And I follow "Private files" in the user menu
|
||||
And I follow "Add..."
|
||||
And I follow "Upload a file"
|
||||
And the field with xpath "//select[@name='license']" matches value "Creative Commons"
|
||||
And I click on "Close" "button" in the "File picker" "dialogue"
|
||||
When I upload "lib/tests/fixtures/empty.txt" file to "Files" filemanager as:
|
||||
| Save as | empty_copy.txt |
|
||||
| license | Public domain |
|
||||
And I press "Save changes"
|
||||
And I follow "Add..."
|
||||
Then the field with xpath "//select[@name='license']" matches value "<expectedlicence>"
|
||||
|
||||
Examples:
|
||||
| rememberuserlicensepref | expectedlicence |
|
||||
| 0 | Creative Commons |
|
||||
| 1 | Public domain |
|
||||
|
||||
@@ -74,10 +74,10 @@ trait reader {
|
||||
* @return mixed Decoded value
|
||||
*/
|
||||
public static function decode_other(?string $other) {
|
||||
if ($other === 'N;' || preg_match('~^.:~', $other)) {
|
||||
if ($other === 'N;' || preg_match('~^.:~', $other ?? '')) {
|
||||
return unserialize($other);
|
||||
} else {
|
||||
return json_decode($other, true);
|
||||
return json_decode($other ?? '', true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,8 +33,7 @@ class store_test extends \advanced_testcase {
|
||||
* Tests log writing.
|
||||
*
|
||||
* @param bool $jsonformat True to test with JSON format
|
||||
* @dataProvider test_log_writing_provider
|
||||
* @throws moodle_exception
|
||||
* @dataProvider log_writing_provider
|
||||
*/
|
||||
public function test_log_writing(bool $jsonformat) {
|
||||
global $DB, $CFG;
|
||||
@@ -252,9 +251,9 @@ class store_test extends \advanced_testcase {
|
||||
* Returns different JSON format settings so the test can be run with JSON format either on or
|
||||
* off.
|
||||
*
|
||||
* @return [bool] Array of true/false
|
||||
* @return bool[] Array of true/false
|
||||
*/
|
||||
public static function test_log_writing_provider(): array {
|
||||
public static function log_writing_provider(): array {
|
||||
return [
|
||||
[false],
|
||||
[true]
|
||||
@@ -311,17 +310,16 @@ class store_test extends \advanced_testcase {
|
||||
$logmanager = get_log_manager();
|
||||
$allreports = \core_component::get_plugin_list('report');
|
||||
|
||||
$supportedreports = array(
|
||||
'report_log' => '/report/log',
|
||||
'report_loglive' => '/report/loglive'
|
||||
);
|
||||
|
||||
// Make sure all supported reports are installed.
|
||||
$expectedreports = array_keys(array_intersect_key($allreports, $supportedreports));
|
||||
$reports = $logmanager->get_supported_reports('logstore_database');
|
||||
$reports = array_keys($reports);
|
||||
$expectedreports = array_intersect_key([
|
||||
'log' => 'report_log',
|
||||
'loglive' => 'report_loglive',
|
||||
], $allreports);
|
||||
|
||||
$supportedreports = $logmanager->get_supported_reports('logstore_database');
|
||||
|
||||
foreach ($expectedreports as $expectedreport) {
|
||||
$this->assertContains($expectedreport, $reports);
|
||||
$this->assertArrayHasKey($expectedreport, $supportedreports);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,20 +217,19 @@ class store_test extends \advanced_testcase {
|
||||
$logmanager = get_log_manager();
|
||||
$allreports = \core_component::get_plugin_list('report');
|
||||
|
||||
$supportedreports = array(
|
||||
'report_log' => '/report/log',
|
||||
'report_loglive' => '/report/loglive',
|
||||
'report_outline' => '/report/outline',
|
||||
'report_participation' => '/report/participation',
|
||||
'report_stats' => '/report/stats'
|
||||
);
|
||||
|
||||
// Make sure all supported reports are installed.
|
||||
$expectedreports = array_keys(array_intersect_key($allreports, $supportedreports));
|
||||
$reports = $logmanager->get_supported_reports('logstore_legacy');
|
||||
$reports = array_keys($reports);
|
||||
$expectedreports = array_intersect_key([
|
||||
'log' => 'report_log',
|
||||
'loglive' => 'report_loglive',
|
||||
'outline' => 'report_outline',
|
||||
'participation' => 'report_participation',
|
||||
'stats' => 'report_stats'
|
||||
], $allreports);
|
||||
|
||||
$supportedreports = $logmanager->get_supported_reports('logstore_legacy');
|
||||
|
||||
foreach ($expectedreports as $expectedreport) {
|
||||
$this->assertContains($expectedreport, $reports);
|
||||
$this->assertArrayHasKey($expectedreport, $supportedreports);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,8 +38,7 @@ class store_test extends \advanced_testcase {
|
||||
* Tests log writing.
|
||||
*
|
||||
* @param bool $jsonformat True to test with JSON format
|
||||
* @dataProvider test_log_writing_provider
|
||||
* @throws moodle_exception
|
||||
* @dataProvider log_writing_provider
|
||||
*/
|
||||
public function test_log_writing(bool $jsonformat) {
|
||||
global $DB;
|
||||
@@ -227,9 +226,9 @@ class store_test extends \advanced_testcase {
|
||||
* Returns different JSON format settings so the test can be run with JSON format either on or
|
||||
* off.
|
||||
*
|
||||
* @return [bool] Array of true/false
|
||||
* @return bool[] Array of true/false
|
||||
*/
|
||||
public static function test_log_writing_provider(): array {
|
||||
public static function log_writing_provider(): array {
|
||||
return [
|
||||
[false],
|
||||
[true]
|
||||
@@ -243,20 +242,19 @@ class store_test extends \advanced_testcase {
|
||||
$logmanager = get_log_manager();
|
||||
$allreports = \core_component::get_plugin_list('report');
|
||||
|
||||
$supportedreports = array(
|
||||
'report_log' => '/report/log',
|
||||
'report_loglive' => '/report/loglive',
|
||||
'report_outline' => '/report/outline',
|
||||
'report_participation' => '/report/participation',
|
||||
'report_stats' => '/report/stats'
|
||||
);
|
||||
|
||||
// Make sure all supported reports are installed.
|
||||
$expectedreports = array_keys(array_intersect_key($allreports, $supportedreports));
|
||||
$reports = $logmanager->get_supported_reports('logstore_standard');
|
||||
$reports = array_keys($reports);
|
||||
$expectedreports = array_intersect_key([
|
||||
'log' => 'report_log',
|
||||
'loglive' => 'report_loglive',
|
||||
'outline' => 'report_outline',
|
||||
'participation' => 'report_participation',
|
||||
'stats' => 'report_stats'
|
||||
], $allreports);
|
||||
|
||||
$supportedreports = $logmanager->get_supported_reports('logstore_standard');
|
||||
|
||||
foreach ($expectedreports as $expectedreport) {
|
||||
$this->assertContains($expectedreport, $reports);
|
||||
$this->assertArrayHasKey($expectedreport, $supportedreports);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,7 +370,7 @@ class store_test extends \advanced_testcase {
|
||||
* Tests the decode_other function can cope with both JSON and PHP serialized format.
|
||||
*
|
||||
* @param mixed $value Value to encode and decode
|
||||
* @dataProvider test_decode_other_provider
|
||||
* @dataProvider decode_other_provider
|
||||
*/
|
||||
public function test_decode_other($value) {
|
||||
$this->assertEquals($value, \logstore_standard\log\store::decode_other(serialize($value)));
|
||||
@@ -391,7 +389,7 @@ class store_test extends \advanced_testcase {
|
||||
*
|
||||
* @return array Array of parameters
|
||||
*/
|
||||
public function test_decode_other_provider(): array {
|
||||
public function decode_other_provider(): array {
|
||||
return [
|
||||
[['info' => 'd2819896', 'logurl' => 'discuss.php?d=2819896']],
|
||||
[null],
|
||||
@@ -404,8 +402,7 @@ class store_test extends \advanced_testcase {
|
||||
* Checks that backup and restore of log data works correctly.
|
||||
*
|
||||
* @param bool $jsonformat True to test with JSON format
|
||||
* @dataProvider test_log_writing_provider
|
||||
* @throws moodle_exception
|
||||
* @dataProvider log_writing_provider
|
||||
*/
|
||||
public function test_backup_restore(bool $jsonformat) {
|
||||
global $DB;
|
||||
|
||||
@@ -229,7 +229,6 @@ class page_helper {
|
||||
$PAGE->set_pagelayout('standard');
|
||||
$PAGE->set_url($url);
|
||||
$PAGE->set_title($title);
|
||||
$PAGE->set_heading(fullname($user));
|
||||
|
||||
if (!empty($plan)) {
|
||||
$PAGE->navbar->add($title, $planurl);
|
||||
@@ -303,7 +302,6 @@ class page_helper {
|
||||
$PAGE->set_pagelayout('standard');
|
||||
$PAGE->set_url($url);
|
||||
$PAGE->set_title($title);
|
||||
$PAGE->set_heading(fullname($user));
|
||||
|
||||
if (!empty($evidence)) {
|
||||
$PAGE->navbar->add($title, $evidenceurl);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -59,22 +59,26 @@ class api_test extends \externallib_advanced_testcase {
|
||||
*/
|
||||
public function test_get_potential_config_issues() {
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/message/lib.php');
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
$this->setAdminUser();
|
||||
|
||||
$CFG->userquota = '73289234723498234723423489273423497234234';
|
||||
// Set non-SSL wwwroot, to avoid spurious certificate checking.
|
||||
$CFG->wwwroot = 'http://www.example.com';
|
||||
$CFG->debugdisplay = 1;
|
||||
|
||||
set_config('debugauthdb', 1, 'auth_db');
|
||||
set_config('debugdb', 1, 'enrol_database');
|
||||
$expectedissues = array('adodbdebugwarning', 'displayerrorswarning');
|
||||
|
||||
// Get potential issues, obtain their keys for comparison.
|
||||
$issues = api::get_potential_config_issues();
|
||||
$this->assertCount(count($expectedissues), $issues);
|
||||
foreach ($issues as $issue) {
|
||||
$this->assertTrue(in_array($issue[0], $expectedissues));
|
||||
}
|
||||
$issuekeys = array_column($issues, 0);
|
||||
|
||||
$this->assertEqualsCanonicalizing([
|
||||
'nohttpsformobilewarning',
|
||||
'adodbdebugwarning',
|
||||
'displayerrorswarning',
|
||||
], $issuekeys);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -60,8 +60,8 @@ class remote_resource {
|
||||
public function __construct(\curl $curl, url $url, \stdClass $metadata) {
|
||||
$this->curl = $curl;
|
||||
$this->url = $url;
|
||||
$this->filename = pathinfo($this->url->get_path(), PATHINFO_FILENAME);
|
||||
$this->extension = pathinfo($this->url->get_path(), PATHINFO_EXTENSION);
|
||||
$this->filename = pathinfo($this->url->get_path() ?? '', PATHINFO_FILENAME);
|
||||
$this->extension = pathinfo($this->url->get_path() ?? '', PATHINFO_EXTENSION);
|
||||
$this->metadata = $metadata;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -108,7 +108,7 @@ class issuer extends persistent {
|
||||
$mform->addHelpButton('clientsecret', 'issuerclientsecret', 'tool_oauth2');
|
||||
|
||||
// Use basic authentication.
|
||||
$mform->addElement('checkbox', 'basicauth', get_string('usebasicauth', 'tool_oauth2'));
|
||||
$mform->addElement('advcheckbox', 'basicauth', get_string('usebasicauth', 'tool_oauth2'));
|
||||
$mform->addHelpButton('basicauth', 'usebasicauth', 'tool_oauth2');
|
||||
|
||||
// Base Url.
|
||||
@@ -244,7 +244,6 @@ class issuer extends persistent {
|
||||
* @return array of additional errors, or overridden errors.
|
||||
*/
|
||||
protected function extra_validation($data, $files, array &$errors) {
|
||||
$errors = [];
|
||||
if ($data->showonloginpage != \core\oauth2\issuer::SERVICEONLY) {
|
||||
if (!strlen(trim($data->loginscopes))) {
|
||||
$errors['loginscopes'] = get_string('required');
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -218,9 +218,13 @@ Feature: Basic OAuth2 functionality
|
||||
| Name | Invalid custom service |
|
||||
| Client ID | thisistheclientid |
|
||||
| Client secret | supersecret |
|
||||
| Service base URL | https://dc.imsglobal.org/ |
|
||||
| Service base URL | http://dc.imsglobal.org/ |
|
||||
When I press "Save changes"
|
||||
Then I should see "Could not discover end points for identity issuer: Invalid custom service"
|
||||
Then I should see "For security reasons only https connections are allowed, sorry"
|
||||
And I set the following fields to these values:
|
||||
| Service base URL | https://dc.imsglobal.org/ |
|
||||
And I press "Save changes"
|
||||
And I should see "Could not discover end points for identity issuer: Invalid custom service"
|
||||
And I should see "URL: https://dc.imsglobal.org/.well-known/openid-configuration"
|
||||
And "Allow services" "icon" should exist in the "Invalid custom service" "table_row"
|
||||
And "Do not allow login" "icon" should exist in the "Invalid custom service" "table_row"
|
||||
@@ -358,3 +362,21 @@ Feature: Basic OAuth2 functionality
|
||||
And I navigate to "Server > OAuth 2 services" in site administration
|
||||
And "Allow login" "icon" should exist in the "Empty custom service" "table_row"
|
||||
And "Do not allow services" "icon" should exist in the "Empty custom service" "table_row"
|
||||
|
||||
@javascript
|
||||
Scenario: Changes to "Authenticate token requests via HTTP headers" are saved
|
||||
Given I press "Custom"
|
||||
And I set the following fields to these values:
|
||||
| Name | Custom service |
|
||||
| Client ID | thisistheclientid |
|
||||
| Client secret | supersecret |
|
||||
And I press "Save changes"
|
||||
When I click on "Edit" "link" in the "Custom service" "table_row"
|
||||
And I click on "Authenticate token requests via HTTP headers" "checkbox"
|
||||
And I press "Save changes"
|
||||
And I click on "Edit" "link" in the "Custom service" "table_row"
|
||||
And the field "Authenticate token requests via HTTP headers" matches value "1"
|
||||
And I click on "Authenticate token requests via HTTP headers" "checkbox"
|
||||
And I press "Save changes"
|
||||
And I click on "Edit" "link" in the "Custom service" "table_row"
|
||||
Then the field "Authenticate token requests via HTTP headers" matches value ""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -462,7 +462,7 @@ Feature: User must accept policy managed by this plugin when logging in and sign
|
||||
And I am on site homepage
|
||||
And I change window size to "large"
|
||||
And I follow "Log in"
|
||||
When I press "Log in as a guest"
|
||||
When I press "Access as a guest"
|
||||
Then I should see "If you continue browsing this website, you agree to our policies"
|
||||
# Confirm when navigating, the pop-up policies are displayed.
|
||||
When I am on the "My courses" page
|
||||
@@ -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"
|
||||
@@ -626,7 +623,7 @@ Feature: User must accept policy managed by this plugin when logging in and sign
|
||||
And I am on site homepage
|
||||
And I follow "Log in"
|
||||
# First log in as a guest
|
||||
And I press "Log in as a guest"
|
||||
And I press "Access as a guest"
|
||||
# Now sign up
|
||||
And I follow "Log in"
|
||||
When I click on "Create new account" "link"
|
||||
|
||||
@@ -133,6 +133,16 @@ class course_bin extends base_bin {
|
||||
\backup::MODE_AUTOMATED,
|
||||
$user->id
|
||||
);
|
||||
|
||||
// When "backup_auto_activities" setting is disabled, activities can't be restored from recycle bin.
|
||||
$plan = $controller->get_plan();
|
||||
$activitiessettings = $plan->get_setting('activities');
|
||||
$settingsvalue = $activitiessettings->get_value();
|
||||
if (empty($settingsvalue)) {
|
||||
$controller->destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
$controller->execute_plan();
|
||||
|
||||
// We don't need the forced setting anymore, hence unsetting it.
|
||||
|
||||
@@ -242,6 +242,29 @@ class course_bin_test extends \advanced_testcase {
|
||||
$this->assertEquals(true, $attemptobj->is_finished());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the activity is NOT stored in bin when
|
||||
* in Automated backup setup settings "backup_auto_activities" is disabled.
|
||||
*
|
||||
* @dataProvider recycle_bin_settings_provider
|
||||
* @covers ::store_item
|
||||
*/
|
||||
public function test_coursemodule_restore_with_activity_setting_disabled() {
|
||||
|
||||
// Set the configuration to not include activities in the automated backup.
|
||||
set_config('backup_auto_activities', false, 'backup');
|
||||
|
||||
// Delete the course module.
|
||||
course_delete_module($this->quiz->cmid);
|
||||
|
||||
// Now, run the course module deletion adhoc task.
|
||||
\phpunit_util::run_all_adhoc_tasks();
|
||||
|
||||
// Check there is no items in the recycle bin.
|
||||
$recyclebin = new \tool_recyclebin\course_bin($this->course->id);
|
||||
$this->assertEquals(0, count($recyclebin->get_items()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that user data is not restored when module is restored.
|
||||
*
|
||||
|
||||
@@ -38,15 +38,6 @@ use core\check\result;
|
||||
*/
|
||||
class adhocqueue extends check {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct() {
|
||||
global $CFG;
|
||||
$this->id = 'adhocqueue';
|
||||
$this->name = get_string('checkadhocqueue', 'tool_task');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return result
|
||||
* @return result
|
||||
|
||||
@@ -38,17 +38,14 @@ use core\check\result;
|
||||
class cronrunning extends check {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* A link the running tasks report
|
||||
*
|
||||
* @return action_link|null
|
||||
*/
|
||||
public function __construct() {
|
||||
global $CFG;
|
||||
$this->id = 'cronrunning';
|
||||
$this->name = get_string('checkcronrunning', 'tool_task');
|
||||
if (empty($CFG->cronclionly)) {
|
||||
$this->actionlink = new \action_link(
|
||||
new \moodle_url('/admin/cron.php'),
|
||||
get_string('cron', 'admin'));
|
||||
}
|
||||
public function get_action_link(): ?\action_link {
|
||||
return new \action_link(
|
||||
new \moodle_url('/admin/tool/task/runningtasks.php'),
|
||||
get_string('runningtasks', 'tool_task'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -39,15 +39,13 @@ use core\check\result;
|
||||
class maxfaildelay extends check {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* Links to the task log report
|
||||
*
|
||||
* @return \action_link|null
|
||||
*/
|
||||
public function __construct() {
|
||||
global $CFG;
|
||||
$this->id = 'cronfaildelay';
|
||||
$this->name = get_string('checkmaxfaildelay', 'tool_task');
|
||||
$this->actionlink = new \action_link(
|
||||
new \moodle_url('/admin/tool/task/scheduledtasks.php'),
|
||||
get_string('scheduledtasks', 'tool_task'));
|
||||
public function get_action_link(): ?\action_link {
|
||||
$url = new \moodle_url('/admin/tasklogs.php');
|
||||
return new \action_link($url, get_string('tasklogs', 'tool_task'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -125,12 +125,10 @@ Feature: Upload users
|
||||
And I should see "Users created: 4"
|
||||
And I press "Continue"
|
||||
# Boost check.
|
||||
And I navigate to "Users > Accounts > Browse list of users" in site administration
|
||||
And I click on ".icon[title=Edit]" "css_element" in the "jonest@example.com" "table_row"
|
||||
And I am on the "jonest@example.com" "user > editing" page
|
||||
And I should see "Boost"
|
||||
# Classic check.
|
||||
And I navigate to "Users > Accounts > Browse list of users" in site administration
|
||||
And I click on ".icon[title=Edit]" "css_element" in the "reznor@example.com" "table_row"
|
||||
And I am on the "reznor@example.com" "user > editing" page
|
||||
And I should see "Classic"
|
||||
|
||||
@javascript
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -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);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -152,14 +152,11 @@ Feature: Apply tour filters to a tour
|
||||
| fullname | shortname | format | enablecompletion |
|
||||
| Course 1 | C1 | topics | 1 |
|
||||
| Course 2 | C2 | topics | 1 |
|
||||
And the following "activities" exist:
|
||||
| activity | course | name | firstpagetitle | wikimode |
|
||||
| wiki | C1 | Test wiki name | First page | collaborative |
|
||||
And I log in as "admin"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
And I add a "Wiki" to section "1" and I fill the form with:
|
||||
| Wiki name | Test wiki name |
|
||||
| Description | Test wiki description |
|
||||
| First page name | First page |
|
||||
| Wiki mode | Collaborative wiki |
|
||||
And I am on "Course 2" course homepage
|
||||
And I am on "Course 2" course homepage with editing mode on
|
||||
And I add a "Forum" to section "1" and I fill the form with:
|
||||
| Forum name | Test forum name |
|
||||
| Forum type | Standard forum for general use |
|
||||
|
||||
@@ -1828,7 +1828,7 @@ class model {
|
||||
*/
|
||||
public function get_name() {
|
||||
|
||||
if (trim($this->model->name) === '') {
|
||||
if (trim($this->model->name ?? '') === '') {
|
||||
return $this->get_target()->get_name();
|
||||
|
||||
} else {
|
||||
|
||||
+34
-18
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user