Compare commits
441 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d4f5e92ee1 | |||
| 2c58f9533b | |||
| 42726d645c | |||
| 02d08d2e4d | |||
| 9b90cf6584 | |||
| bdbc077efc | |||
| fb06894e75 | |||
| 6b4946e0a8 | |||
| 057f63909f | |||
| ea84a13152 | |||
| 9639feb9a3 | |||
| f009e2621e | |||
| 3449379bca | |||
| c77d6a79ec | |||
| d8558b1d42 | |||
| b8614dee98 | |||
| b852d34285 | |||
| 250ef211f0 | |||
| 2ebcdda606 | |||
| ce89c63b0b | |||
| 8acc343fb8 | |||
| 15a54747b9 | |||
| 7666131199 | |||
| 8b54213ad0 | |||
| 561a371423 | |||
| 6cc39f8e94 | |||
| 58e54a3d33 | |||
| eb1c1e79a1 | |||
| e7c973a195 | |||
| a982ebad0c | |||
| 27692e70b5 | |||
| 05db16c24d | |||
| 0aaf7fe953 | |||
| aea9770cfc | |||
| 05fe39a88f | |||
| 44305df587 | |||
| 638caa1355 | |||
| 6d29dc587e | |||
| 0f03ac03b5 | |||
| a11b849ebe | |||
| 4cfa5bca7c | |||
| 19ad8459d7 | |||
| 3f6919adc7 | |||
| 5d2ce59559 | |||
| e08dd90c32 | |||
| 26d7f16686 | |||
| 5b168b055f | |||
| 29ab56c4d2 | |||
| 6c640dacf4 | |||
| 234d24dfea | |||
| 835505681c | |||
| e930abfcf8 | |||
| cb06a0a271 | |||
| f8f19a4d8e | |||
| e2a8d063ee | |||
| 1fd2f0364c | |||
| 7f450dcea3 | |||
| c39a3357ed | |||
| cc10212366 | |||
| df2b3113d9 | |||
| d4f428311a | |||
| e2c677c3e8 | |||
| 80b3cb4951 | |||
| dfa5124909 | |||
| d3a6c97a43 | |||
| 316a50f2b7 | |||
| 2883fd28e4 | |||
| aa7f8f9e09 | |||
| c4b56bb393 | |||
| 156d214635 | |||
| 31b07a6298 | |||
| 2c82466444 | |||
| 91e70280a2 | |||
| 68e93dbfee | |||
| c12d07d9d4 | |||
| a29ef48500 | |||
| 0fae681a7c | |||
| 149df44e29 | |||
| da1fafe59d | |||
| 3821c707b8 | |||
| 1c7a5d9972 | |||
| 0fdbf26fd0 | |||
| 4f3c3432cd | |||
| b6715bb1ae | |||
| 40094ddbe0 | |||
| 8535c32bf3 | |||
| 6b90c69bda | |||
| 6f3337dcba | |||
| ff33f82ac2 | |||
| bf6cf572ca | |||
| 433109e2ac | |||
| 07bfca9041 | |||
| b2320ccd71 | |||
| 5e4ba1f036 | |||
| 7e17945088 | |||
| 535e970ad3 | |||
| dd8595035f | |||
| 06ae387ca5 | |||
| df48a73c5c | |||
| 70bbb44030 | |||
| 70ab78478f | |||
| 8a66b2b99a | |||
| ee5ac3f1f3 | |||
| 9e71503ab3 | |||
| 651ecb52d9 | |||
| 85845b13d7 | |||
| 7b0946129c | |||
| 7d7cfee0b7 | |||
| 6751ba6500 | |||
| 6d387e4b2b | |||
| ed3b73d9fc | |||
| f95b010ec5 | |||
| 97060da478 | |||
| 9715fd56da | |||
| 7e3d7fb02d | |||
| a9cc2ebb6e | |||
| 1a7ec4cf3d | |||
| 7715c626ad | |||
| 1c9098aeb8 | |||
| 5d18431087 | |||
| 0d27c76e34 | |||
| 8deeed9771 | |||
| 7893a34171 | |||
| e6966b7ae7 | |||
| 3e81ebecb6 | |||
| f9c5802f4e | |||
| e6ee7a5b1c | |||
| 17081ee902 | |||
| e817dd70db | |||
| bc30a904f4 | |||
| b2d9c6ff81 | |||
| 18dae18e20 | |||
| 18c7f3cdf3 | |||
| f0b462209a | |||
| 974db25816 | |||
| 7a02ae5644 | |||
| a9f24ce125 | |||
| a68fcabc65 | |||
| 2978ffc4a5 | |||
| a694661d5c | |||
| 7d77854fd6 | |||
| 17124e62a9 | |||
| d9ae012d15 | |||
| c03706fafb | |||
| f77aa8c4a7 | |||
| 4044abd156 | |||
| 6164556c85 | |||
| 69a8f920d4 | |||
| ff263b6dbc | |||
| 763c71194b | |||
| 19ac0728f2 | |||
| 39cb2a03e5 | |||
| c8c84b4af1 | |||
| 5ec01a7226 | |||
| f878e05d62 | |||
| aa0e2cffdd | |||
| c18b59808c | |||
| a0d8c025f7 | |||
| 137d311fd1 | |||
| 57f20b6cb3 | |||
| a10506b8d7 | |||
| 28b8fc9896 | |||
| f55b829f44 | |||
| 836b2c23a2 | |||
| 0068701bc6 | |||
| 83a978d1cd | |||
| d05795db8e | |||
| 629a7066be | |||
| 058b11e57b | |||
| aa230649d9 | |||
| f8cce391b2 | |||
| a7d476c35f | |||
| 5425fb3d79 | |||
| 587d737690 | |||
| 31b68a2b5d | |||
| a48f00d940 | |||
| f7da739ea0 | |||
| ebb346cd8e | |||
| b39d8d5027 | |||
| cf48f359ad | |||
| 8ac29c9422 | |||
| 7f3617860c | |||
| b35f70ce14 | |||
| 77d0c0caa9 | |||
| c2193308a6 | |||
| 9456911fe9 | |||
| bd2c8fdd54 | |||
| 234d81e39b | |||
| 9747b5b26b | |||
| 508d462fa7 | |||
| f687e02dec | |||
| 72c3038925 | |||
| d393030c58 | |||
| d68ea84924 | |||
| 85825dcc55 | |||
| 80f152296c | |||
| 48aa9aae56 | |||
| 025065daf5 | |||
| 49be0febdc | |||
| 73f57aab98 | |||
| 457f0e9384 | |||
| 4b15c803e2 | |||
| 50aff515b7 | |||
| 5b33398f07 | |||
| 47568f31c5 | |||
| eba83406da | |||
| 137c6f36bb | |||
| d9043b324d | |||
| e57e560385 | |||
| eebd5742b6 | |||
| 0837a24c05 | |||
| f2e48b39f6 | |||
| 5060c53176 | |||
| 3f392e934e | |||
| c1e4679ed5 | |||
| 9254b7e75a | |||
| 1d609ca0ee | |||
| 61fabd3e73 | |||
| e8d03fdae4 | |||
| c8922328b2 | |||
| 19635ef720 | |||
| 9d5a4746ab | |||
| e4406472f0 | |||
| cf2f67272a | |||
| 2ebac5604c | |||
| c5f48698da | |||
| c2a043f50d | |||
| 001c78285f | |||
| af90bb572e | |||
| 882a660f7e | |||
| adc1de569d | |||
| 607716f206 | |||
| 5358ce28ee | |||
| fef8dda150 | |||
| b049731dfb | |||
| 5277dca108 | |||
| 4be4d0d489 | |||
| 3d0bcb7172 | |||
| 7dac3707fd | |||
| 037a7d5d6c | |||
| f5587cf881 | |||
| a18c0b7c8b | |||
| 097a879e1a | |||
| 58acb96ee6 | |||
| 1901cd3fdf | |||
| 2c9a5c009f | |||
| b564a40d74 | |||
| 179bfdf5c5 | |||
| eb975467bd | |||
| 5efbd6e0e1 | |||
| fa20b1790f | |||
| 7d579dd4bb | |||
| 6b0661b9ea | |||
| afd57ca9a0 | |||
| c14e75a017 | |||
| 318e2344c0 | |||
| 17ed404ccc | |||
| 5b54a87858 | |||
| ea49be1e61 | |||
| 0f3d3b2d77 | |||
| dee41e0648 | |||
| c11b1c6b3f | |||
| e2362b0a7a | |||
| d63721cb15 | |||
| 66b1733bda | |||
| 81821e6833 | |||
| 965a5fdb27 | |||
| 6aa2828161 | |||
| d46a37ebd4 | |||
| 8c358aefd9 | |||
| 40aceb0256 | |||
| f64240ce94 | |||
| 10c7926c0f | |||
| 8a26315686 | |||
| 0e8d4d8e2a | |||
| 5ac11701ac | |||
| dc65317ba6 | |||
| f0ae7c4343 | |||
| b52c1855fa | |||
| 6e1e63cc6b | |||
| 50ae525be9 | |||
| a700d119df | |||
| 9ccd794b36 | |||
| 49673645ea | |||
| e6316e6b9b | |||
| 5003afbe48 | |||
| 7e1bbaaf20 | |||
| d1111bc3e8 | |||
| 8de03684ac | |||
| 2db07c4d0a | |||
| 3ac3d942dd | |||
| 84308c7dfc | |||
| 24a0e76ebf | |||
| 2444b2b787 | |||
| b704fa7dda | |||
| 9d3d193f29 | |||
| 43c7557e84 | |||
| da74ac4987 | |||
| 1e5bc3a393 | |||
| 7c20cd1b81 | |||
| edf1a1ac8c | |||
| 5a2acac1ed | |||
| 719c95009e | |||
| 860b4b941a | |||
| 26ed48ec40 | |||
| 58ba1a9448 | |||
| 40d2b4de39 | |||
| bce1d7ff85 | |||
| 3f439ff9d4 | |||
| 0c34355c85 | |||
| ab118d48f5 | |||
| 6df3213c9b | |||
| 6dbd0e938f | |||
| 8889f6f26f | |||
| 36aa09726b | |||
| 196c2ae7a5 | |||
| 4d217e3a1a | |||
| 09e3db1aa6 | |||
| 8815ecb3fd | |||
| 8482210209 | |||
| 281ef7ef8d | |||
| 6970580b4c | |||
| 652633b467 | |||
| 5a7963d9f5 | |||
| 56ff5e78f1 | |||
| e31db928b5 | |||
| 03fcd81892 | |||
| 05a41b6e32 | |||
| 8a38a37f1f | |||
| 4ea1bd3537 | |||
| 35b7cbf027 | |||
| 87eb0fdf21 | |||
| c8aef2e4ba | |||
| fa72ebc155 | |||
| 6d7f162df0 | |||
| 97d64fb809 | |||
| fd3c586992 | |||
| 61fb8ee988 | |||
| 44f8c11123 | |||
| e5b9510af2 | |||
| ab151f86ab | |||
| e36e40f530 | |||
| 3a8a8c8463 | |||
| 2f811e6acf | |||
| fb7da9c74f | |||
| d27862e2ca | |||
| c7fde934c7 | |||
| be50fbe20c | |||
| 74bf7382dd | |||
| cb075f6257 | |||
| 8a1b5fc2b0 | |||
| bd22abe778 | |||
| b3867e7422 | |||
| 8c298f14bb | |||
| 58bd2ed8e4 | |||
| 36ca488c06 | |||
| bca28b03a9 | |||
| a6216bb890 | |||
| 94b14df11d | |||
| 0dc60026c8 | |||
| 63a1ec81c4 | |||
| 150fc0837a | |||
| 72958879c6 | |||
| d8d560b7fe | |||
| 917dd49db2 | |||
| 80691cf5a3 | |||
| e20720ce0e | |||
| f976349b95 | |||
| 312a166f1e | |||
| f16ca8fa7a | |||
| 846a8b6568 | |||
| 692d3090fa | |||
| 8092eb95c4 | |||
| 2b864ceec3 | |||
| 80083244b6 | |||
| 560f4d895b | |||
| 95c3017da4 | |||
| 8fffc08ee8 | |||
| 364230d6f8 | |||
| 45f674e8cb | |||
| 4ba166cf80 | |||
| 3a779663aa | |||
| ca7b24f6d0 | |||
| 35a2a34a7d | |||
| 603c794190 | |||
| 38c4d51947 | |||
| a86228f547 | |||
| b30033c6c0 | |||
| 5661849a02 | |||
| 66e8ce9976 | |||
| cf92eaeb0b | |||
| f7f40e54e6 | |||
| 627177855a | |||
| 8c7983a43f | |||
| 0d0aa11e19 | |||
| eb0439b5da | |||
| 37101f0698 | |||
| 4b8a001bb0 | |||
| a9cf9d5519 | |||
| 53275e5b8f | |||
| 518e5799e4 | |||
| 6a610605f6 | |||
| e4eb16bd9f | |||
| 3e831cc8c5 | |||
| 5ecca13a6f | |||
| 278f58dae0 | |||
| 73cf6fb65c | |||
| 6483b5d68f | |||
| 6c24b8289f | |||
| e2e00a9dd4 | |||
| 8168155e17 | |||
| d6650b63bf | |||
| 174389cc26 | |||
| 03454c2262 | |||
| e591ddc4e8 | |||
| e1767fa80f | |||
| e25033e42d | |||
| dd2c19eb09 | |||
| 3b8b8d7d84 | |||
| 5378285a9a | |||
| 3a098250f1 | |||
| 148ddd26e7 | |||
| cdf432ee17 | |||
| b1e918edd8 | |||
| f58df6fa88 | |||
| eae562817d | |||
| 033a59f9c5 | |||
| a34a8636be | |||
| 08d2e7c136 | |||
| 886bbcbdb8 | |||
| 0c3e759d98 | |||
| 607a452a74 | |||
| 3ef6e04f3c | |||
| dfc7dcd062 | |||
| 80d773868c | |||
| eb127cc93b | |||
| e5eb66e405 | |||
| 67f869a081 | |||
| a11a9ca5aa | |||
| 3fe8dbd027 | |||
| 6aeecbdf46 |
@@ -1,8 +1,12 @@
|
||||
{
|
||||
'plugins': [
|
||||
'babel',
|
||||
'@babel',
|
||||
'promise',
|
||||
'jsdoc',
|
||||
'jsdoc'
|
||||
],
|
||||
'extends': [
|
||||
'eslint:recommended',
|
||||
'plugin:promise/recommended'
|
||||
],
|
||||
'env': {
|
||||
'browser': true,
|
||||
@@ -16,42 +20,19 @@
|
||||
'M': true,
|
||||
'Y': true
|
||||
},
|
||||
// Warn about unused eslint-disable statements.
|
||||
'reportUnusedDisableDirectives': true,
|
||||
'rules': {
|
||||
// See http://eslint.org/docs/rules/ for all rules and explanations of all
|
||||
// rules.
|
||||
|
||||
// === Possible Errors ===
|
||||
'comma-dangle': 'off',
|
||||
'no-compare-neg-zero': 'error',
|
||||
'no-cond-assign': 'error',
|
||||
'no-console': 'error',
|
||||
'no-constant-condition': 'error',
|
||||
'no-control-regex': 'error',
|
||||
'no-debugger': 'error',
|
||||
'no-dupe-args': 'error',
|
||||
'no-dupe-keys': 'error',
|
||||
'no-duplicate-case': 'error',
|
||||
'no-empty': 'warn',
|
||||
'no-empty-character-class': 'error',
|
||||
'no-ex-assign': 'error',
|
||||
'no-extra-boolean-cast': 'error',
|
||||
'no-extra-parens': 'off',
|
||||
'no-extra-semi': 'error',
|
||||
'no-func-assign': 'error',
|
||||
'no-inner-declarations': 'error',
|
||||
'no-invalid-regexp': 'error',
|
||||
'no-irregular-whitespace': 'error',
|
||||
'no-obj-calls': 'error',
|
||||
'no-prototype-builtins': 'off',
|
||||
'no-regex-spaces': 'error',
|
||||
'no-sparse-arrays': 'error',
|
||||
'no-unexpected-multiline': 'error',
|
||||
'no-unreachable': 'warn',
|
||||
'no-unsafe-finally': 'error',
|
||||
'no-unsafe-negation': 'error',
|
||||
'use-isnan': 'error',
|
||||
'valid-jsdoc': ['warn', { 'requireReturn': false, 'requireParamDescription': false, 'requireReturnDescription': false }],
|
||||
'valid-typeof': 'error',
|
||||
|
||||
// === Best Practices ===
|
||||
// (these mostly match our jshint config)
|
||||
@@ -63,15 +44,12 @@
|
||||
'dot-notation': 'warn',
|
||||
'no-alert': 'warn',
|
||||
'no-caller': 'error',
|
||||
'no-case-declarations': 'error',
|
||||
'no-div-regex': 'error',
|
||||
'no-empty-pattern': 'error',
|
||||
'no-empty-function': 'warn',
|
||||
'no-eq-null': 'error',
|
||||
'no-eval': 'error',
|
||||
'no-extend-native': 'error',
|
||||
'no-extra-bind': 'warn',
|
||||
'no-fallthrough': 'error',
|
||||
'no-floating-decimal': 'warn',
|
||||
'no-global-assign': 'warn',
|
||||
'no-implied-eval': 'error',
|
||||
@@ -83,28 +61,22 @@
|
||||
'no-multi-str': 'error',
|
||||
'no-new-func': 'error',
|
||||
'no-new-wrappers': 'error',
|
||||
'no-octal': 'error',
|
||||
'no-octal-escape': 'error',
|
||||
'no-proto': 'error',
|
||||
'no-redeclare': 'warn',
|
||||
'no-restricted-globals': ['error', { 'name': 'Notification' }],
|
||||
'no-return-assign': 'error',
|
||||
'no-script-url': 'error',
|
||||
'no-self-assign': 'error',
|
||||
'no-self-compare': 'error',
|
||||
'no-sequences': 'warn',
|
||||
'no-throw-literal': 'warn',
|
||||
'no-unmodified-loop-condition': 'error',
|
||||
'no-unused-expressions': 'error',
|
||||
'no-unused-labels': 'error',
|
||||
'no-useless-call': 'warn',
|
||||
'no-useless-escape': 'warn',
|
||||
'no-with': 'error',
|
||||
'wrap-iife': ['error', 'any'],
|
||||
|
||||
// === Variables ===
|
||||
'no-delete-var': 'error',
|
||||
'no-undef': 'error',
|
||||
'no-undef-init': 'error',
|
||||
'no-unused-vars': ['error', { 'caughtErrors': 'none' }],
|
||||
|
||||
@@ -137,8 +109,6 @@
|
||||
'max-statements-per-line': ['warn', { max: 2 }],
|
||||
'new-cap': ['warn', { 'properties': false }],
|
||||
'new-parens': 'warn',
|
||||
'newline-after-var': 'off',
|
||||
'newline-before-return': 'off',
|
||||
'newline-per-chained-call': 'off',
|
||||
'no-array-constructor': 'off',
|
||||
'no-bitwise': 'error',
|
||||
@@ -146,7 +116,6 @@
|
||||
'no-inline-comments': 'off',
|
||||
'no-lonely-if': 'off',
|
||||
'no-mixed-operators': 'off',
|
||||
'no-mixed-spaces-and-tabs': 'error',
|
||||
'no-multiple-empty-lines': 'warn',
|
||||
'no-negated-condition': 'off',
|
||||
'no-nested-ternary': 'warn',
|
||||
@@ -168,7 +137,6 @@
|
||||
'padded-blocks': 'off',
|
||||
'quote-props': ['warn', 'as-needed', {'unnecessary': false, 'keywords': true, 'numbers': true}],
|
||||
'quotes': 'off',
|
||||
'require-jsdoc': 'warn',
|
||||
'semi': 'error',
|
||||
'semi-spacing': ['warn', {'before': false, 'after': true}],
|
||||
'sort-vars': 'off',
|
||||
@@ -181,21 +149,19 @@
|
||||
'unicode-bom': 'error',
|
||||
'wrap-regex': 'off',
|
||||
|
||||
// === Promises ===
|
||||
// === Promises (override plugin:promise/recommended) ===
|
||||
'promise/always-return': 'warn',
|
||||
'promise/no-return-wrap': 'warn',
|
||||
'promise/param-names': 'warn',
|
||||
'promise/catch-or-return': ['warn', {terminationMethod: ['catch', 'fail']}],
|
||||
'promise/no-native': 'warn',
|
||||
'promise/no-promise-in-callback': 'warn',
|
||||
'promise/no-callback-in-promise': 'warn',
|
||||
'promise/avoid-new': 'warn',
|
||||
|
||||
// === Deprecations ===
|
||||
"no-restricted-properties": ['warn', {
|
||||
'object': 'M',
|
||||
'property': 'str',
|
||||
'message': 'Use AMD module "core/str" or M.util.get_string()'
|
||||
'message': 'Use "core/str" module or M.util.get_string()'
|
||||
}],
|
||||
},
|
||||
overrides: [
|
||||
@@ -235,7 +201,6 @@
|
||||
parser: '@babel/eslint-parser',
|
||||
// Check AMD with some slightly stricter rules.
|
||||
rules: {
|
||||
'no-unused-vars': 'error',
|
||||
'no-implicit-globals': 'error',
|
||||
// Disable all of the rules that have babel versions.
|
||||
'new-cap': 'off',
|
||||
@@ -247,65 +212,37 @@
|
||||
'semi': 'off',
|
||||
'no-unused-expressions': 'off',
|
||||
// Enable all of the babel version of these rules.
|
||||
'babel/new-cap': ['warn', { 'properties': false }],
|
||||
'@babel/new-cap': ['warn', { 'properties': false }],
|
||||
// Not using this rule for the time being because it isn't
|
||||
// compatible with jQuery and ES6.
|
||||
'babel/no-invalid-this': 'off',
|
||||
'babel/object-curly-spacing': 'warn',
|
||||
// This is off in the original style int.
|
||||
'babel/quotes': 'off',
|
||||
'babel/semi': 'error',
|
||||
'babel/no-unused-expressions': 'error',
|
||||
'@babel/no-invalid-this': 'off',
|
||||
'@babel/object-curly-spacing': 'warn',
|
||||
'@babel/semi': 'error',
|
||||
'@babel/no-unused-expressions': 'error',
|
||||
// === Promises ===
|
||||
// We have Promise now that we're using ES6.
|
||||
'promise/no-native': 'off',
|
||||
'promise/avoid-new': 'off',
|
||||
|
||||
// === JSDocs ===
|
||||
"jsdoc/check-access": [
|
||||
'error',
|
||||
],
|
||||
"jsdoc/check-alignment": 1, // Recommended.
|
||||
"jsdoc/check-param-names": [
|
||||
'error',
|
||||
],
|
||||
"jsdoc/check-property-names": [
|
||||
'error',
|
||||
],
|
||||
"jsdoc/empty-tags": [
|
||||
'error',
|
||||
],
|
||||
"jsdoc/implements-on-classes": [
|
||||
'error',
|
||||
],
|
||||
"jsdoc/multiline-blocks": [
|
||||
'error',
|
||||
],
|
||||
"jsdoc/require-jsdoc": [
|
||||
'error',
|
||||
],
|
||||
"jsdoc/require-param": [
|
||||
'error',
|
||||
],
|
||||
"jsdoc/require-param-name": [
|
||||
'error',
|
||||
],
|
||||
"jsdoc/require-param-type": [
|
||||
'error',
|
||||
],
|
||||
"jsdoc/require-property": [
|
||||
'error',
|
||||
],
|
||||
"jsdoc/require-property-name": [
|
||||
'error',
|
||||
],
|
||||
"jsdoc/require-property-type": [
|
||||
'error',
|
||||
],
|
||||
'jsdoc/check-access': 'error',
|
||||
'jsdoc/check-alignment': 1, // Recommended.
|
||||
'jsdoc/check-param-names': 'error',
|
||||
'jsdoc/check-property-names': 'error',
|
||||
'jsdoc/empty-tags': 'error',
|
||||
'jsdoc/implements-on-classes': 'error',
|
||||
'jsdoc/multiline-blocks': 'error',
|
||||
'jsdoc/require-jsdoc': 'error',
|
||||
'jsdoc/require-param': 'error',
|
||||
'jsdoc/require-param-name': 'error',
|
||||
'jsdoc/require-param-type': 'error',
|
||||
'jsdoc/require-property': 'error',
|
||||
'jsdoc/require-property-name': 'error',
|
||||
'jsdoc/require-property-type': 'error'
|
||||
},
|
||||
parserOptions: {
|
||||
'sourceType': 'module',
|
||||
'requireConfigFile': false,
|
||||
'requireConfigFile': false
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -3,7 +3,7 @@ name: Core
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- master
|
||||
- main
|
||||
- MOODLE_[0-9]+_STABLE
|
||||
tags-ignore:
|
||||
- v[0-9]+.[0-9]+.[0-9]+*
|
||||
@@ -23,10 +23,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checking out code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Configuring node & npm
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
@@ -103,7 +103,7 @@ jobs:
|
||||
coverage: none
|
||||
|
||||
- name: Checking out code from ${{ env.GITHUB_REF_SLUG }}
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setting up PHPUnit
|
||||
env:
|
||||
|
||||
@@ -16,10 +16,10 @@ jobs:
|
||||
git config --global core.eol lf
|
||||
|
||||
- name: Checking out code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Configuring node & npm
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
@@ -112,7 +112,7 @@ jobs:
|
||||
git config --global core.eol lf
|
||||
|
||||
- name: Checking out code from ${{ env.GITHUB_REF_SLUG }}
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Needs to be done after php is available, git configured and Moodle checkout has happened.
|
||||
- name: Setting up moodle-exttests service
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* Function to generate the destination for the uglify task
|
||||
* Function to generate the destination for the minification task
|
||||
* (e.g. build/file.min.js). This function will be passed to
|
||||
* the rename property of files array when building dynamically:
|
||||
* http://gruntjs.com/configuring-tasks#building-the-files-object-dynamically
|
||||
@@ -60,7 +60,6 @@ module.exports = grunt => {
|
||||
grunt.registerTask('js', ['amd', 'yui']);
|
||||
|
||||
// Register NPM tasks.
|
||||
grunt.loadNpmTasks('grunt-contrib-uglify');
|
||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||
grunt.loadNpmTasks('grunt-rollup');
|
||||
|
||||
@@ -104,6 +103,7 @@ module.exports = grunt => {
|
||||
// The queue runner will run the next `size` items in the queue.
|
||||
const runQueue = (size = 1) => {
|
||||
queue.splice(0, size).forEach(resolve => {
|
||||
grunt.log.debug(`Item resolved. Kicking off next one.`);
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
@@ -113,15 +113,17 @@ module.exports = grunt => {
|
||||
|
||||
// The options hook is run in parallel.
|
||||
// We can return an unresolved Promise which is queued for later resolution.
|
||||
options: async() => {
|
||||
options: async(options) => {
|
||||
return new Promise(resolve => {
|
||||
queue.push(resolve);
|
||||
startQueue();
|
||||
return options;
|
||||
});
|
||||
},
|
||||
|
||||
// When an item in the queue completes, start the next item in the queue.
|
||||
buildEnd: () => {
|
||||
generateBundle: (options, bundle) => {
|
||||
grunt.log.debug(`Finished output phase for ${Object.keys(bundle).join(', ')}`);
|
||||
runQueue();
|
||||
},
|
||||
};
|
||||
@@ -151,24 +153,10 @@ module.exports = grunt => {
|
||||
//
|
||||
// It also adds the Moodle plugin name to the AMD module definition
|
||||
// so that it can be imported as expected in other modules.
|
||||
path.resolve('.grunt/babel-plugin-add-module-to-define.js'),
|
||||
'@babel/plugin-syntax-dynamic-import',
|
||||
'@babel/plugin-syntax-import-meta',
|
||||
['@babel/plugin-proposal-class-properties', {'loose': false}],
|
||||
'@babel/plugin-proposal-json-strings'
|
||||
path.resolve('.grunt/babel-plugin-add-module-to-define.js')
|
||||
],
|
||||
presets: [
|
||||
['@babel/preset-env', {
|
||||
targets: {
|
||||
browsers: [
|
||||
">0.3%",
|
||||
"last 2 versions",
|
||||
"not ie >= 0",
|
||||
"not op_mini all",
|
||||
"not Opera > 0",
|
||||
"not dead"
|
||||
]
|
||||
},
|
||||
modules: false,
|
||||
useBuiltIns: false
|
||||
}]
|
||||
|
||||
+28
-12
@@ -20,17 +20,33 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
module.exports = grunt => {
|
||||
// Project configuration.
|
||||
grunt.config.merge({
|
||||
jsdoc: {
|
||||
dist: {
|
||||
options: {
|
||||
configure: ".grunt/jsdoc/jsdoc.conf.js",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
module.exports = (grunt) => {
|
||||
const path = require('path');
|
||||
|
||||
grunt.loadNpmTasks('grunt-jsdoc');
|
||||
grunt.registerTask('jsdoc', 'Generate JavaScript documentation using jsdoc', function() {
|
||||
const done = this.async();
|
||||
const configuration = path.resolve('.grunt/jsdoc/jsdoc.conf.js');
|
||||
|
||||
grunt.util.spawn({
|
||||
cmd: 'jsdoc',
|
||||
args: [
|
||||
'--configure',
|
||||
configuration,
|
||||
]
|
||||
}, function(error, result, code) {
|
||||
if (result.stdout) {
|
||||
grunt.log.write(result.stdout);
|
||||
}
|
||||
|
||||
if (result.stderr) {
|
||||
grunt.log.error(result.stderr);
|
||||
}
|
||||
if (error) {
|
||||
grunt.fail.fatal(`JSDoc failed with error code ${code}`);
|
||||
} else {
|
||||
grunt.log.write('JSDoc completed successfully'.green);
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -29,6 +29,7 @@ module.exports = grunt => {
|
||||
// Use a fully-qualified path.
|
||||
src: files,
|
||||
options: {
|
||||
quietDeprecationWarnings: true,
|
||||
configOverrides: {
|
||||
rules: {
|
||||
// These rules have to be disabled in .stylelintrc for scss compat.
|
||||
@@ -45,7 +46,10 @@ module.exports = grunt => {
|
||||
return {
|
||||
stylelint: {
|
||||
scss: {
|
||||
options: {syntax: 'scss'},
|
||||
options: {
|
||||
quietDeprecationWarnings: true,
|
||||
customSyntax: 'postcss-scss',
|
||||
},
|
||||
src: files,
|
||||
},
|
||||
},
|
||||
|
||||
+16
-4
@@ -1,9 +1,14 @@
|
||||
{
|
||||
"customSyntax": "postcss-scss",
|
||||
"plugins": [
|
||||
"stylelint-csstree-validator"
|
||||
],
|
||||
"rules": {
|
||||
"csstree/validator": true,
|
||||
"csstree/validator": {
|
||||
"syntaxExtensions": [
|
||||
"sass"
|
||||
]
|
||||
},
|
||||
"at-rule-empty-line-before": [ "always",
|
||||
{"except": [ "blockless-after-blockless"], ignore: ["after-comment", "inside-block"]}
|
||||
],
|
||||
@@ -45,7 +50,7 @@
|
||||
"function-name-case": "lower",
|
||||
"function-parentheses-newline-inside": "always-multi-line",
|
||||
"function-parentheses-space-inside": "never-single-line",
|
||||
"function-url-scheme-blacklist": ["data"],
|
||||
"function-url-scheme-disallowed-list": ["data"],
|
||||
"function-whitespace-after": "always",
|
||||
"indentation": 4,
|
||||
"keyframe-declaration-no-important": true,
|
||||
@@ -84,12 +89,19 @@
|
||||
"selector-type-no-unknown": true,
|
||||
"string-no-newline": true,
|
||||
"time-min-milliseconds": 100,
|
||||
"unit-blacklist": ["pt"],
|
||||
"unit-disallowed-list": ["pt"],
|
||||
"unit-case": "lower",
|
||||
"unit-no-unknown": true,
|
||||
"value-keyword-case": ["lower", {"ignoreKeywords": ["/(@|$)/"]}],
|
||||
"value-list-comma-newline-after": "always-multi-line",
|
||||
"value-list-comma-space-after": "always-single-line",
|
||||
"value-list-comma-space-before": "never",
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["**/yui/**/*.css"],
|
||||
"rules": {
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -3923,6 +3923,8 @@
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_xmlrpc_usage" level="optional">
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_oracle_usage" level="optional">
|
||||
</CUSTOM_CHECK>
|
||||
</CUSTOM_CHECKS>
|
||||
</MOODLE>
|
||||
<MOODLE version="4.2" requires="3.11.8">
|
||||
@@ -3939,6 +3941,7 @@
|
||||
<VENDOR name="oracle" version="19" />
|
||||
</DATABASE>
|
||||
<PHP version="8.0.0" level="required">
|
||||
<RESTRICT function="restrict_php_version_83" message="unsupportedphpversion83" />
|
||||
</PHP>
|
||||
<PCREUNICODE level="optional">
|
||||
<FEEDBACK>
|
||||
@@ -4112,6 +4115,8 @@
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mod_assignment" level="required">
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_oracle_usage" level="optional">
|
||||
</CUSTOM_CHECK>
|
||||
</CUSTOM_CHECKS>
|
||||
</MOODLE>
|
||||
<MOODLE version="4.3" requires="3.11.8">
|
||||
@@ -4128,6 +4133,7 @@
|
||||
<VENDOR name="oracle" version="19" />
|
||||
</DATABASE>
|
||||
<PHP version="8.0.0" level="required">
|
||||
<RESTRICT function="restrict_php_version_83" message="unsupportedphpversion83" />
|
||||
</PHP>
|
||||
<PCREUNICODE level="optional">
|
||||
<FEEDBACK>
|
||||
@@ -4303,6 +4309,8 @@
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_db_prefix_length" level="required">
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_oracle_usage" level="optional">
|
||||
</CUSTOM_CHECK>
|
||||
</CUSTOM_CHECKS>
|
||||
</MOODLE>
|
||||
<MOODLE version="4.4" requires="4.1.2">
|
||||
@@ -4494,6 +4502,205 @@
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_db_prefix_length" level="required">
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_oracle_usage" level="optional">
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_async_backup" level="recommended">
|
||||
</CUSTOM_CHECK>
|
||||
</CUSTOM_CHECKS>
|
||||
</MOODLE>
|
||||
<MOODLE version="4.5" requires="4.1.2">
|
||||
<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.1.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_CHECK file="lib/upgradelib.php" function="check_db_prefix_length" level="required">
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_oracle_usage" level="optional">
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_async_backup" level="recommended">
|
||||
</CUSTOM_CHECK>
|
||||
</CUSTOM_CHECKS>
|
||||
</MOODLE>
|
||||
</COMPATIBILITY_MATRIX>
|
||||
|
||||
+5
-2
@@ -2052,8 +2052,10 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
// We are checking installed & enabled things
|
||||
if ($environment_result->getLevel() == 'required') {
|
||||
$stringtouse = 'environmentrequirecustomcheck';
|
||||
} else {
|
||||
} else if ($environment_result->getLevel() == 'optional') {
|
||||
$stringtouse = 'environmentrecommendcustomcheck';
|
||||
} else {
|
||||
$stringtouse = 'environmentshouldfixcustomcheck';
|
||||
}
|
||||
|
||||
} else if ($environment_result->getPart() == 'php_setting') {
|
||||
@@ -2084,7 +2086,8 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
if ($status) { //Handle ok result (ok)
|
||||
$status = get_string('statusok');
|
||||
} else {
|
||||
if ($environment_result->getLevel() == 'optional') {//Handle check result (warning)
|
||||
// Handle check result (warning).
|
||||
if (in_array($environment_result->getLevel(), ['optional', 'recommended'])) {
|
||||
$status = get_string('check');
|
||||
$warningline = true;
|
||||
} else { //Handle error result (error)
|
||||
|
||||
@@ -152,7 +152,8 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
|
||||
$sensiblesettingsdefault .= 'server_password@@search_solr, ssl_keypassword@@search_solr, ';
|
||||
$sensiblesettingsdefault .= 'alternateserver_password@@search_solr, alternatessl_keypassword@@search_solr, ';
|
||||
$sensiblesettingsdefault .= 'test_password@@cachestore_redis, password@@mlbackend_python, ';
|
||||
$sensiblesettingsdefault .= 'badges_badgesalt@@none, calendar_exportsalt@@none';
|
||||
$sensiblesettingsdefault .= 'badges_badgesalt@@none, calendar_exportsalt@@none, ';
|
||||
$sensiblesettingsdefault .= 'bigbluebuttonbn_shared_secret@@none';
|
||||
$temp->add(new admin_setting_configtextarea('adminpresets/sensiblesettings',
|
||||
get_string('sensiblesettings', 'core_adminpresets'),
|
||||
get_string('sensiblesettingstext', 'core_adminpresets'),
|
||||
|
||||
@@ -18,7 +18,7 @@ Feature: An administrator can browse user accounts
|
||||
When I navigate to "Users > Accounts > Browse list of users" in site administration
|
||||
# Name field always present, email field is default for showidentity.
|
||||
Then the following should exist in the "users" table:
|
||||
| First name / Last name | Email address |
|
||||
| First name | Email address |
|
||||
| User One | one@example.com |
|
||||
| User Two | two@example.com |
|
||||
# Should not see other identity fields or non-default name fields.
|
||||
@@ -34,7 +34,7 @@ Feature: An administrator can browse user accounts
|
||||
| alternativefullnameformat | firstnamephonetic lastname |
|
||||
When I navigate to "Users > Accounts > Browse list of users" in site administration
|
||||
Then the following should exist in the "users" table:
|
||||
| First name - phonetic / Last name | Email address |
|
||||
| First name - phonetic | Email address |
|
||||
| Yewzer One | one@example.com |
|
||||
| Yoozare Two | two@example.com |
|
||||
|
||||
@@ -43,7 +43,7 @@ Feature: An administrator can browse user accounts
|
||||
| showuseridentity | department,profile_field_frog |
|
||||
When I navigate to "Users > Accounts > Browse list of users" in site administration
|
||||
Then the following should exist in the "users" table:
|
||||
| First name / Last name | Favourite frog | Department |
|
||||
| First name | Favourite frog | Department |
|
||||
| User One | Kermit | Attack |
|
||||
| User Two | Tree | Defence |
|
||||
And I should not see "Email address" in the "table" "css_element"
|
||||
|
||||
@@ -48,25 +48,3 @@ Feature: View task logs report and use its filters
|
||||
| operator | shouldornotsee |
|
||||
| Less than | should not see |
|
||||
| Greater than | should see |
|
||||
|
||||
@javascript
|
||||
Scenario: Reset task log filters
|
||||
Given I log in as "admin"
|
||||
And I change window size to "large"
|
||||
And I navigate to "Server > Tasks > Task logs" in site administration
|
||||
When I click on "Filters" "button"
|
||||
And I set the following fields in the "Result" "core_reportbuilder > Filter" to these values:
|
||||
| Result operator | Is equal to |
|
||||
| Result value | Fail |
|
||||
And I click on "Apply" "button" in the "[data-region='report-filters']" "css_element"
|
||||
Then I should see "Filters applied"
|
||||
And I should see "Filters (1)" in the "#dropdownFiltersButton" "css_element"
|
||||
And I should see "Nothing to display"
|
||||
And I click on "Reset all" "button" in the "[data-region='report-filters']" "css_element"
|
||||
And I should see "Filters reset"
|
||||
And I should not see "Filters (1)" in the "#dropdownFiltersButton" "css_element"
|
||||
And I should see "Filters" in the "#dropdownFiltersButton" "css_element"
|
||||
And "[data-region='report-filters']" "css_element" should be visible
|
||||
And the following fields in the "Result" "core_reportbuilder > Filter" match these values:
|
||||
| Result operator | Is any value |
|
||||
And I should not see "Nothing to display"
|
||||
|
||||
@@ -64,7 +64,7 @@ class delete extends base {
|
||||
* Delete the DB preset
|
||||
*/
|
||||
public function execute(): void {
|
||||
confirm_sesskey();
|
||||
require_sesskey();
|
||||
|
||||
$this->manager->delete_preset($this->id);
|
||||
|
||||
|
||||
@@ -52,8 +52,6 @@ class export extends base {
|
||||
* Stores a preset into the DB.
|
||||
*/
|
||||
public function execute(): void {
|
||||
confirm_sesskey();
|
||||
|
||||
$url = new \moodle_url('/admin/tool/admin_presets/index.php', ['action' => 'export', 'mode' => 'execute']);
|
||||
$this->moodleform = new export_form($url);
|
||||
|
||||
@@ -87,7 +85,7 @@ class export extends base {
|
||||
* @throws xml_writer_exception
|
||||
*/
|
||||
public function download_xml(): void {
|
||||
confirm_sesskey();
|
||||
require_sesskey();
|
||||
|
||||
list($xmlstr, $filename) = $this->manager->download_preset($this->id);
|
||||
|
||||
|
||||
@@ -40,8 +40,6 @@ class import extends base {
|
||||
* Imports the xmlfile into DB
|
||||
*/
|
||||
public function execute(): void {
|
||||
confirm_sesskey();
|
||||
|
||||
$url = new \moodle_url('/admin/tool/admin_presets/index.php', ['action' => 'import', 'mode' => 'execute']);
|
||||
$this->moodleform = new import_form($url);
|
||||
|
||||
|
||||
@@ -38,8 +38,6 @@ class load extends base {
|
||||
public function execute(): void {
|
||||
global $OUTPUT;
|
||||
|
||||
confirm_sesskey();
|
||||
|
||||
$url = new \moodle_url('/admin/tool/admin_presets/index.php', ['action' => 'load', 'mode' => 'execute']);
|
||||
$this->moodleform = new load_form($url);
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ class rollback extends base {
|
||||
public function execute(): void {
|
||||
global $OUTPUT;
|
||||
|
||||
confirm_sesskey();
|
||||
require_sesskey();
|
||||
|
||||
list($presetapp, $rollback, $failures) = $this->manager->revert_preset($this->id);
|
||||
|
||||
|
||||
@@ -97,28 +97,28 @@ if ($onlycli === false) {
|
||||
switch ($action) {
|
||||
|
||||
case 'enable':
|
||||
confirm_sesskey();
|
||||
require_sesskey();
|
||||
|
||||
$model->enable();
|
||||
redirect($returnurl);
|
||||
break;
|
||||
|
||||
case 'disable':
|
||||
confirm_sesskey();
|
||||
require_sesskey();
|
||||
|
||||
$model->update(0, false, false);
|
||||
redirect($returnurl);
|
||||
break;
|
||||
|
||||
case 'delete':
|
||||
confirm_sesskey();
|
||||
require_sesskey();
|
||||
|
||||
$model->delete();
|
||||
redirect($returnurl);
|
||||
break;
|
||||
|
||||
case 'edit':
|
||||
confirm_sesskey();
|
||||
require_sesskey();
|
||||
|
||||
$invalidcurrenttimesplitting = $model->invalid_timesplitting_selected();
|
||||
$potentialtimesplittings = $model->get_potential_timesplittings();
|
||||
@@ -183,7 +183,7 @@ switch ($action) {
|
||||
break;
|
||||
|
||||
case 'evaluate':
|
||||
confirm_sesskey();
|
||||
require_sesskey();
|
||||
|
||||
if ($model->is_static()) {
|
||||
throw new moodle_exception('errornostaticevaluate', 'tool_analytics');
|
||||
@@ -226,7 +226,7 @@ switch ($action) {
|
||||
break;
|
||||
|
||||
case 'scheduledanalysis':
|
||||
confirm_sesskey();
|
||||
require_sesskey();
|
||||
|
||||
if ($onlycli) {
|
||||
throw new moodle_exception('erroronlycli', 'tool_analytics');
|
||||
@@ -292,7 +292,7 @@ switch ($action) {
|
||||
break;
|
||||
|
||||
case 'clear':
|
||||
confirm_sesskey();
|
||||
require_sesskey();
|
||||
|
||||
$model->clear();
|
||||
redirect($returnurl);
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
@tool_behat
|
||||
Feature: Confirm that we can open multiple browser tabs
|
||||
In order to use multiple browser tabs
|
||||
As a test writer
|
||||
I need the relevant Behat steps to work
|
||||
|
||||
@javascript @_switch_window
|
||||
Scenario: Open multiple browser tabs
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname |
|
||||
| Course 1 | C1 |
|
||||
| Course 2 | C2 |
|
||||
| Course 3 | C3 |
|
||||
And I am on the "C1" "Course" page logged in as "admin"
|
||||
|
||||
# Open a new tab on the same page.
|
||||
When I open a tab named "CourseViewer1" on the current page
|
||||
And I should see "Course 1" in the "h1" "css_element"
|
||||
And I am on the "C2" "Course" page
|
||||
|
||||
# Open new tab for specified page with identifier.
|
||||
And I open a tab named "CourseViewer2" on the "C3" "Course" page
|
||||
|
||||
# And for a specified page without identifier.
|
||||
And I open a tab named "CourseViewer4" on the "My courses" page
|
||||
|
||||
# Switch between all the tabs and confirm their different contents.
|
||||
Then I should see "No courses"
|
||||
And I switch to "CourseViewer2" tab
|
||||
And "Course 3" "heading" should exist
|
||||
And I switch to "CourseViewer1" tab
|
||||
And "Course 2" "heading" should exist
|
||||
And I switch to the main tab
|
||||
And "Course 1" "heading" should exist
|
||||
@@ -155,9 +155,9 @@ class data_request extends persistent {
|
||||
case api::DATAREQUEST_STATUS_EXPIRED:
|
||||
$result = true;
|
||||
break;
|
||||
// Complete requests are expired if the expiry time has elapsed.
|
||||
// Complete requests are expired if the expiry time is a positive value, and has elapsed.
|
||||
case api::DATAREQUEST_STATUS_DOWNLOAD_READY:
|
||||
$expiryseconds = get_config('tool_dataprivacy', 'privacyrequestexpiry');
|
||||
$expiryseconds = (int) get_config('tool_dataprivacy', 'privacyrequestexpiry');
|
||||
if ($expiryseconds > 0 && time() >= ($request->get('timemodified') + $expiryseconds)) {
|
||||
$result = true;
|
||||
}
|
||||
@@ -178,7 +178,12 @@ class data_request extends persistent {
|
||||
public static function get_expired_requests($userid = 0) {
|
||||
global $DB;
|
||||
|
||||
$expiryseconds = get_config('tool_dataprivacy', 'privacyrequestexpiry');
|
||||
// Complete requests are expired if the expiry time is a positive value, and has elapsed.
|
||||
$expiryseconds = (int) get_config('tool_dataprivacy', 'privacyrequestexpiry');
|
||||
if ($expiryseconds <= 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$expirytime = strtotime("-{$expiryseconds} second");
|
||||
$table = self::TABLE;
|
||||
$sqlwhere = 'type = :export_type AND status = :completestatus AND timemodified <= :expirytime';
|
||||
|
||||
@@ -54,6 +54,7 @@ Feature: Data delete from the privacy API
|
||||
And I should see "Awaiting approval" in the "Victim User 1" "table_row"
|
||||
And I open the action menu in "Victim User 1" "table_row"
|
||||
And I follow "Approve request"
|
||||
And I wait until "Approve request" "button" exists
|
||||
And I press "Approve request"
|
||||
And I should see "Approved" in the "Victim User 1" "table_row"
|
||||
And I run all adhoc tasks
|
||||
@@ -80,6 +81,7 @@ Feature: Data delete from the privacy API
|
||||
And I navigate to "Users > Privacy and policies > Data requests" in site administration
|
||||
And I open the action menu in "Victim User 1" "table_row"
|
||||
And I follow "Approve request"
|
||||
And I wait until "Approve request" "button" exists
|
||||
And I press "Approve request"
|
||||
|
||||
And I log out
|
||||
@@ -115,6 +117,7 @@ Feature: Data delete from the privacy API
|
||||
And I navigate to "Users > Privacy and policies > Data requests" in site administration
|
||||
And I open the action menu in "Victim User 1" "table_row"
|
||||
And I follow "Approve request"
|
||||
And I wait until "Approve request" "button" exists
|
||||
And I press "Approve request"
|
||||
|
||||
And I log out
|
||||
|
||||
@@ -48,6 +48,7 @@ Feature: Data export from the privacy API
|
||||
And I should see "Awaiting approval" in the "Victim User 1" "table_row"
|
||||
And I open the action menu in "Victim User 1" "table_row"
|
||||
And I follow "Approve request"
|
||||
And I wait until "Approve request" "button" exists
|
||||
And I press "Approve request"
|
||||
And I should see "Approved" in the "Victim User 1" "table_row"
|
||||
And I run all adhoc tasks
|
||||
@@ -115,6 +116,7 @@ Feature: Data export from the privacy API
|
||||
And I navigate to "Users > Privacy and policies > Data requests" in site administration
|
||||
And I open the action menu in "Victim User 1" "table_row"
|
||||
And I follow "Approve request"
|
||||
And I wait until "Approve request" "button" exists
|
||||
And I press "Approve request"
|
||||
|
||||
And I log out
|
||||
|
||||
@@ -26,10 +26,11 @@ require_once('data_privacy_testcase.php');
|
||||
* Expired data requests tests.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @covers \tool_dataprivacy\data_request
|
||||
* @copyright 2018 Michael Hawkins
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class expired_data_requests_test extends data_privacy_testcase {
|
||||
final class expired_data_requests_test extends data_privacy_testcase {
|
||||
|
||||
/**
|
||||
* Test tearDown.
|
||||
@@ -107,6 +108,52 @@ class expired_data_requests_test extends data_privacy_testcase {
|
||||
$this->assertEquals(0, $DB->count_records('files', $fileconditions));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that data requests are not expired when expiration is disabled (set to zero)
|
||||
*/
|
||||
public function test_data_request_expiry_never(): void {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
\core_privacy\local\request\writer::setup_real_writer_instance();
|
||||
|
||||
// Disable request expiry.
|
||||
set_config('privacyrequestexpiry', 0, 'tool_dataprivacy');
|
||||
|
||||
// Create and approve data request.
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$usercontext = \context_user::instance($user->id);
|
||||
|
||||
$this->setUser($user->id);
|
||||
$datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT);
|
||||
$requestid = $datarequest->get('id');
|
||||
|
||||
$this->setAdminUser();
|
||||
api::approve_data_request($requestid);
|
||||
|
||||
ob_start();
|
||||
$this->runAdhocTasks('\tool_dataprivacy\task\process_data_request_task');
|
||||
ob_end_clean();
|
||||
|
||||
// Run expiry deletion - should not affect test export.
|
||||
$expiredrequests = data_request::get_expired_requests();
|
||||
$this->assertEmpty($expiredrequests);
|
||||
data_request::expire($expiredrequests);
|
||||
|
||||
// Confirm approved and exported.
|
||||
$request = new data_request($requestid);
|
||||
$this->assertEquals(api::DATAREQUEST_STATUS_DOWNLOAD_READY, $request->get('status'));
|
||||
$fileconditions = [
|
||||
'userid' => $user->id,
|
||||
'component' => 'tool_dataprivacy',
|
||||
'filearea' => 'export',
|
||||
'itemid' => $requestid,
|
||||
'contextid' => $usercontext->id,
|
||||
];
|
||||
$this->assertEquals(2, $DB->count_records('files', $fileconditions));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for \tool_dataprivacy\data_request::is_expired()
|
||||
* Tests for the expected request status to protect from false positive/negative,
|
||||
|
||||
@@ -120,8 +120,7 @@ Feature: Add customised file types
|
||||
| Custom description | Froggy file |
|
||||
And I press "Save changes"
|
||||
# Create a resource activity and add it to a course
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
When I add a "File" to section "1"
|
||||
When I add a resource activity to course "Course 1" section "1"
|
||||
And I set the following fields to these values:
|
||||
| Name | An example of customised file type |
|
||||
| Description | File description |
|
||||
|
||||
@@ -114,7 +114,7 @@ if ($mode == DELETION_OF_SELECTED_LANG and (!empty($uninstalllang) or !empty($co
|
||||
}
|
||||
}
|
||||
|
||||
if ($mode == UPDATE_ALL_LANG) {
|
||||
if ($mode == UPDATE_ALL_LANG && confirm_sesskey()) {
|
||||
core_php_time_limit::raise();
|
||||
$controller->update_all_installed_languages();
|
||||
}
|
||||
|
||||
@@ -87,6 +87,7 @@
|
||||
<div>
|
||||
<form id="updateform" action="{{updateurl}}" method="post">
|
||||
<fieldset>
|
||||
<input type="hidden" name="sesskey" value="{{sesskey}}">
|
||||
<input type="submit" value="{{#str}}updatelangs, tool_langimport{{/str}}" class="btn btn-secondary">
|
||||
{{#hasmanyinstalledlanguages}}
|
||||
<p class="mt-2 small">{{#str}} updatelangsnote, tool_langimport, {"taskname": {{#quote}}{{updatelangstaskname}}{{/quote}} }{{/str}}</p>
|
||||
|
||||
@@ -14,6 +14,6 @@ Feature: In a report, admin can see logstore visibility changes
|
||||
And I click on "Enable" "icon" in the "External database log" "table_row"
|
||||
And I navigate to "Reports > Config changes" in site administration
|
||||
Then the following should exist in the "reportbuilder-table" table:
|
||||
| User | Plugin | Setting | New value | Original value |
|
||||
| First name | Plugin | Setting | New value | Original value |
|
||||
| Admin User | logstore_standard | tool_logstore_visibility | 0 | 1 |
|
||||
| Admin User | logstore_database | tool_logstore_visibility | 1 | 0 |
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -71,14 +71,12 @@ define(['jquery'], function($) {
|
||||
return null;
|
||||
};
|
||||
|
||||
// eslint-disable-line valid-jsdoc
|
||||
/**
|
||||
* Return the type of the module.
|
||||
*
|
||||
* @return {String}
|
||||
* @method getType
|
||||
*/
|
||||
// eslint-enable-line valid-jsdoc
|
||||
Rule.prototype.getType = function() {
|
||||
throw new Error('Not implemented');
|
||||
};
|
||||
|
||||
@@ -419,13 +419,13 @@ class api {
|
||||
public static function get_qrlogin_key(stdClass $mobilesettings) {
|
||||
global $USER;
|
||||
// Delete previous keys.
|
||||
delete_user_key('tool_mobile', $USER->id);
|
||||
delete_user_key('tool_mobile/qrlogin', $USER->id);
|
||||
|
||||
// Create a new key.
|
||||
$iprestriction = !empty($mobilesettings->qrsameipcheck) ? getremoteaddr(null) : null;
|
||||
$qrkeyttl = !empty($mobilesettings->qrkeyttl) ? $mobilesettings->qrkeyttl : self::LOGIN_QR_KEY_TTL;
|
||||
$validuntil = time() + $qrkeyttl;
|
||||
return create_user_key('tool_mobile', $USER->id, null, $iprestriction, $validuntil);
|
||||
return create_user_key('tool_mobile/qrlogin', $USER->id, null, $iprestriction, $validuntil);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<?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_mobile;
|
||||
|
||||
use core\session\utility\cookie_helper;
|
||||
use core\event\user_loggedin;
|
||||
|
||||
/**
|
||||
* Event handler for tool_mobile.
|
||||
*
|
||||
* @package tool_mobile
|
||||
* @copyright 2024 Juan Leyva
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class event_handler {
|
||||
|
||||
/**
|
||||
* Allows the plugin to augment Set-Cookie headers when the user_loggedin event is fired as part of complete_user_login() calls.
|
||||
*
|
||||
* @param user_loggedin $event the event
|
||||
* @return void
|
||||
*/
|
||||
public static function handle_user_loggedin(user_loggedin $event): void {
|
||||
global $CFG;
|
||||
|
||||
// Set Partitioned and Secure attributes to the MoodleSession cookie if the user is using the Moodle app.
|
||||
if (\core_useragent::is_moodle_app()) {
|
||||
cookie_helper::add_attributes_to_cookie_response_header('MoodleSession'.$CFG->sessioncookie, ['Secure', 'Partitioned']);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -649,8 +649,8 @@ class external extends external_api {
|
||||
api::check_autologin_prerequisites($params['userid']); // Checks https, avoid site admins using this...
|
||||
|
||||
// Validate and delete the key.
|
||||
$key = validate_user_key($params['qrloginkey'], 'tool_mobile', null);
|
||||
delete_user_key('tool_mobile', $params['userid']);
|
||||
$key = validate_user_key($params['qrloginkey'], 'tool_mobile/qrlogin', null);
|
||||
delete_user_key('tool_mobile/qrlogin', $params['userid']);
|
||||
|
||||
// Double check key belong to user.
|
||||
if ($key->userid != $params['userid']) {
|
||||
|
||||
@@ -66,7 +66,7 @@ class provider implements
|
||||
FROM {user_private_key} k
|
||||
JOIN {user} u ON k.userid = u.id
|
||||
JOIN {context} ctx ON ctx.instanceid = u.id AND ctx.contextlevel = :contextlevel
|
||||
WHERE k.userid = :userid AND k.script = 'tool_mobile'";
|
||||
WHERE k.userid = :userid AND (k.script = 'tool_mobile' OR k.script = 'tool_mobile/qrlogin')";
|
||||
$params = ['userid' => $userid, 'contextlevel' => CONTEXT_USER];
|
||||
$contextlist = new contextlist();
|
||||
$contextlist->add_from_sql($sql, $params);
|
||||
@@ -88,6 +88,7 @@ class provider implements
|
||||
|
||||
// Add users based on userkey.
|
||||
\core_userkey\privacy\provider::get_user_contexts_with_script($userlist, $context, 'tool_mobile');
|
||||
\core_userkey\privacy\provider::get_user_contexts_with_script($userlist, $context, 'tool_mobile/qrlogin');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,6 +109,7 @@ class provider implements
|
||||
}
|
||||
// Export associated userkeys.
|
||||
\core_userkey\privacy\provider::export_userkeys($context, [], 'tool_mobile');
|
||||
\core_userkey\privacy\provider::export_userkeys($context, [], 'tool_mobile/qrlogin');
|
||||
}
|
||||
/**
|
||||
* Export all user preferences for the plugin.
|
||||
@@ -138,6 +140,7 @@ class provider implements
|
||||
$userid = $context->instanceid;
|
||||
// Delete all the userkeys.
|
||||
\core_userkey\privacy\provider::delete_userkeys('tool_mobile', $userid);
|
||||
\core_userkey\privacy\provider::delete_userkeys('tool_mobile/qrlogin', $userid);
|
||||
}
|
||||
/**
|
||||
* Delete all user data for the specified user, in the specified contexts.
|
||||
@@ -158,6 +161,7 @@ class provider implements
|
||||
$userid = $context->instanceid;
|
||||
// Delete all the userkeys.
|
||||
\core_userkey\privacy\provider::delete_userkeys('tool_mobile', $userid);
|
||||
\core_userkey\privacy\provider::delete_userkeys('tool_mobile/qrlogin', $userid);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,5 +182,6 @@ class provider implements
|
||||
|
||||
// Delete all the userkeys.
|
||||
\core_userkey\privacy\provider::delete_userkeys('tool_mobile', $userid);
|
||||
\core_userkey\privacy\provider::delete_userkeys('tool_mobile/qrlogin', $userid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* tool_mobile plugin event handler definition.
|
||||
*
|
||||
* @package tool_mobile
|
||||
* @category event
|
||||
* @copyright 2024 Juan Leyva
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$observers = [
|
||||
[
|
||||
'eventname' => '\core\event\user_loggedin',
|
||||
'callback' => '\tool_mobile\event_handler::handle_user_loggedin',
|
||||
],
|
||||
];
|
||||
@@ -69,6 +69,14 @@ if (empty($service)) {
|
||||
throw new moodle_exception('servicenotavailable', 'webservice');
|
||||
}
|
||||
|
||||
// If the user is using the inapp (embedded) browser, we need to set the Secure and Partitioned attributes to the session cookie.
|
||||
if (\core_useragent::is_moodle_app()) {
|
||||
\core\session\utility\cookie_helper::add_attributes_to_cookie_response_header(
|
||||
"MoodleSession{$CFG->sessioncookie}",
|
||||
['Secure', 'Partitioned'],
|
||||
);
|
||||
}
|
||||
|
||||
require_login(0, false);
|
||||
|
||||
// Require an active user: not guest, not suspended.
|
||||
|
||||
@@ -265,3 +265,16 @@ function tool_mobile_pre_processor_message_send($procname, $data) {
|
||||
$data->fullmessagehtml .= html_writer::tag('p', get_string('readingthisemailgettheapp', 'tool_mobile', $url->out()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback to add headers before the HTTP headers are sent.
|
||||
*
|
||||
*/
|
||||
function tool_mobile_before_http_headers() {
|
||||
global $CFG;
|
||||
|
||||
// Set Partitioned and Secure attributes to the MoodleSession cookie if the user is using the Moodle app.
|
||||
if (\core_useragent::is_moodle_app()) {
|
||||
\core\session\utility\cookie_helper::add_attributes_to_cookie_response_header('MoodleSession'.$CFG->sessioncookie, ['Secure', 'Partitioned']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +87,8 @@ class provider_test extends \core_privacy\tests\provider_testcase {
|
||||
$context1 = \context_user::instance($user1->id);
|
||||
$context2 = \context_user::instance($user2->id);
|
||||
$key1 = get_user_key('tool_mobile', $user1->id);
|
||||
$key2 = get_user_key('tool_mobile', $user2->id);
|
||||
$key2 = get_user_key('tool_mobile/qrlogin', $user1->id);
|
||||
$key3 = get_user_key('tool_mobile', $user2->id);
|
||||
|
||||
// Ensure only user1 is found in context1.
|
||||
$userlist = new \core_privacy\local\request\userlist($context1, $component);
|
||||
@@ -174,12 +175,15 @@ class provider_test extends \core_privacy\tests\provider_testcase {
|
||||
$context1 = \context_user::instance($user1->id);
|
||||
$context2 = \context_user::instance($user2->id);
|
||||
$keyvalue1 = get_user_key('tool_mobile', $user1->id);
|
||||
$keyvalue2 = get_user_key('tool_mobile', $user2->id);
|
||||
$keyvalue2 = get_user_key('tool_mobile/qrlogin', $user1->id);
|
||||
$keyvalue3 = get_user_key('tool_mobile', $user2->id);
|
||||
$key1 = $DB->get_record('user_private_key', ['value' => $keyvalue1]);
|
||||
|
||||
// Before deletion, we should have 2 user_private_keys.
|
||||
// Before deletion, we should have 2 user_private_keys for tool_mobile and one for tool_mobile/qrlogin.
|
||||
$count = $DB->count_records('user_private_key', ['script' => 'tool_mobile']);
|
||||
$this->assertEquals(2, $count);
|
||||
$count = $DB->count_records('user_private_key', ['script' => 'tool_mobile/qrlogin']);
|
||||
$this->assertEquals(1, $count);
|
||||
|
||||
// Ensure deleting wrong user in the user context does nothing.
|
||||
$approveduserids = [$user2->id];
|
||||
@@ -197,6 +201,8 @@ class provider_test extends \core_privacy\tests\provider_testcase {
|
||||
// Ensure only user1's data is deleted, user2's remains.
|
||||
$count = $DB->count_records('user_private_key', ['script' => 'tool_mobile']);
|
||||
$this->assertEquals(1, $count);
|
||||
$count = $DB->count_records('user_private_key', ['script' => 'tool_mobile/qrlogin']);
|
||||
$this->assertEquals(0, $count);
|
||||
|
||||
$params = ['script' => $component];
|
||||
$userid = $DB->get_field_select('user_private_key', 'userid', 'script = :script', $params);
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
$plugin->version = 2022112800; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->version = 2022112801; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2022111800; // Requires this Moodle version.
|
||||
$plugin->component = 'tool_mobile'; // Full name of the plugin (used for diagnostics).
|
||||
$plugin->dependencies = array(
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -121,7 +121,6 @@ define(['tool_moodlenet/validator',
|
||||
});
|
||||
// Trigger the transition between 'pages'.
|
||||
carousel.carousel(2);
|
||||
// eslint-disable-next-line max-len
|
||||
modal.setFooter(Templates.render('tool_moodlenet/chooser_footer_close_mnet', {}));
|
||||
};
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ if ($cancel) {
|
||||
}
|
||||
redirect($url);
|
||||
} else if ($continue) {
|
||||
confirm_sesskey();
|
||||
require_sesskey();
|
||||
|
||||
// Handle backups.
|
||||
if (strtolower($importinfo->get_resource()->get_extension()) == 'mbz') {
|
||||
|
||||
@@ -81,7 +81,7 @@ switch ($config->type) {
|
||||
}
|
||||
|
||||
if ($import && $module) {
|
||||
confirm_sesskey();
|
||||
require_sesskey();
|
||||
|
||||
$handlerinfo = $handlerregistry->get_resource_handler_for_mod_and_strategy($importinfo->get_resource(), $module, $strategy);
|
||||
if (is_null($handlerinfo)) {
|
||||
|
||||
@@ -50,12 +50,10 @@ list($options, $unrecognized) = cli_get_params(
|
||||
)
|
||||
);
|
||||
|
||||
if (file_exists(__DIR__.'/../../../../vendor/phpunit/phpunit/composer.json')) {
|
||||
// Composer packages present.
|
||||
require_once(__DIR__.'/../../../../vendor/autoload.php');
|
||||
|
||||
} else {
|
||||
// Note: installation via PEAR is not supported any more.
|
||||
// Basic check to see if phpunit is installed.
|
||||
if (!file_exists(__DIR__.'/../../../../vendor/phpunit/phpunit/composer.json') ||
|
||||
!file_exists(__DIR__.'/../../../../vendor/bin/phpunit') ||
|
||||
!file_exists(__DIR__.'/../../../../vendor/autoload.php')) {
|
||||
phpunit_bootstrap_error(PHPUNIT_EXITCODE_PHPUNITMISSING);
|
||||
}
|
||||
|
||||
@@ -74,12 +72,13 @@ if ($options['run']) {
|
||||
}
|
||||
}
|
||||
$_SERVER['argv'] = array_values($_SERVER['argv']);
|
||||
PHPUnit\TextUI\Command::main();
|
||||
require(__DIR__ . '/../../../../vendor/bin/phpunit');
|
||||
exit(0);
|
||||
}
|
||||
|
||||
define('PHPUNIT_UTIL', true);
|
||||
|
||||
require(__DIR__.'/../../../../vendor/autoload.php');
|
||||
require(__DIR__ . '/../../../../lib/phpunit/bootstrap.php');
|
||||
|
||||
// from now on this is a regular moodle CLI_SCRIPT
|
||||
|
||||
@@ -170,6 +170,7 @@ Feature: Viewing acceptances reports and accepting on behalf of other users
|
||||
And I navigate to "Users > Privacy and policies > Manage policies" in site administration
|
||||
And I click on "Actions" "link_or_button" in the "This privacy policy" "table_row"
|
||||
And I click on "Set status to \"Active\"" "link" in the "This privacy policy" "table_row"
|
||||
And I wait until "Activate" "button" exists
|
||||
And I press "Activate"
|
||||
And I log out
|
||||
When I log in as "manager"
|
||||
@@ -260,6 +261,7 @@ Feature: Viewing acceptances reports and accepting on behalf of other users
|
||||
And I navigate to "Users > Privacy and policies > Manage policies" in site administration
|
||||
And I click on "Actions" "link_or_button" in the "This privacy policy" "table_row"
|
||||
And I click on "Set status to \"Active\"" "link" in the "This privacy policy" "table_row"
|
||||
And I wait until "Activate" "button" exists
|
||||
And I press "Activate"
|
||||
And I log out
|
||||
When I log in as "manager"
|
||||
|
||||
@@ -87,7 +87,8 @@ require_sesskey();
|
||||
|
||||
// Prepare to handle output via mtrace.
|
||||
echo html_writer::start_tag('pre');
|
||||
require('lib.php');
|
||||
|
||||
require_once("{$CFG->dirroot}/{$CFG->admin}/tool/task/lib.php");
|
||||
$CFG->mtrace_wrapper = 'tool_task_mtrace_wrapper';
|
||||
|
||||
// Run the specified task (this will output an error if it doesn't exist).
|
||||
|
||||
@@ -62,8 +62,8 @@ Feature: Manage scheduled tasks
|
||||
And I press "Save changes"
|
||||
Then I should see "Changes saved"
|
||||
And the following should not exist in the "admintable" table:
|
||||
| Name | Component | Minute | Hour | Day | Day of week | Month |
|
||||
| Log table cleanup | Standard log | */5 | 1 | 2 | 4 | 3 |
|
||||
| Name | Minute | Hour | Day | Day of week | Month |
|
||||
| Log table cleanup | */5 | 1 | 2 | 4 | 3 |
|
||||
And I should see "Log table cleanup" in the "tr.table-primary" "css_element"
|
||||
|
||||
Scenario: Disabled plugin's tasks are labelled as disabled too
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -36,6 +36,7 @@ import {dispatchEvent} from 'core/event_dispatcher';
|
||||
import {eventTypes} from './events';
|
||||
import {get_string as getString} from 'core/str';
|
||||
import {prefetchStrings} from 'core/prefetch';
|
||||
import PendingPromise from 'core/pending';
|
||||
|
||||
/**
|
||||
* The minimum spacing for tour step to display.
|
||||
@@ -485,14 +486,22 @@ const Tour = class {
|
||||
return this.endTour();
|
||||
}
|
||||
|
||||
const pendingPromise = new PendingPromise(`tool_usertours/tour:_gotoStep-${stepConfig.stepNumber}`);
|
||||
|
||||
if (typeof stepConfig.delay !== 'undefined' && stepConfig.delay && !stepConfig.delayed) {
|
||||
stepConfig.delayed = true;
|
||||
window.setTimeout(this._gotoStep.bind(this), stepConfig.delay, stepConfig, direction);
|
||||
window.setTimeout(function(stepConfig, direction) {
|
||||
this._gotoStep(stepConfig, direction);
|
||||
pendingPromise.resolve();
|
||||
}, stepConfig.delay, stepConfig, direction);
|
||||
|
||||
return this;
|
||||
} else if (!stepConfig.orphan && !this.isStepActuallyVisible(stepConfig)) {
|
||||
let fn = direction == -1 ? 'getPreviousStepNumber' : 'getNextStepNumber';
|
||||
return this.gotoStep(this[fn](stepConfig.stepNumber), direction);
|
||||
const fn = direction == -1 ? 'getPreviousStepNumber' : 'getNextStepNumber';
|
||||
this.gotoStep(this[fn](stepConfig.stepNumber), direction);
|
||||
|
||||
pendingPromise.resolve();
|
||||
return this;
|
||||
}
|
||||
|
||||
this.hide();
|
||||
@@ -503,6 +512,7 @@ const Tour = class {
|
||||
this.dispatchEvent(eventTypes.stepRendered, {stepConfig});
|
||||
}
|
||||
|
||||
pendingPromise.resolve();
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -820,12 +830,14 @@ const Tour = class {
|
||||
left: 0,
|
||||
});
|
||||
|
||||
const pendingPromise = new PendingPromise(`tool_usertours/tour:addStepToPage-${stepConfig.stepNumber}`);
|
||||
animationTarget
|
||||
.animate({
|
||||
scrollTop: this.calculateScrollTop(stepConfig),
|
||||
}).promise().then(function() {
|
||||
this.positionStep(stepConfig);
|
||||
this.revealStep(stepConfig);
|
||||
pendingPromise.resolve();
|
||||
return;
|
||||
}.bind(this))
|
||||
.catch(function() {
|
||||
@@ -897,6 +909,7 @@ const Tour = class {
|
||||
*/
|
||||
revealStep(stepConfig) {
|
||||
// Fade the step in.
|
||||
const pendingPromise = new PendingPromise(`tool_usertours/tour:revealStep-${stepConfig.stepNumber}`);
|
||||
this.currentStepNode.fadeIn('', $.proxy(function() {
|
||||
// Announce via ARIA.
|
||||
this.announceStep(stepConfig);
|
||||
@@ -910,6 +923,7 @@ const Tour = class {
|
||||
if (this.currentStepNode) {
|
||||
this.currentStepNode.focus();
|
||||
}
|
||||
pendingPromise.resolve();
|
||||
}, this), 100);
|
||||
|
||||
}, this));
|
||||
@@ -1157,6 +1171,7 @@ const Tour = class {
|
||||
return this;
|
||||
}
|
||||
|
||||
const pendingPromise = new PendingPromise('tool_usertours/tour:hide');
|
||||
if (this.currentStepNode && this.currentStepNode.length) {
|
||||
this.currentStepNode.hide();
|
||||
if (this.currentStepPopper) {
|
||||
@@ -1191,17 +1206,22 @@ const Tour = class {
|
||||
this.currentStepConfig = null;
|
||||
}
|
||||
|
||||
let fadeTime = 0;
|
||||
if (transition) {
|
||||
fadeTime = 400;
|
||||
}
|
||||
|
||||
// Remove the backdrop features.
|
||||
$('[data-flexitour="step-background"]').remove();
|
||||
$('[data-flexitour="step-backdrop"]').removeAttr('data-flexitour');
|
||||
$('[data-flexitour="backdrop"]').fadeOut(fadeTime, function() {
|
||||
$(this).remove();
|
||||
});
|
||||
|
||||
const backdrop = $('[data-flexitour="backdrop"]');
|
||||
if (backdrop.length) {
|
||||
if (transition) {
|
||||
const backdropRemovalPromise = new PendingPromise('tool_usertours/tour:hide:backdrop');
|
||||
backdrop.fadeOut(400, function() {
|
||||
$(this).remove();
|
||||
backdropRemovalPromise.resolve();
|
||||
});
|
||||
} else {
|
||||
backdrop.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// Remove aria-describedby and tabindex attributes.
|
||||
if (this.currentStepNode && this.currentStepNode.length) {
|
||||
@@ -1222,6 +1242,8 @@ const Tour = class {
|
||||
|
||||
this.currentStepNode = null;
|
||||
this.currentStepPopper = null;
|
||||
|
||||
pendingPromise.resolve();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,11 @@ class new_table_from_mysql extends XMLDBAction {
|
||||
function init() {
|
||||
parent::init();
|
||||
|
||||
global $DB;
|
||||
if ($DB->get_dbfamily() !== 'mysql') {
|
||||
throw new moodle_exception('DB family not supported');
|
||||
}
|
||||
|
||||
// Set own custom attributes
|
||||
|
||||
// Get needed strings
|
||||
@@ -55,8 +60,6 @@ class new_table_from_mysql extends XMLDBAction {
|
||||
function invoke() {
|
||||
parent::invoke();
|
||||
|
||||
$result = true;
|
||||
|
||||
// Set own core attributes
|
||||
$this->does_generate = ACTION_GENERATE_HTML;
|
||||
|
||||
@@ -125,11 +128,15 @@ class new_table_from_mysql extends XMLDBAction {
|
||||
// If table, retrofit information and, if everything works,
|
||||
// go to the table edit action
|
||||
} else {
|
||||
// Get some params (table is mandatory here)
|
||||
$tableparam = required_param('table', PARAM_CLEAN);
|
||||
$afterparam = required_param('after', PARAM_CLEAN);
|
||||
// Get some params (table is mandatory here).
|
||||
$tableparam = required_param('table', PARAM_ALPHANUMEXT);
|
||||
$afterparam = required_param('after', PARAM_ALPHANUMEXT);
|
||||
|
||||
// Create one new xmldb_table
|
||||
if (empty($tableparam) || empty($afterparam)) {
|
||||
throw new moodle_exception('Invalid param value detected.');
|
||||
}
|
||||
|
||||
// Create one new xmldb_table.
|
||||
$table = new xmldb_table(strtolower(trim($tableparam)));
|
||||
$table->setComment($table->getName() . ' table retrofitted from MySQL');
|
||||
// Get fields info from ADODb
|
||||
@@ -147,7 +154,6 @@ class new_table_from_mysql extends XMLDBAction {
|
||||
// Get PK, UK and indexes info from ADODb
|
||||
$dbindexes = $DB->get_indexes($tableparam);
|
||||
if ($dbindexes) {
|
||||
$lastkey = NULL; //To temp store the last key processed
|
||||
foreach ($dbindexes as $indexname => $dbindex) {
|
||||
// Add the indexname to the array
|
||||
$dbindex['name'] = $indexname;
|
||||
@@ -156,9 +162,6 @@ class new_table_from_mysql extends XMLDBAction {
|
||||
$key = new xmldb_key(strtolower($dbindex['name']));
|
||||
// Set key with info retrofitted
|
||||
$key->setFromADOKey($dbindex);
|
||||
// Set default comment to PKs
|
||||
if ($key->getType() == XMLDB_KEY_PRIMARY) {
|
||||
}
|
||||
// Add key to the table
|
||||
$table->addKey($key);
|
||||
|
||||
@@ -172,18 +175,17 @@ class new_table_from_mysql extends XMLDBAction {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Finally, add the whole retroffited table to the structure
|
||||
// in the place specified
|
||||
// Finally, add the whole retrofitted table to the structure in the place specified.
|
||||
$structure->addTable($table, $afterparam);
|
||||
}
|
||||
|
||||
// Launch postaction if exists (leave this here!)
|
||||
if ($this->getPostAction() && $result) {
|
||||
if ($this->getPostAction()) {
|
||||
return $this->launch($this->getPostAction());
|
||||
}
|
||||
|
||||
// Return ok if arrived here
|
||||
return $result;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -114,6 +114,7 @@ Feature: Manage analytics models
|
||||
And I should see "Read actions amount"
|
||||
And I click on "Select Student 6 for bulk action" "checkbox" in the "Student 6" "table_row"
|
||||
And I click on "Accept" "button"
|
||||
And I wait until "Confirm" "button" exists
|
||||
And I click on "Confirm" "button" in the "Accept" "dialogue"
|
||||
And I click on "View prediction details" "icon" in the "Student 5" "table_row"
|
||||
And I click on "Select Student 5 for bulk action" "checkbox" in the "Student 5" "table_row"
|
||||
|
||||
+9
-11
@@ -14,31 +14,29 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Unit tests for activities completed by classification.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2017 David Monllaó {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace core_analytics;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
use advanced_testcase;
|
||||
use ReflectionClass;
|
||||
use ReflectionMethod;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Unit tests for activities completed by classification.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @covers \core_analytics\local\indicator\community_of_inquiry_activity
|
||||
* @copyright 2017 David Monllaó {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class community_of_inquiry_activities_completed_by_testcase extends advanced_testcase {
|
||||
final class community_of_inquiry_activities_completed_by_test extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* availability_levels
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function availability_levels() {
|
||||
public static function availability_levels(): array {
|
||||
return array(
|
||||
'activity' => array('activity'),
|
||||
'section' => array('section'),
|
||||
@@ -148,7 +146,7 @@ class community_of_inquiry_activities_completed_by_testcase extends advanced_tes
|
||||
$second = $startdate + WEEKSECS;
|
||||
$third = $startdate + (WEEKSECS * 2);
|
||||
$forth = $startdate + (WEEKSECS * 3);
|
||||
$this->assertCount(1, $method->invoke($indicator, $first, $first + WEEKSECS, $stu1));
|
||||
$this->assertCount(2, $method->invoke($indicator, $first, $first + WEEKSECS, $stu1));
|
||||
$this->assertCount(1, $method->invoke($indicator, $second, $second + WEEKSECS, $stu1));
|
||||
$this->assertCount(0, $method->invoke($indicator, $third, $third + WEEKSECS, $stu1));
|
||||
$this->assertCount(2, $method->invoke($indicator, $forth, $forth + WEEKSECS, $stu1));
|
||||
+1
-1
@@ -116,7 +116,7 @@ class auth_plugin_lti extends \auth_plugin_base {
|
||||
if (isloggedin()) {
|
||||
// If a different user is currently logged in, authenticate the linked user instead.
|
||||
global $USER;
|
||||
if ((int) $USER->id !== $user->id) {
|
||||
if ($USER->id !== $user->id) {
|
||||
complete_user_login($user);
|
||||
}
|
||||
// If the linked user is already logged in, skip the call to complete_user_login() because this affects deep linking
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<?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 auth_lti\local\ltiadvantage\event;
|
||||
|
||||
use auth_lti\local\ltiadvantage\utility\cookie_helper;
|
||||
use core\event\user_loggedin;
|
||||
|
||||
/**
|
||||
* Event handler for auth_lti.
|
||||
*
|
||||
* @package auth_lti
|
||||
* @copyright 2024 Jake Dallimore <jrhdallimore@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class event_handler {
|
||||
|
||||
/**
|
||||
* Allows the plugin to augment Set-Cookie headers when the user_loggedin event is fired as part of complete_user_login() calls.
|
||||
*
|
||||
* @param user_loggedin $event the event
|
||||
* @return void
|
||||
*/
|
||||
public static function handle_user_loggedin(user_loggedin $event): void {
|
||||
// The event data isn't important here. The intent of this listener is to ensure that the MoodleSession cookie is set up
|
||||
// properly during LTI launches + login. This means two things:
|
||||
// i) it's set with SameSite=None; Secure; where possible (since OIDC needs HTTPS this will almost always be possible).
|
||||
// ii) it set with the 'Partitioned' attribute, when required.
|
||||
// The former ensures cross-site cookies are sent for embedded launches. The latter is an opt-in flag needed to use Chrome's
|
||||
// partitioning mechanism, CHIPS.
|
||||
if (cookie_helper::cookies_supported()) {
|
||||
cookie_helper::setup_session_cookie();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
<?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 auth_lti\local\ltiadvantage\utility;
|
||||
use core\session\utility\cookie_helper as core_cookie_helper;
|
||||
|
||||
/**
|
||||
* Helper class providing utils dealing with cookies in LTI, particularly 3rd party cookies.
|
||||
*
|
||||
* @package auth_lti
|
||||
* @copyright 2024 Jake Dallimore <jrhdallimore@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
final class cookie_helper {
|
||||
|
||||
/** @var int Cookies are not supported. */
|
||||
public const COOKIE_METHOD_NOT_SUPPORTED = 0;
|
||||
|
||||
/** @var int Cookies are supported without explicit partitioning. */
|
||||
public const COOKIE_METHOD_NO_PARTITIONING = 1;
|
||||
|
||||
/** @var int Cookies are supported via explicit partitioning. */
|
||||
public const COOKIE_METHOD_EXPLICIT_PARTITIONING = 2;
|
||||
|
||||
/**
|
||||
* Check whether cookies can be used with the current user agent and, if so, via what method they are set.
|
||||
*
|
||||
* Currently, this tries 2 modes of setting a test cookie:
|
||||
* 1. Setting a SameSite=None, Secure cookie. This will work in any first party context, and in 3rd party contexts for
|
||||
* any browsers supporting automatic partitioning of 3rd party cookies (E.g. Firefox, Brave).
|
||||
* 2. If 1 fails, setting a cookie with the Chrome 'Partitioned' attribute included, opting that cookie into CHIPS. This will
|
||||
* work for Chrome.
|
||||
*
|
||||
* Upon completion of the cookie check, the check sets a SESSION flag indicating the method used to set the cookie, and upgrades
|
||||
* the session cookie ('MoodleSession') using the respective method. This ensure the session cookie will continue to be sent.
|
||||
*
|
||||
* Then, the following methods can be used by client code to query whether the UA supports cookies, and how:
|
||||
* @see self::cookies_supported() - whether it could be set at all.
|
||||
* @see self::get_cookies_supported_mode() - if a cookie could be set, what mode was used to set it.
|
||||
*
|
||||
* This permits client code to make sure it's setting its cookies appropriately (via the advertised method), and allows it to
|
||||
* present notices - such as in the case where a given UA is found to be lacking the requisite cookie support.
|
||||
* E.g.
|
||||
* cookie_helper::do_cookie_check($mypageurl);
|
||||
* if (!cookie_helper::cookies_supported()) {
|
||||
* // Print a notice stating that cookie support is required.
|
||||
* }
|
||||
* // Elsewhere in other client code...
|
||||
* if (cookie_helper::get_cookies_supported_mode() === cookie_helper::COOKIE_METHOD_EXPLICIT_PARTITIONING) {
|
||||
* // Set a cookie, making sure to use the helper to also opt-in to partitioning.
|
||||
* setcookie('myauthcookie', 'myauthcookievalue', ['samesite' => 'None', 'secure' => true]);
|
||||
* cookie_helper::add_partitioning_to_cookie('myauthcookie');
|
||||
* }
|
||||
*
|
||||
* @param \moodle_url $pageurl the URL of the page making the check, used to redirect back to after setting test cookies.
|
||||
* @return void
|
||||
*/
|
||||
public static function do_cookie_check(\moodle_url $pageurl): void {
|
||||
global $_COOKIE, $SESSION, $CFG;
|
||||
$cookiecheck1 = optional_param('cookiecheck1', null, PARAM_INT);
|
||||
$cookiecheck2 = optional_param('cookiecheck2', null, PARAM_INT);
|
||||
|
||||
if (empty($cookiecheck1)) {
|
||||
// Start the cookie check. Set two test cookies - one samesite none, and one partitioned - and redirect.
|
||||
// Set cookiecheck to show the check has started.
|
||||
self::set_test_cookie('cookiecheck1', self::COOKIE_METHOD_NO_PARTITIONING);
|
||||
self::set_test_cookie('cookiecheck2', self::COOKIE_METHOD_EXPLICIT_PARTITIONING, true);
|
||||
$pageurl->params([
|
||||
'cookiecheck1' => self::COOKIE_METHOD_NO_PARTITIONING,
|
||||
'cookiecheck2' => self::COOKIE_METHOD_EXPLICIT_PARTITIONING,
|
||||
]);
|
||||
|
||||
// LTI needs to guarantee the 'SameSite=None', 'Secure' (and sometimes 'Partitioned') attributes are set on the
|
||||
// MoodleSession cookie. This is done via manipulation of the outgoing headers after the cookie check redirect. To
|
||||
// guarantee these outgoing Set-Cookie headers will be created after the redirect, expire the current cookie.
|
||||
core_cookie_helper::expire_moodlesession();
|
||||
|
||||
redirect($pageurl);
|
||||
} else {
|
||||
// Have already started a cookie check, so check the result.
|
||||
$cookie1received = isset($_COOKIE['cookiecheck1']) && $_COOKIE['cookiecheck1'] == $cookiecheck1;
|
||||
$cookie2received = isset($_COOKIE['cookiecheck2']) && $_COOKIE['cookiecheck2'] == $cookiecheck2;
|
||||
|
||||
if ($cookie1received || $cookie2received) {
|
||||
// The test cookie could be set and received.
|
||||
// Set a session flag storing the method used to set it, and make sure the session cookie uses this method.
|
||||
$cookiemethod = $cookie1received ? self::COOKIE_METHOD_NO_PARTITIONING : self::COOKIE_METHOD_EXPLICIT_PARTITIONING;
|
||||
$SESSION->auth_lti_cookie_method = $cookiemethod;
|
||||
|
||||
self::setup_session_cookie();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If a cookie check has been made, returns whether cookies could be set or not.
|
||||
*
|
||||
* @return bool whether cookies are supported or not.
|
||||
*/
|
||||
public static function cookies_supported(): bool {
|
||||
return self::get_cookies_supported_method() !== self::COOKIE_METHOD_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* If a cookie check has been made, gets the method used to set a cookie, or self::COOKIE_METHOD_NOT_SUPPORTED if not supported.
|
||||
*
|
||||
* For cookie methods:
|
||||
* @see self::COOKIE_METHOD_NOT_SUPPORTED
|
||||
* @see self::COOKIE_METHOD_NO_PARTITIONING
|
||||
* @see self::COOKIE_METHOD_EXPLICIT_PARTITIONING
|
||||
*
|
||||
* @return int the constant representing the method by which the cookie was set, or not.
|
||||
*/
|
||||
public static function get_cookies_supported_method(): int {
|
||||
global $SESSION;
|
||||
return $SESSION->auth_lti_cookie_method ?? self::COOKIE_METHOD_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the session cookie according to the method used in the cookie check, and with SameSite=None; Secure attributes.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function setup_session_cookie(): void {
|
||||
global $CFG;
|
||||
require_once($CFG->libdir . '/sessionlib.php');
|
||||
|
||||
if (is_moodle_cookie_secure()) {
|
||||
$atts = ['SameSite=None', 'Secure'];
|
||||
if (self::get_cookies_supported_method() == self::COOKIE_METHOD_EXPLICIT_PARTITIONING) {
|
||||
$atts[] = 'Partitioned';
|
||||
}
|
||||
core_cookie_helper::add_attributes_to_cookie_response_header('MoodleSession' . $CFG->sessioncookie, $atts);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a test cookie, using SameSite=None; Secure; attributes if possible, and with or without partitioning opt-in.
|
||||
*
|
||||
* @param string $name cookie name
|
||||
* @param string $value cookie value
|
||||
* @param bool $partitioned whether to try to add partitioning opt-in, which requires secure cookies (https sites).
|
||||
* @return void
|
||||
*/
|
||||
private static function set_test_cookie(string $name, string $value, bool $partitioned = false): void {
|
||||
global $CFG;
|
||||
require_once($CFG->libdir . '/sessionlib.php');
|
||||
|
||||
$atts = ['expires' => time() + 30];
|
||||
if (is_moodle_cookie_secure()) {
|
||||
$atts['samesite'] = 'none';
|
||||
$atts['secure'] = true;
|
||||
}
|
||||
setcookie($name, $value, $atts);
|
||||
|
||||
if (is_moodle_cookie_secure() && $partitioned) {
|
||||
core_cookie_helper::add_attributes_to_cookie_response_header($name, ['Partitioned']);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,33 +33,32 @@ class renderer extends \plugin_renderer_base {
|
||||
* @return string the html.
|
||||
*/
|
||||
public function render_account_binding_options_page(int $provisioningmode): string {
|
||||
|
||||
$formaction = new \moodle_url('/auth/lti/login.php');
|
||||
$notification = new notification(get_string('firstlaunchnotice', 'auth_lti'), \core\notification::INFO, false);
|
||||
$noauthnotice = new notification(get_string('firstlaunchnoauthnotice', 'auth_lti', get_docs_url('Publish_as_LTI_tool')),
|
||||
\core\notification::WARNING, false);
|
||||
$cancreateaccounts = !get_config('moodle', 'authpreventaccountcreation');
|
||||
if ($provisioningmode == \auth_plugin_lti::PROVISIONING_MODE_PROMPT_EXISTING_ONLY) {
|
||||
$cancreateaccounts = false;
|
||||
}
|
||||
|
||||
$accountinfo = ['isloggedin' => isloggedin()];
|
||||
$accountinfo = [];
|
||||
if (isloggedin()) {
|
||||
global $USER;
|
||||
$accountinfo = array_merge($accountinfo, [
|
||||
$accountinfo = [
|
||||
'firstname' => $USER->firstname,
|
||||
'lastname' => $USER->lastname,
|
||||
'email' => $USER->email,
|
||||
'picturehtml' => $this->output->user_picture($USER, ['size' => 35, 'class' => 'round']),
|
||||
]);
|
||||
];
|
||||
}
|
||||
|
||||
$context = [
|
||||
'isloggedin' => isloggedin(),
|
||||
'info' => $notification->export_for_template($this),
|
||||
'formaction' => $formaction->out(),
|
||||
'sesskey' => sesskey(),
|
||||
'accountinfo' => $accountinfo,
|
||||
'cancreateaccounts' => $cancreateaccounts,
|
||||
'noauthnotice' => $noauthnotice->export_for_template($this)
|
||||
];
|
||||
return parent::render_from_template('auth_lti/local/ltiadvantage/login', $context);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* LTI Auth plugin event handler definition.
|
||||
*
|
||||
* @package auth_lti
|
||||
* @category event
|
||||
* @copyright 2024 Jake Dallimore <jrhdallimore@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$observers = [
|
||||
[
|
||||
'eventname' => '\core\event\user_loggedin',
|
||||
'callback' => '\auth_lti\local\ltiadvantage\event\event_handler::handle_user_loggedin',
|
||||
],
|
||||
];
|
||||
@@ -35,7 +35,7 @@ $string['firstlaunchnoauthnotice'] = 'To link your existing account you must be
|
||||
$string['getstartedwithnewaccount'] = 'Get started with a new account';
|
||||
$string['haveexistingaccount'] = 'I have an existing account';
|
||||
$string['linkthisaccount'] = 'Link this account';
|
||||
$string['mustbeloggedin'] = 'You need to be logged in to your existing account';
|
||||
$string['mustbeloggedin'] = 'Sign in to link your existing account';
|
||||
$string['pluginname'] = 'LTI';
|
||||
$string['privacy:metadata:auth_lti'] = 'LTI authentication';
|
||||
$string['privacy:metadata:auth_lti:authsubsystem'] = 'This plugin is connected to the authentication subsystem.';
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
* info - a notification describing the first launch options
|
||||
* cancreateaccounts - whether or not the user is allowed to create auth_lti accounts
|
||||
* accountinfo - information about the user, importantly whether they are logged in or not.
|
||||
* noauthnotice - a notification telling the user they must be authenticated to link accounts. Only relevant when not logged in.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
@@ -46,19 +45,12 @@
|
||||
"issuccess": true
|
||||
},
|
||||
"cancreateaccounts": true,
|
||||
"isloggedin": true,
|
||||
"accountinfo": {
|
||||
"isloggedin": true,
|
||||
"firstname": "John",
|
||||
"lastname": "Smith",
|
||||
"email": "john@example.com",
|
||||
"picturehtml": "<img src=\"http://site.example.com/pluginfile.php/5/user/icon/boost/f2?rev=99\" class=\"round\" alt=\"\" width=\"35\" height=\"35\">"
|
||||
},
|
||||
"noauthnotice": {
|
||||
"message": "To link your existing account you must be logged in to the site...",
|
||||
"extraclasses": "",
|
||||
"announce": false,
|
||||
"closebutton": false,
|
||||
"iswarning": true
|
||||
}
|
||||
}
|
||||
}}
|
||||
@@ -79,8 +71,8 @@
|
||||
<div class="card-body text-center d-flex flex-column">
|
||||
<i class="fa fa-user-circle-o fa-2x link"></i>
|
||||
<h4 class="card-title">{{#str}} useexistingaccount, auth_lti {{/str}}</h4>
|
||||
{{#accountinfo}}
|
||||
{{#isloggedin}}
|
||||
{{#accountinfo}}
|
||||
<p class="card-text mt-2">
|
||||
<span class="text-muted">
|
||||
{{#str}} currentlyloggedinas, auth_lti {{/str}}
|
||||
@@ -90,14 +82,12 @@
|
||||
{{firstname}} {{lastname}} ({{email}})
|
||||
</p>
|
||||
<input type="submit" class="btn btn-primary mt-auto" name="existing_account" value="{{#str}} linkthisaccount, auth_lti {{/str}}">
|
||||
{{/accountinfo}}
|
||||
{{/isloggedin}}
|
||||
{{^isloggedin}}
|
||||
<p class="card-text text-muted">{{#str}} mustbeloggedin, auth_lti {{/str}}</p>
|
||||
{{#noauthnotice}}
|
||||
{{> core/notification}}
|
||||
{{/noauthnotice}}
|
||||
<input type="submit" class="btn btn-primary mt-auto" name="existing_account" value="{{#str}} login, moodle {{/str}}">
|
||||
{{/isloggedin}}
|
||||
{{/accountinfo}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2022112800; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->version = 2022112801; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2022111800; // Requires this Moodle version.
|
||||
$plugin->component = 'auth_lti'; // Full name of the plugin (used for diagnostics).
|
||||
|
||||
@@ -345,7 +345,7 @@ class api {
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete linked login
|
||||
* Delete a users own linked login
|
||||
*
|
||||
* Requires auth/oauth2:managelinkedlogins capability at the user context.
|
||||
*
|
||||
@@ -353,14 +353,19 @@ class api {
|
||||
* @return boolean
|
||||
*/
|
||||
public static function delete_linked_login($linkedloginid) {
|
||||
$login = new linked_login($linkedloginid);
|
||||
$userid = $login->get('userid');
|
||||
global $USER;
|
||||
|
||||
if (\core\session\manager::is_loggedinas()) {
|
||||
throw new moodle_exception('notwhileloggedinas', 'auth_oauth2');
|
||||
}
|
||||
|
||||
$context = context_user::instance($userid);
|
||||
$login = linked_login::get_record([
|
||||
'id' => $linkedloginid,
|
||||
'userid' => $USER->id,
|
||||
'confirmtoken' => '',
|
||||
], MUST_EXIST);
|
||||
|
||||
$context = context_user::instance($login->get('userid'));
|
||||
require_capability('auth/oauth2:managelinkedlogins', $context);
|
||||
|
||||
$login->delete();
|
||||
|
||||
@@ -137,6 +137,7 @@ class api_test extends \advanced_testcase {
|
||||
$issuer = \core\oauth2\api::create_standard_issuer('google');
|
||||
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$this->setUser($user);
|
||||
|
||||
$info = [];
|
||||
$info['username'] = 'banana';
|
||||
@@ -169,6 +170,30 @@ class api_test extends \advanced_testcase {
|
||||
$this->assertEquals($newuser->id, $match->get('userid'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that we cannot deleted a linked login for another user
|
||||
*/
|
||||
public function test_delete_linked_login_other_user(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$this->setAdminUser();
|
||||
$issuer = \core\oauth2\api::create_standard_issuer('google');
|
||||
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
|
||||
api::link_login([
|
||||
'username' => 'banana',
|
||||
'email' => 'banana@example.com',
|
||||
], $issuer, $user->id);
|
||||
|
||||
/** @var linked_login $linkedlogin */
|
||||
$linkedlogin = api::get_linked_logins($user->id)[0];
|
||||
|
||||
// We are logged in as a different user, so cannot delete this.
|
||||
$this->expectException(\dml_missing_record_exception::class);
|
||||
api::delete_linked_login($linkedlogin->get('id'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that is_enabled correctly identifies when the plugin is enabled.
|
||||
*/
|
||||
|
||||
@@ -52,7 +52,7 @@ abstract class backup_block_task extends backup_task {
|
||||
}
|
||||
|
||||
$this->blockid = $blockid;
|
||||
$this->blockname = $block->blockname;
|
||||
$this->blockname = clean_param($block->blockname, PARAM_PLUGIN);
|
||||
$this->contextid = context_block::instance($this->blockid)->id;
|
||||
$this->moduleid = $moduleid;
|
||||
$this->modulename = null;
|
||||
|
||||
+10
-10
@@ -134,14 +134,14 @@ class core_backup_renderer extends plugin_renderer_base {
|
||||
$html .= $this->backup_detail_pair(get_string('backupmode', 'backup'), get_string('backupmode'.$details->mode, 'backup'));
|
||||
$html .= $this->backup_detail_pair(get_string('backupdate', 'backup'), userdate($details->backup_date));
|
||||
$html .= $this->backup_detail_pair(get_string('moodleversion', 'backup'),
|
||||
html_writer::tag('span', $details->moodle_release, array('class' => 'moodle_release')).
|
||||
html_writer::tag('span', '['.$details->moodle_version.']', array('class' => 'moodle_version sub-detail')));
|
||||
html_writer::tag('span', s($details->moodle_release), array('class' => 'moodle_release')).
|
||||
html_writer::tag('span', '[' . s($details->moodle_version) .']', array('class' => 'moodle_version sub-detail')));
|
||||
$html .= $this->backup_detail_pair(get_string('backupversion', 'backup'),
|
||||
html_writer::tag('span', $details->backup_release, array('class' => 'moodle_release')).
|
||||
html_writer::tag('span', '['.$details->backup_version.']', array('class' => 'moodle_version sub-detail')));
|
||||
html_writer::tag('span', s($details->backup_release), array('class' => 'moodle_release')).
|
||||
html_writer::tag('span', '[' . s($details->backup_version) . ']', array('class' => 'moodle_version sub-detail')));
|
||||
$html .= $this->backup_detail_pair(get_string('originalwwwroot', 'backup'),
|
||||
html_writer::tag('span', $details->original_wwwroot, array('class' => 'originalwwwroot')).
|
||||
html_writer::tag('span', '['.$details->original_site_identifier_hash.']', array('class' => 'sitehash sub-detail')));
|
||||
html_writer::tag('span', s($details->original_wwwroot), array('class' => 'originalwwwroot')).
|
||||
html_writer::tag('span', '[' . s($details->original_site_identifier_hash) . ']', array('class' => 'sitehash sub-detail')));
|
||||
if (!empty($details->include_file_references_to_external_content)) {
|
||||
$message = '';
|
||||
if (backup_general_helper::backup_is_samesite($details)) {
|
||||
@@ -169,8 +169,8 @@ class core_backup_renderer extends plugin_renderer_base {
|
||||
$html .= html_writer::start_tag('div', ['class' => 'backup-section',
|
||||
'role' => 'table', 'aria-labelledby' => 'backupcoursedetailsheader']);
|
||||
$html .= $this->output->heading(get_string('backupcoursedetails', 'backup'), 2, 'header', 'backupcoursedetailsheader');
|
||||
$html .= $this->backup_detail_pair(get_string('coursetitle', 'backup'), $details->course->title);
|
||||
$html .= $this->backup_detail_pair(get_string('courseid', 'backup'), $details->course->courseid);
|
||||
$html .= $this->backup_detail_pair(get_string('coursetitle', 'backup'), format_string($details->course->title));
|
||||
$html .= $this->backup_detail_pair(get_string('courseid', 'backup'), clean_param($details->course->courseid, PARAM_INT));
|
||||
|
||||
// Warning users about front page backups.
|
||||
if ($details->original_course_format === 'site') {
|
||||
@@ -188,7 +188,7 @@ class core_backup_renderer extends plugin_renderer_base {
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
$html .= $this->backup_detail_pair(get_string('backupcoursesection', 'backup', $section->title), $value);
|
||||
$html .= $this->backup_detail_pair(get_string('backupcoursesection', 'backup', format_string($section->title)), $value);
|
||||
$table = null;
|
||||
foreach ($details->activities as $activitykey => $activity) {
|
||||
if ($activity->sectionid != $section->sectionid) {
|
||||
@@ -206,7 +206,7 @@ class core_backup_renderer extends plugin_renderer_base {
|
||||
$icon = new image_icon('monologo', '', $activity->modulename, ['class' => 'iconlarge icon-pre']);
|
||||
$table->data[] = array(
|
||||
$this->output->render($icon).$name,
|
||||
$activity->title,
|
||||
format_string($activity->title),
|
||||
($activity->settings[$activitykey.'_userinfo']) ? $yestick : $notick,
|
||||
);
|
||||
}
|
||||
|
||||
+2
-1
@@ -107,9 +107,10 @@ if (!has_any_capability(array(
|
||||
$PAGE->set_title($hdr);
|
||||
$output = $PAGE->get_renderer('core', 'badges');
|
||||
|
||||
if (($delete || $archive) && has_capability('moodle/badges:deletebadge', $PAGE->context)) {
|
||||
if ($delete || $archive) {
|
||||
$badgeid = ($archive != 0) ? $archive : $delete;
|
||||
$badge = new badge($badgeid);
|
||||
require_capability('moodle/badges:deletebadge', $badge->get_context());
|
||||
if (!$confirm) {
|
||||
echo $output->header();
|
||||
// Archive this badge?
|
||||
|
||||
@@ -119,3 +119,97 @@ Feature: Award badges with separate groups
|
||||
And I press "Award badge"
|
||||
# Teacher 2 shouldn't be able to go further
|
||||
Then I should see "Sorry, but you need to be part of a group to see this page."
|
||||
|
||||
@javascript
|
||||
Scenario: Editing teacher can award badge to members of separate groups
|
||||
Given I am on the "Course 1" course page logged in as teacher1
|
||||
And I navigate to "Badges > Manage badges" in current page administration
|
||||
And I follow "Course Badge"
|
||||
And I press "Award badge"
|
||||
When I set the field "role" to "Teacher"
|
||||
# Confirm that editing teacher sees a separate groups dropdown menu.
|
||||
Then "Separate groups" "select" should exist
|
||||
And I should see "All participants" in the "Separate groups" "select"
|
||||
# Confirm that all participants are displayed when All participants is selected.
|
||||
And I should see "Student 1" in the "potentialrecipients[]" "select"
|
||||
And I should see "Student 2" in the "potentialrecipients[]" "select"
|
||||
And I should see "Teacher 1" in the "potentialrecipients[]" "select"
|
||||
And I should see "Teacher 2" in the "potentialrecipients[]" "select"
|
||||
And I set the field "Separate groups" to "Class A"
|
||||
# Confirm that only members of selected group are displayed
|
||||
And I should not see "Student 1" in the "potentialrecipients[]" "select"
|
||||
And I should not see "Teacher 1" in the "potentialrecipients[]" "select"
|
||||
And I should see "Student 2" in the "potentialrecipients[]" "select"
|
||||
And I should see "Teacher 2" in the "potentialrecipients[]" "select"
|
||||
And I set the field "Separate groups" to "Class B"
|
||||
And I should not see "Student 2" in the "potentialrecipients[]" "select"
|
||||
And I should not see "Teacher 2" in the "potentialrecipients[]" "select"
|
||||
And I should see "Student 1" in the "potentialrecipients[]" "select"
|
||||
And I should see "Teacher 1" in the "potentialrecipients[]" "select"
|
||||
|
||||
@javascript
|
||||
Scenario Outline: Teacher can award badge to members of visible groups
|
||||
Given I am on the "Course 1" "course editing" page logged in as teacher1
|
||||
And I expand all fieldsets
|
||||
# Set the group mode to visible groups.
|
||||
And I set the field "Group mode" to "Visible groups"
|
||||
And I press "Save and display"
|
||||
When I am on the "Course 1" course page logged in as <loggedinuser>
|
||||
And I navigate to "Badges > Manage badges" in current page administration
|
||||
And I follow "Course Badge"
|
||||
And I press "Award badge"
|
||||
And I set the field "role" to "<awarder>"
|
||||
# Confirm that teachers see a visible groups dropdown menu.
|
||||
Then "Visible groups" "select" should exist
|
||||
# Confirm that My groups option group exists.
|
||||
And "optgroup[label='My groups']" "css_element" should exist in the "select[name='group']" "css_element"
|
||||
# Confirm that Other groups option group exists.
|
||||
And "optgroup[label='Other groups']" "css_element" should exist in the "select[name='group']" "css_element"
|
||||
# Confirm that all participants are displayed when All participants is selected.
|
||||
And I set the field "Visible groups" to "All participants"
|
||||
And I should see "Student 1" in the "potentialrecipients[]" "select"
|
||||
And I should see "Student 2" in the "potentialrecipients[]" "select"
|
||||
And I should see "Teacher 1" in the "potentialrecipients[]" "select"
|
||||
And I should see "Teacher 2" in the "potentialrecipients[]" "select"
|
||||
# Confirm that only members of selected group are displayed.
|
||||
And I set the field "Visible groups" to "Class A"
|
||||
And I should not see "Student 1" in the "potentialrecipients[]" "select"
|
||||
And I should not see "Teacher 1" in the "potentialrecipients[]" "select"
|
||||
And I should see "Student 2" in the "potentialrecipients[]" "select"
|
||||
And I should see "Teacher 2" in the "potentialrecipients[]" "select"
|
||||
And I set the field "Visible groups" to "Class B"
|
||||
And I should not see "Student 2" in the "potentialrecipients[]" "select"
|
||||
And I should not see "Teacher 2" in the "potentialrecipients[]" "select"
|
||||
And I should see "Student 1" in the "potentialrecipients[]" "select"
|
||||
And I should see "Teacher 1" in the "potentialrecipients[]" "select"
|
||||
|
||||
Examples:
|
||||
| loggedinuser | awarder |
|
||||
| teacher1 | Teacher |
|
||||
| teacher2 | Non-editing teacher |
|
||||
|
||||
@javascript
|
||||
Scenario Outline: Teacher can award badge to members when group mode is set to no groups
|
||||
Given I am on the "Course 1" "course editing" page logged in as teacher1
|
||||
And I expand all fieldsets
|
||||
# Set the group mode to no groups.
|
||||
And I set the field "Group mode" to "No groups"
|
||||
And I press "Save and display"
|
||||
When I am on the "Course 1" course page logged in as <loggedinuser>
|
||||
And I navigate to "Badges > Manage badges" in current page administration
|
||||
And I follow "Course Badge"
|
||||
And I press "Award badge"
|
||||
And I set the field "role" to "<awarder>"
|
||||
# Confirm that no group dropdowns don't exist.
|
||||
Then "Separate groups" "select" should not exist
|
||||
And "Visible groups" "select" should not exist
|
||||
# Confirm all participants are displayed.
|
||||
And I should see "Student 1" in the "potentialrecipients[]" "select"
|
||||
And I should see "Student 2" in the "potentialrecipients[]" "select"
|
||||
And I should see "Teacher 1" in the "potentialrecipients[]" "select"
|
||||
And I should see "Teacher 2" in the "potentialrecipients[]" "select"
|
||||
|
||||
Examples:
|
||||
| loggedinuser | awarder |
|
||||
| teacher1 | Teacher |
|
||||
| teacher2 | Non-editing teacher |
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
@core @core_badges
|
||||
Feature: Delete course badge already awarded
|
||||
As a teacher
|
||||
I can delete awarded course badge
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | 1 | teacher1@example.com |
|
||||
| student1 | Student | 1 | student1@example.com |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname |
|
||||
| Course 1 | C1 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| student1 | C1 | student |
|
||||
|
||||
@javascript
|
||||
Scenario Outline: Delete course badge already awarded to student
|
||||
Given the following "core_badges > Badges" exist:
|
||||
| name | course | description | image | status | type |
|
||||
| <badgename> | C1 | <badgename> description | badges/tests/behat/badge.png | active | 2 |
|
||||
And the following "core_badges > Criterias" exist:
|
||||
| badge | role |
|
||||
| <badgename> | editingteacher |
|
||||
And the following "core_badges > Issued badges" exist:
|
||||
| badge | user |
|
||||
| <badgename> | student1 |
|
||||
When I am on the "Course 1" "enrolled users" page logged in as "teacher1"
|
||||
And I click on "Student 1" "link"
|
||||
Then I should see "<badgename>"
|
||||
And I am on the "Course 1" course page
|
||||
# Navigate to Manage Badges page in order to delete the badge
|
||||
And I navigate to "Badges > Manage badges" in current page administration
|
||||
# Delete the badge
|
||||
And I click on "Delete" "icon" in the "<badgename>" "table_row"
|
||||
And I press "<deleteoption>"
|
||||
And I am on the "Course 1" "enrolled users" page
|
||||
And I click on "Student 1" "link"
|
||||
# Confirm that Badge 1 is retained as awarded badge but Badge 2 is not
|
||||
And I <visibility> see "<badgename>"
|
||||
And I am on the "Course 1" course page
|
||||
# Navigate to Badges page to confirm that no badges exist, hence, Manage badges would not exist
|
||||
And I navigate to "Badges" in current page administration
|
||||
# Confirm that badges are sucessfully deleted
|
||||
And I should see "There are currently no badges available for users to earn."
|
||||
|
||||
Examples:
|
||||
| badgename | deleteoption | visibility |
|
||||
| Badge 1 | Delete and keep existing issued badges | should |
|
||||
| Badge 2 | Delete and remove existing issued badges | should not |
|
||||
+20
-1
@@ -59,7 +59,26 @@ function block_comments_comment_validate($comment_param) {
|
||||
* @return array
|
||||
*/
|
||||
function block_comments_comment_permissions($args) {
|
||||
return array('post'=>true, 'view'=>true);
|
||||
global $DB, $USER;
|
||||
// By default, anyone can post and view comments.
|
||||
$canpost = $canview = true;
|
||||
// Check if it's the user context and not the owner's profile.
|
||||
if ($args->context->contextlevel == CONTEXT_USER && $USER->id != $args->context->instanceid) {
|
||||
// Check whether the context owner has a comment block in the user's profile.
|
||||
$sqlparam = [
|
||||
'blockname' => 'comments',
|
||||
'parentcontextid' => $args->context->id,
|
||||
'pagetypepattern' => 'user-profile',
|
||||
];
|
||||
// If the comment block is not present at the target user's profile,
|
||||
// then the logged-in user cannot post or view comments.
|
||||
$canpost = $canview = $DB->record_exists_select(
|
||||
'block_instances',
|
||||
'blockname = :blockname AND parentcontextid = :parentcontextid AND pagetypepattern = :pagetypepattern',
|
||||
$sqlparam,
|
||||
);
|
||||
}
|
||||
return ['post' => $canpost, 'view' => $canview];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,7 +10,7 @@ Feature: Add a comment to the comments block
|
||||
| Course 1 | C1 | 0 |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | Frist | teacher1@example.com |
|
||||
| teacher1 | Teacher | First | teacher1@example.com |
|
||||
| student1 | Student | First | student1@example.com |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
@@ -25,11 +25,16 @@ Feature: Add a comment to the comments block
|
||||
Scenario: Add a comment with Javascript enabled
|
||||
When I add "I'm a comment from student1" comment to comments block
|
||||
Then I should see "I'm a comment from student1"
|
||||
And I am on the "Course 1" course page logged in as teacher1
|
||||
And I should see "I'm a comment from student1"
|
||||
|
||||
Scenario: Add a comment with Javascript disabled
|
||||
When I follow "Show comments"
|
||||
And I add "I'm a comment from student1" comment to comments block
|
||||
Then I should see "I'm a comment from student1"
|
||||
And I am on the "Course 1" course page logged in as teacher1
|
||||
And I follow "Show comments"
|
||||
And I should see "I'm a comment from student1"
|
||||
|
||||
@javascript
|
||||
Scenario: Test comment block pagination
|
||||
|
||||
@@ -10,7 +10,7 @@ Feature: Enable Block comments on an activity page and view comments
|
||||
| Course 1 | C1 | 0 |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | Frist | teacher1@example.com |
|
||||
| teacher1 | Teacher | First | teacher1@example.com |
|
||||
| student1 | Student | First | student1@example.com |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
@block @block_comments
|
||||
Feature: Enable Block comments on a course page and view comments
|
||||
In order to enable the comments block on a course page
|
||||
As a teacher
|
||||
I can add the comments block to the course page
|
||||
|
||||
Scenario: Add the comments block on the course page and add comments
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname | category |
|
||||
| Course 1 | C1 | 0 |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | Frist | teacher1@example.com |
|
||||
| student1 | Student | First | student1@example.com |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| student1 | C1 | student |
|
||||
And the following "blocks" exist:
|
||||
| blockname | contextlevel | reference | pagetypepattern | defaultregion |
|
||||
| comments | Course | C1 | course-view-* | side-pre |
|
||||
And I am on the "Course 1" course page logged in as teacher1
|
||||
And I follow "Show comments"
|
||||
And I add "I'm a comment from the teacher" comment to comments block
|
||||
When I am on the "Course 1" course page logged in as student1
|
||||
And I follow "Show comments"
|
||||
Then I should see "I'm a comment from the teacher"
|
||||
@@ -10,7 +10,7 @@ Feature: Enable Block comments on the dashboard and view comments
|
||||
| Course 1 | C1 | 0 |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | Frist | teacher1@example.com |
|
||||
| teacher1 | Teacher | First | teacher1@example.com |
|
||||
|
||||
Scenario: Add the comments block on the dashboard and add comments with Javascript disabled
|
||||
When I log in as "teacher1"
|
||||
|
||||
@@ -58,7 +58,7 @@ class helper {
|
||||
FROM {block_recentlyaccesseditems} rai
|
||||
JOIN {course} c ON c.id = rai.courseid
|
||||
WHERE userid = :userid
|
||||
ORDER BY rai.timeaccess DESC";
|
||||
ORDER BY rai.timeaccess DESC, rai.id DESC";
|
||||
$records = $DB->get_records_sql($sql, $paramsql);
|
||||
$order = 0;
|
||||
|
||||
|
||||
@@ -27,9 +27,8 @@
|
||||
<div class="text-xs-center text-center mt-4" data-region="empty-message">
|
||||
<img
|
||||
src="{{noitemsimgurl}}"
|
||||
alt="{{#str}} noitems, block_recentlyaccesseditems {{/str}}"
|
||||
role="presentation"
|
||||
alt=""
|
||||
style="height: 70px; width: 70px;"
|
||||
>
|
||||
<p class="text-muted mt-3 mb-0">{{#str}} noitems, block_recentlyaccesseditems {{/str}}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@ Feature: Edit activities in main menu block
|
||||
I need to add and edit activities there
|
||||
|
||||
@javascript
|
||||
Scenario: Edit name of acitivity in-place in site main menu block
|
||||
Scenario: Edit name of activity in-place in site main menu block
|
||||
Given the following "activity" exists:
|
||||
| activity | forum |
|
||||
| course | Acceptance test site |
|
||||
@@ -36,6 +36,7 @@ Feature: Edit activities in main menu block
|
||||
| forum | Acceptance test site | 0 | Visible forum |
|
||||
| forum | Acceptance test site | 0 | My forum name |
|
||||
And I log in as "admin"
|
||||
And I change window size to "large"
|
||||
And I am on site homepage
|
||||
And I turn editing mode on
|
||||
And "My forum name" activity in site main menu block should have "Hide" editing icon
|
||||
|
||||
@@ -101,13 +101,14 @@ class block_tag_flickr extends block_base {
|
||||
$request .= '&api_key='.FLICKR_DEV_KEY;
|
||||
$request .= '&photoset_id='.$this->config->photoset;
|
||||
$request .= '&per_page='.$numberofphotos;
|
||||
$request .= '&format=php_serial';
|
||||
$request .= '&format=json';
|
||||
// We need to add nojsoncallback=? here, otherwise, Flickr will return the jsonFlickrApi object.
|
||||
$request .= '&nojsoncallback=?';
|
||||
|
||||
$response = $this->fetch_request($request);
|
||||
|
||||
$search = @unserialize($response);
|
||||
if ($search === false && $search != serialize(false)) {
|
||||
// The response didn't appear to be anything serialized, exit...
|
||||
$search = @json_decode($response, true);
|
||||
if (!is_array($search) || json_last_error() !== JSON_ERROR_NONE) {
|
||||
// The response didn't appear to be in correct format.
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -126,13 +127,14 @@ class block_tag_flickr extends block_base {
|
||||
$request .= '&tags='.$tagscsv;
|
||||
$request .= '&per_page='.$numberofphotos;
|
||||
$request .= '&sort='.$sortby;
|
||||
$request .= '&format=php_serial';
|
||||
$request .= '&format=json';
|
||||
// We need to add nojsoncallback=? here, otherwise, Flickr will return the jsonFlickrApi object.
|
||||
$request .= '&nojsoncallback=?';
|
||||
|
||||
$response = $this->fetch_request($request);
|
||||
|
||||
$search = @unserialize($response);
|
||||
if ($search === false && $search != serialize(false)) {
|
||||
// The response didn't appear to be anything serialized, exit...
|
||||
$search = @json_decode($response, true);
|
||||
if (!is_array($search) || json_last_error() !== JSON_ERROR_NONE) {
|
||||
// The response didn't appear to be in correct format.
|
||||
return;
|
||||
}
|
||||
$photos = array_values($search['photos']['photo']);
|
||||
|
||||
Vendored
+2
-1
@@ -608,7 +608,8 @@ class cache implements cache_loader {
|
||||
// store; parent method will have set it to all stores if needed.
|
||||
if ($setaftervalidation) {
|
||||
$lock = null;
|
||||
if (!empty($this->requirelockingbeforewrite)) {
|
||||
// Only try to acquire a lock for this cache if we do not already have one.
|
||||
if (!empty($this->requirelockingbeforewrite) && !$this->check_lock_state($key)) {
|
||||
$lock = $this->acquire_lock($key);
|
||||
}
|
||||
if ($requiredversion === self::VERSION_NONE) {
|
||||
|
||||
@@ -78,7 +78,8 @@ trait eventtype {
|
||||
if (!empty($eventtypes['user']) && count($options) == 1) {
|
||||
$mform->addElement('hidden', 'eventtype');
|
||||
$mform->setType('eventtype', PARAM_TEXT);
|
||||
$mform->setDefault('eventtype', 'user');
|
||||
$mform->hardFreeze('eventtype');
|
||||
$mform->setConstant('eventtype', 'user');
|
||||
return;
|
||||
} else {
|
||||
$mform->addElement('select', 'eventtype', get_string('eventkind', 'calendar'), $options);
|
||||
|
||||
@@ -106,7 +106,7 @@ class managesubscriptions extends \moodleform {
|
||||
$courseid = (!empty($data[$coursekey])) ? $data[$coursekey] : null;
|
||||
$eventtypes = calendar_get_allowed_event_types($courseid);
|
||||
|
||||
if (empty($eventtype) || !isset($eventtypes[$eventtype])) {
|
||||
if (empty($eventtype) || !isset($eventtypes[$eventtype]) || $eventtypes[$eventtype] == false) {
|
||||
$errors['eventtype'] = get_string('invalideventtype', 'calendar');
|
||||
}
|
||||
|
||||
|
||||
@@ -898,7 +898,17 @@ class core_calendar_external extends external_api {
|
||||
$courseid = (!empty($data[$coursekey])) ? $data[$coursekey] : null;
|
||||
$editoroptions = \core_calendar\local\event\forms\create::build_editor_options($context);
|
||||
$formoptions = ['editoroptions' => $editoroptions, 'courseid' => $courseid];
|
||||
$formoptions['eventtypes'] = calendar_get_allowed_event_types($courseid);
|
||||
$allowedeeventtypes = calendar_get_allowed_event_types($courseid);
|
||||
|
||||
// Event type validation.
|
||||
if (in_array(true, $allowedeeventtypes, true) === false) {
|
||||
throw new \moodle_exception('nopermissiontoupdatecalendar');
|
||||
}
|
||||
if (empty($eventtype) || !isset($allowedeeventtypes[$eventtype]) || $allowedeeventtypes[$eventtype] == false) {
|
||||
return ['validationerror' => true];
|
||||
}
|
||||
|
||||
$formoptions['eventtypes'] = $allowedeeventtypes;
|
||||
if ($courseid) {
|
||||
require_once($CFG->libdir . '/grouplib.php');
|
||||
$groupcoursedata = groups_get_course_data($courseid);
|
||||
|
||||
@@ -3555,6 +3555,18 @@ function calendar_get_view(\calendar_information $calendar, $view, $includenavig
|
||||
}
|
||||
}
|
||||
|
||||
// Check if $data has events.
|
||||
if (isset($data->events)) {
|
||||
// Let's check and sanitize all "name" in $data->events before it's sent to front end.
|
||||
foreach ($data->events as $d) {
|
||||
$name = $d->name ?? null;
|
||||
// Encode special characters if our decoded name does not match the original name.
|
||||
if ($name && (html_entity_decode($name) !== $name)) {
|
||||
$d->name = htmlspecialchars(html_entity_decode($name), ENT_QUOTES, 'utf-8');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [$data, $template];
|
||||
}
|
||||
|
||||
|
||||
@@ -176,7 +176,23 @@ class behat_calendar extends behat_base {
|
||||
* @return void
|
||||
*/
|
||||
public function i_am_viewing_site_calendar() {
|
||||
$url = new moodle_url('/calendar/view.php', ['view' => 'month']);
|
||||
$this->i_am_viewing_calendar_in_view('month');
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to a specific view in the calendar.
|
||||
*
|
||||
* @Given /^I am viewing calendar in "([^"]+)" view$/
|
||||
* @param string $view The calendar view ('month', 'day' and 'upcoming') to navigate to.
|
||||
* @return void
|
||||
*/
|
||||
public function i_am_viewing_calendar_in_view(string $view): void {
|
||||
|
||||
if (!in_array($view, ['month', 'day', 'upcoming'])) {
|
||||
throw new Exception("Invalid calendar view. Allowed values are: 'month', 'day' and 'upcoming'");
|
||||
}
|
||||
|
||||
$url = new moodle_url('/calendar/view.php', ['view' => $view]);
|
||||
$this->execute('behat_general::i_visit', [$url]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ Feature: Allow to mark course as completed without cron for activity completion
|
||||
|
||||
Background:
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname | category |
|
||||
| Completion course | CC1 | 0 |
|
||||
| fullname | shortname | category | enablecompletion |
|
||||
| Completion course | CC1 | 0 | 1 |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| student1 | Student | First | student1@example.com |
|
||||
@@ -25,11 +25,7 @@ Feature: Allow to mark course as completed without cron for activity completion
|
||||
And the following "blocks" exist:
|
||||
| blockname | contextlevel | reference | pagetypepattern | defaultregion |
|
||||
| completionstatus | Course | CC1 | course-view-* | side-pre |
|
||||
And I am on the "Completion course" "course editing" page logged in as admin
|
||||
And I expand all fieldsets
|
||||
And I set the field "Enable completion tracking" to "Yes"
|
||||
And I click on "Save and display" "button"
|
||||
And I am on the "Test assignment name" "assign activity editing" page
|
||||
And I am on the "Test assignment name" "assign activity editing" page logged in as admin
|
||||
And I follow "Expand all"
|
||||
And I set the field "Completion tracking" to "Show activity as complete when conditions are met"
|
||||
And I set the field "completionusegrade" to "1"
|
||||
|
||||
@@ -10,7 +10,7 @@ Feature: Allow teachers to bulk edit activity completion rules in a course.
|
||||
| Course 1 | C1 | 0 |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | Frist | teacher1@example.com |
|
||||
| teacher1 | Teacher | First | teacher1@example.com |
|
||||
| student1 | Student | First | student1@example.com |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
|
||||
@@ -6,8 +6,8 @@ Feature: Show activity completion status or activity completion configuration on
|
||||
|
||||
Background:
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname | category |
|
||||
| Course 1 | C1 | 0 |
|
||||
| fullname | shortname | category | enablecompletion |
|
||||
| Course 1 | C1 | 0 | 1 |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | First | teacher1@example.com |
|
||||
@@ -18,12 +18,6 @@ Feature: Show activity completion status or activity completion configuration on
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| teacher2 | C1 | teacher |
|
||||
| student1 | C1 | student |
|
||||
And I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
And I navigate to "Settings" in current page administration
|
||||
And I set the following fields to these values:
|
||||
| Enable completion tracking | Yes |
|
||||
And I press "Save and display"
|
||||
And the following "activities" exist:
|
||||
| activity | course | idnumber | name | intro | completion | completionview | completionexpected |
|
||||
| assign | C1 | assign1 | Test assignment name | Test assignment description | 2 | 1 | 0 |
|
||||
|
||||
@@ -14,7 +14,7 @@ Feature: Allow teachers to edit the default activity completion rules in a cours
|
||||
| Course 1 | C1 | 0 |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | Frist | teacher1@example.com |
|
||||
| teacher1 | Teacher | First | teacher1@example.com |
|
||||
| student1 | Student | First | student1@example.com |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
@@ -56,7 +56,7 @@ Feature: Allow teachers to edit the default activity completion rules in a cours
|
||||
| Course 1 | C1 | 0 | 1 |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | Frist | teacher1@example.com |
|
||||
| teacher1 | Teacher | First | teacher1@example.com |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
|
||||
@@ -7,7 +7,7 @@ Feature: Students will be marked as completed if they have achieved a passing gr
|
||||
| Course 1 | C1 | 0 | 1 |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | Frist | teacher1@example.com |
|
||||
| teacher1 | Teacher | First | teacher1@example.com |
|
||||
| student1 | Student | First | student1@example.com |
|
||||
| student2 | Student | Second | student2@example.com |
|
||||
And the following "course enrolments" exist:
|
||||
|
||||
@@ -8,7 +8,7 @@ Feature: Students will be marked as completed and pass/fail
|
||||
| Course 1 | C1 | 0 | 1 |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | Frist | teacher1@example.com |
|
||||
| teacher1 | Teacher | First | teacher1@example.com |
|
||||
| student1 | Student | First | student1@example.com |
|
||||
| student2 | Student | Second | student2@example.com |
|
||||
| student3 | Student | Third | student3@example.com |
|
||||
|
||||
@@ -7,29 +7,22 @@ Feature: Allow students to manually mark an activity as complete
|
||||
@javascript
|
||||
Scenario: Mark an activity as completed
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname | category |
|
||||
| Course 1 | C1 | 0 |
|
||||
| fullname | shortname | category | enablecompletion |
|
||||
| Course 1 | C1 | 0 | 1 |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | Frist | teacher1@example.com |
|
||||
| teacher1 | Teacher | First | teacher1@example.com |
|
||||
| student1 | Student | First | student1@example.com |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| student1 | C1 | student |
|
||||
And the following "activity" exists:
|
||||
| activity | forum |
|
||||
| course | C1 |
|
||||
| name | Test forum name |
|
||||
| activity | forum |
|
||||
| course | C1 |
|
||||
| name | Test forum name |
|
||||
| completion | 1 |
|
||||
And I am on the "Course 1" course page logged in as teacher1
|
||||
And I navigate to "Settings" in current page administration
|
||||
And I set the following fields to these values:
|
||||
| Enable completion tracking | Yes |
|
||||
And I press "Save and display"
|
||||
And I am on the "Test forum name" "forum activity editing" page
|
||||
And I set the following fields to these values:
|
||||
| completion | 1 |
|
||||
And I press "Save and return to course"
|
||||
And "Student First" user has not completed "Test forum name" activity
|
||||
And I am on the "Course 1" course page logged in as student1
|
||||
When I toggle the manual completion state of "Test forum name"
|
||||
|
||||
@@ -10,7 +10,7 @@ Feature: Students will be shown relevant completion state based on grade item vi
|
||||
| Course 1 | C1 | 0 | 1 |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | Frist | teacher1@example.com |
|
||||
| teacher1 | Teacher | First | teacher1@example.com |
|
||||
| student1 | Student | First | student1@example.com |
|
||||
| student2 | Student | Second | student2@example.com |
|
||||
And the following "course enrolments" exist:
|
||||
|
||||
@@ -10,7 +10,7 @@ Feature: Restrict activity availability through date conditions
|
||||
| Course 1 | C1 | 0 |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | Frist | teacher1@example.com |
|
||||
| teacher1 | Teacher | First | teacher1@example.com |
|
||||
| student1 | Student | First | student1@example.com |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
|
||||
@@ -11,7 +11,7 @@ Feature: Restrict activity availability through grade conditions
|
||||
| Course 1 | C1 | 0 |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | Frist | teacher1@example.com |
|
||||
| teacher1 | Teacher | First | teacher1@example.com |
|
||||
| student1 | Student | First | student1@example.com |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
|
||||
@@ -6,11 +6,11 @@ Feature: Restrict sections availability through completion or grade conditions
|
||||
|
||||
Background:
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname | category |
|
||||
| Course 1 | C1 | 0 |
|
||||
| fullname | shortname | category | enablecompletion |
|
||||
| Course 1 | C1 | 0 | 1 |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | Frist | teacher1@example.com |
|
||||
| teacher1 | Teacher | First | teacher1@example.com |
|
||||
| student1 | Student | First | student1@example.com |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
@@ -23,15 +23,11 @@ Feature: Restrict sections availability through completion or grade conditions
|
||||
|
||||
@javascript
|
||||
Scenario: Show section greyed-out to student when completion condition is not satisfied
|
||||
Given I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
And I navigate to "Settings" in current page administration
|
||||
And I set the following fields to these values:
|
||||
| Enable completion tracking | Yes |
|
||||
And I press "Save and display"
|
||||
And the following "activities" exist:
|
||||
Given the following "activities" exist:
|
||||
| activity | course | section | intro | completion | idnumber |
|
||||
| label | C1 | 1 | Test label | 1 | 1 |
|
||||
And I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
When I edit the section "2"
|
||||
And I expand all fieldsets
|
||||
And I click on "Add restriction..." "button"
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
@core @core_contentbank @core_h5p @contenttype_h5p @_file_upload @_switch_iframe @javascript
|
||||
@core @core_contentbank @core_h5p @contenttype_h5p @_switch_iframe @javascript
|
||||
Feature: H5P file upload to content bank for admins
|
||||
In order import new H5P content to content bank
|
||||
As an admin
|
||||
I need to be able to upload a new .h5p file to content bank
|
||||
|
||||
Background:
|
||||
Given the following "blocks" exist:
|
||||
| blockname | contextlevel | reference | pagetypepattern | defaultregion |
|
||||
| private_files | System | 1 | my-index | side-post |
|
||||
Given the following "user private file" exists:
|
||||
| user | admin |
|
||||
| filepath | h5p/tests/fixtures/filltheblanks.h5p |
|
||||
And I log in as "admin"
|
||||
And I follow "Manage private files..."
|
||||
And I upload "h5p/tests/fixtures/filltheblanks.h5p" file to "Files" filemanager
|
||||
And I click on "Save changes" "button"
|
||||
And I turn editing mode on
|
||||
And the following config values are set as admin:
|
||||
| unaddableblocks | | theme_boost|
|
||||
|
||||
@@ -19,13 +19,10 @@ Feature: H5P file upload to content bank for non admins
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| teacher2 | C1 | editingteacher |
|
||||
And the following "blocks" exist:
|
||||
| blockname | contextlevel | reference | pagetypepattern | defaultregion |
|
||||
| private_files | System | 1 | my-index | side-post |
|
||||
And the following "user private file" exists:
|
||||
| user | teacher1 |
|
||||
| filepath | h5p/tests/fixtures/filltheblanks.h5p |
|
||||
And I log in as "teacher1"
|
||||
And I follow "Manage private files..."
|
||||
And I upload "h5p/tests/fixtures/filltheblanks.h5p" file to "Files" filemanager
|
||||
And I click on "Save changes" "button"
|
||||
|
||||
Scenario: Teachers can not access system level content bank
|
||||
Given I turn editing mode on
|
||||
@@ -128,11 +125,10 @@ Feature: H5P file upload to content bank for non admins
|
||||
And I should not see "filltheblanks.h5p"
|
||||
|
||||
Scenario: Teachers can not see existing contents when libraries are not installed
|
||||
Given I log out
|
||||
Given the following "user private file" exists:
|
||||
| user | admin |
|
||||
| filepath | h5p/tests/fixtures/filltheblanks.h5p |
|
||||
And I log in as "admin"
|
||||
And I follow "Manage private files..."
|
||||
And I upload "h5p/tests/fixtures/filltheblanks.h5p" file to "Files" filemanager
|
||||
And I click on "Save changes" "button"
|
||||
And I navigate to "H5P > Manage H5P content types" in site administration
|
||||
And I should not see "Fill in the Blanks"
|
||||
When I upload "h5p/tests/fixtures/filltheblanks.h5p" file to "H5P content type" filemanager
|
||||
|
||||
@@ -1,28 +1,14 @@
|
||||
@core @core_contentbank @core_h5p @contentbank_h5p @_file_upload @javascript
|
||||
@core @core_contentbank @core_h5p @contentbank_h5p @javascript
|
||||
Feature: Delete H5P file from the content bank
|
||||
In order remove H5P content from the content bank
|
||||
As an admin
|
||||
I need to be able to delete any H5P content from the content bank
|
||||
|
||||
Background:
|
||||
Given the following "blocks" exist:
|
||||
| blockname | contextlevel | reference | pagetypepattern | defaultregion |
|
||||
| private_files | System | 1 | my-index | side-post |
|
||||
And I log in as "admin"
|
||||
And I follow "Manage private files..."
|
||||
And I upload "h5p/tests/fixtures/filltheblanks.h5p" file to "Files" filemanager
|
||||
And I click on "Save changes" "button"
|
||||
And I am on site homepage
|
||||
And I turn editing mode on
|
||||
And the following config values are set as admin:
|
||||
| unaddableblocks | | theme_boost|
|
||||
And I add the "Navigation" block if not present
|
||||
And I configure the "Navigation" block
|
||||
And I set the following fields to these values:
|
||||
| Page contexts | Display throughout the entire site |
|
||||
And I press "Save changes"
|
||||
And I click on "Site pages" "list_item" in the "Navigation" "block"
|
||||
And I click on "Content bank" "link" in the "Navigation" "block"
|
||||
Given the following "user private file" exists:
|
||||
| user | admin |
|
||||
| filepath | h5p/tests/fixtures/filltheblanks.h5p |
|
||||
And I am on the "Content bank" page logged in as "admin"
|
||||
And I click on "Upload" "link"
|
||||
And I click on "Choose a file..." "button"
|
||||
And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
|
||||
@@ -32,14 +18,16 @@ Feature: Delete H5P file from the content bank
|
||||
And I click on "Save changes" "button"
|
||||
|
||||
Scenario: Admins can delete content from the content bank
|
||||
Given I click on "More" "button"
|
||||
Given I wait "2" seconds
|
||||
And I click on "More" "button"
|
||||
And I should see "Delete"
|
||||
And I click on "Delete" "link" in the ".cb-toolbar-container" "css_element"
|
||||
And I click on "Delete" "link"
|
||||
And I should see "Are you sure you want to delete the content 'content2delete.h5p'"
|
||||
And I should not see "The content will only be deleted from the content bank"
|
||||
And I click on "Cancel" "button" in the "Delete content" "dialogue"
|
||||
Then I should see "content2delete.h5p"
|
||||
And I click on "More" "button"
|
||||
And I wait "2" seconds
|
||||
And I click on "More" "button"
|
||||
And I click on "Delete" "link" in the ".cb-toolbar-container" "css_element"
|
||||
And I click on "Delete" "button" in the "Delete content" "dialogue"
|
||||
And I wait until the page is ready
|
||||
@@ -56,40 +44,38 @@ Feature: Delete H5P file from the content bank
|
||||
And the following "role assigns" exist:
|
||||
| user | role | contextlevel | reference |
|
||||
| manager | manager | System | |
|
||||
And I log out
|
||||
And I log in as "manager"
|
||||
And I follow "Manage private files..."
|
||||
And I upload "h5p/tests/fixtures/find-the-words.h5p" file to "Files" filemanager
|
||||
And I click on "Save changes" "button"
|
||||
When I click on "Site pages" "list_item" in the "Navigation" "block"
|
||||
And I click on "Content bank" "link" in the "Navigation" "block"
|
||||
And the following "user private file" exists:
|
||||
| user | manager |
|
||||
| filepath | h5p/tests/fixtures/find-the-words.h5p |
|
||||
When I am on the "Content bank" page logged in as "manager"
|
||||
And I should see "content2delete.h5p"
|
||||
And I follow "content2delete.h5p"
|
||||
And I wait "2" seconds
|
||||
And I click on "More" "button"
|
||||
Then I should not see "Delete"
|
||||
And I click on "Content bank" "link"
|
||||
And I am on the "Content bank" page
|
||||
And I click on "Upload" "link"
|
||||
And I click on "Choose a file..." "button"
|
||||
And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
|
||||
And I click on "find-the-words.h5p" "link"
|
||||
And I click on "Select this file" "button"
|
||||
And I click on "Save changes" "button"
|
||||
And I click on "More" "button"
|
||||
And I wait "2" seconds
|
||||
And I click on "More" "button"
|
||||
And I should see "Delete"
|
||||
|
||||
Scenario: The number of times a content is used is displayed before removing it
|
||||
Given I follow "Dashboard"
|
||||
And I follow "Manage private files..."
|
||||
Given I am on the "My private files" page
|
||||
And I click on "Add..." "button"
|
||||
And I select "Content bank" repository in file picker
|
||||
And I click on "content2delete.h5p" "file" in repository content area
|
||||
And I click on "Link to the file" "radio"
|
||||
And I click on "Select this file" "button"
|
||||
And I click on "Save changes" "button"
|
||||
When I click on "Site pages" "list_item" in the "Navigation" "block"
|
||||
And I click on "Content bank" "link" in the "Navigation" "block"
|
||||
And I am on the "Content bank" page
|
||||
And I follow "content2delete.h5p"
|
||||
And I click on "More" "button"
|
||||
And I wait "2" seconds
|
||||
And I click on "More" "button"
|
||||
And I click on "Delete" "link" in the ".cb-toolbar-container" "css_element"
|
||||
Then I should see "Are you sure you want to delete the content 'content2delete.h5p'"
|
||||
And I should see "The content will only be deleted from the content bank"
|
||||
|
||||
@@ -5,10 +5,7 @@ Feature: Content bank use editor feature
|
||||
I need to be able to access the edition options
|
||||
|
||||
Background:
|
||||
Given the following "blocks" exist:
|
||||
| blockname | contextlevel | reference | pagetypepattern | defaultregion |
|
||||
| private_files | System | 1 | my-index | side-post |
|
||||
And I log in as "admin"
|
||||
Given I log in as "admin"
|
||||
And I am on site homepage
|
||||
And I turn editing mode on
|
||||
And the following config values are set as admin:
|
||||
@@ -25,10 +22,9 @@ Feature: Content bank use editor feature
|
||||
Then the "[data-action=Add-content]" "css_element" should be disabled
|
||||
|
||||
Scenario: Users can see the Add button if there is content type available for creation
|
||||
Given I follow "Dashboard"
|
||||
And I follow "Manage private files..."
|
||||
And I upload "h5p/tests/fixtures/filltheblanks.h5p" file to "Files" filemanager
|
||||
And I click on "Save changes" "button"
|
||||
Given the following "user private file" exists:
|
||||
| user | admin |
|
||||
| filepath | h5p/tests/fixtures/filltheblanks.h5p |
|
||||
And I click on "Site pages" "list_item" in the "Navigation" "block"
|
||||
And I click on "Content bank" "link" in the "Navigation" "block"
|
||||
And I click on "Upload" "link"
|
||||
@@ -44,10 +40,9 @@ Feature: Content bank use editor feature
|
||||
And I should see "Fill in the Blanks"
|
||||
|
||||
Scenario: Users can edit content if they have the required permission
|
||||
Given I follow "Dashboard"
|
||||
And I follow "Manage private files..."
|
||||
And I upload "h5p/tests/fixtures/filltheblanks.h5p" file to "Files" filemanager
|
||||
And I click on "Save changes" "button"
|
||||
Given the following "user private file" exists:
|
||||
| user | admin |
|
||||
| filepath | h5p/tests/fixtures/filltheblanks.h5p |
|
||||
And I click on "Site pages" "list_item" in the "Navigation" "block"
|
||||
And I click on "Content bank" "link" in the "Navigation" "block"
|
||||
And I click on "Upload" "link"
|
||||
|
||||
@@ -1,30 +1,23 @@
|
||||
@core @core_contentbank @core_h5p @contentbank_h5p @_file_upload @_switch_iframe @javascript
|
||||
@core @core_contentbank @core_h5p @contentbank_h5p @_switch_iframe @javascript
|
||||
Feature: Confirm content bank events are triggered
|
||||
In order to log content bank actions
|
||||
As an admin
|
||||
I need to be able to check triggered events
|
||||
|
||||
Background:
|
||||
Given the following "blocks" exist:
|
||||
| blockname | contextlevel | reference | pagetypepattern | defaultregion |
|
||||
| private_files | System | 1 | my-index | side-post |
|
||||
And I log in as "admin"
|
||||
And I navigate to "H5P > Manage H5P content types" in site administration
|
||||
And I upload "h5p/tests/fixtures/filltheblanks.h5p" file to "H5P content type" filemanager
|
||||
And I click on "Upload H5P content types" "button" in the "#fitem_id_uploadlibraries" "css_element"
|
||||
And the following "courses" exist:
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname | category |
|
||||
| Course 1 | C1 | 0 |
|
||||
And the following "contentbank content" exist:
|
||||
| contextlevel | reference | contenttype | user | contentname | filepath |
|
||||
| Course | C1 | contenttype_h5p | admin | Existing | /h5p/tests/fixtures/filltheblanks.h5p |
|
||||
And I follow "Dashboard"
|
||||
And I follow "Manage private files..."
|
||||
And I upload "h5p/tests/fixtures/filltheblanks.h5p" file to "Files" filemanager
|
||||
And I click on "Save changes" "button"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
And the following "user private file" exists:
|
||||
| user | admin |
|
||||
| filepath | h5p/tests/fixtures/filltheblanks.h5p |
|
||||
And the following config values are set as admin:
|
||||
| unaddableblocks | | theme_boost|
|
||||
And I log in as "admin"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
And I add the "Navigation" block if not present
|
||||
|
||||
Scenario: Content created and uploaded events when uploading a content file
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
@core @core_contentbank @core_h5p @contentbank_h5p @_file_upload @javascript
|
||||
@core @core_contentbank @core_h5p @contentbank_h5p @javascript
|
||||
Feature: Store the content bank view preference
|
||||
In order to consistantly view the content bank in icons or details view
|
||||
As an admin
|
||||
I need to be able to store my view preference
|
||||
|
||||
Background:
|
||||
Given the following "blocks" exist:
|
||||
| blockname | contextlevel | reference | pagetypepattern | defaultregion |
|
||||
| private_files | System | 1 | my-index | side-post |
|
||||
Given the following "user private files" exist:
|
||||
| user | filepath |
|
||||
| admin | h5p/tests/fixtures/filltheblanks.h5p |
|
||||
| admin | h5p/tests/fixtures/greeting-card.h5p |
|
||||
And I log in as "admin"
|
||||
And I follow "Manage private files..."
|
||||
And I upload "h5p/tests/fixtures/filltheblanks.h5p" file to "Files" filemanager
|
||||
And I upload "h5p/tests/fixtures/greeting-card.h5p" file to "Files" filemanager
|
||||
And I click on "Save changes" "button"
|
||||
And I am on site homepage
|
||||
And I turn editing mode on
|
||||
And the following config values are set as admin:
|
||||
@@ -55,8 +52,7 @@ Feature: Store the content bank view preference
|
||||
And I should not see "Last modified"
|
||||
|
||||
Scenario: Display the number of times a content is used in file details view
|
||||
Given I follow "Dashboard"
|
||||
And I follow "Manage private files..."
|
||||
Given I am on the "My private files" page
|
||||
And I click on "Add..." "button"
|
||||
And I select "Content bank" repository in file picker
|
||||
And I click on "fib.h5p" "file" in repository content area
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user