Compare commits
331 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8c989335be | |||
| d4937c4527 | |||
| 62e7dc94d9 | |||
| cf40395830 | |||
| ab62e5f23f | |||
| a29056941b | |||
| 78db6582ba | |||
| b1ccbfc24d | |||
| 8c9545ca78 | |||
| 5b48a61484 | |||
| ef3eb09347 | |||
| abff2a6570 | |||
| 15afb64771 | |||
| c8631653cd | |||
| 30add0c159 | |||
| 2fbb766149 | |||
| 3fa41951f3 | |||
| 3d02f36f87 | |||
| dcd20bec34 | |||
| fec48e90ac | |||
| 1a469cda51 | |||
| f98b725610 | |||
| cc9f2527a8 | |||
| 8e5f9ca9d4 | |||
| d247ea08e9 | |||
| eab902354f | |||
| d3e4957556 | |||
| 40b68fd88f | |||
| c180b1babd | |||
| a1e281a31d | |||
| ab12596a7b | |||
| fd5585777f | |||
| 5132a27822 | |||
| 9f6fbc11dd | |||
| a801a741b9 | |||
| 685be3d41b | |||
| 0e504f73ee | |||
| 7dfcc7e3f5 | |||
| 35500db40c | |||
| cfb5b76ba7 | |||
| ff68353e39 | |||
| 0f410c087a | |||
| e160cdd8a7 | |||
| d25ad8aaf9 | |||
| c938c41de8 | |||
| 04996f2cf8 | |||
| 5481c31fe6 | |||
| 79a9167a1e | |||
| 4d035d0f80 | |||
| cd885856f3 | |||
| 5f67d77aae | |||
| d59a10108c | |||
| b4cf5c50bf | |||
| 71440dc236 | |||
| 75264720ac | |||
| 735c2c6b47 | |||
| 319d3954f8 | |||
| 0fc6e59121 | |||
| ace6e8db3d | |||
| 2970f0f433 | |||
| 4899d8c11a | |||
| 20302d4f86 | |||
| d1053fe035 | |||
| 5d361ee332 | |||
| a43b2b2998 | |||
| cf46e93304 | |||
| bb7b0f996d | |||
| 9c5e63cf17 | |||
| 5d89ce0058 | |||
| 6143ad4b1b | |||
| d54edef0f7 | |||
| 02d2e80bae | |||
| 5cf5134a12 | |||
| 5112c89929 | |||
| fab3b2ccbc | |||
| 56f75d7aea | |||
| 2f10bf1a1f | |||
| 0384f8dfa0 | |||
| e05576d74c | |||
| 01b577ee65 | |||
| 0f5a1a61f7 | |||
| b4479d7981 | |||
| 2a2badacb2 | |||
| eb0585ea72 | |||
| 7faf41dce5 | |||
| 6aa360ae83 | |||
| 804e8b62da | |||
| 1092a998ae | |||
| 027e3c6660 | |||
| 2b407d891f | |||
| 1b284001ac | |||
| b3350a0ea7 | |||
| 0ffed15ee4 | |||
| d20cf16533 | |||
| 3e13847324 | |||
| 24f646038c | |||
| 928760f865 | |||
| 9a3f097da5 | |||
| 1198d57f71 | |||
| 6b8884e13b | |||
| 7edbca66f9 | |||
| e0c8854480 | |||
| 7d5b30290f | |||
| 3e2bf77656 | |||
| 15b0cd25e6 | |||
| e99fcecdf9 | |||
| 5feb510035 | |||
| cae92e5a92 | |||
| 1342f28224 | |||
| b0b214bbf2 | |||
| 1ff32127d4 | |||
| a443a29b79 | |||
| f92c748a2d | |||
| af0f37405f | |||
| fb71a7a8c7 | |||
| b10afcdd4c | |||
| 93c35a89bb | |||
| 43fdf37a96 | |||
| b490a0ae08 | |||
| 80410a354b | |||
| b1b0c2d59b | |||
| aa7aec1088 | |||
| 7bae6c8ca6 | |||
| 7a5806d3cb | |||
| 279cc50e5c | |||
| dc4ff6a2f5 | |||
| fb343534d0 | |||
| b4935649b3 | |||
| 0d426e571a | |||
| b938b53096 | |||
| 525b69dc69 | |||
| 5630f86d81 | |||
| 30ac87781a | |||
| 2b9a9286d0 | |||
| 2e6d8b72b1 | |||
| 2724eb6041 | |||
| df8413ade0 | |||
| 414f0df046 | |||
| 29a7e00f9c | |||
| 81b9a51fb7 | |||
| 03c461ab87 | |||
| c2fbb10a52 | |||
| 4781b41631 | |||
| caf4da762c | |||
| 0ab5bf01f0 | |||
| 078782e656 | |||
| 2b6456c1d0 | |||
| a37ce627a8 | |||
| 26b885edb6 | |||
| b514f0bb1d | |||
| 12dc12c954 | |||
| ec7b3cab86 | |||
| 6808c8f3b2 | |||
| 109a0ffeb9 | |||
| 4a04cda71b | |||
| a0920d8b8d | |||
| 11bf54289f | |||
| 666708b561 | |||
| a1162e3943 | |||
| 9de9651809 | |||
| dcb3ce31ce | |||
| 8b641a1b0d | |||
| 8fa53a336e | |||
| 5a22258f42 | |||
| 92cba2c5ba | |||
| 7d8a40fe3e | |||
| 2de2d6bc30 | |||
| beb55edffc | |||
| ae8466162d | |||
| b5f98e022d | |||
| b9d21ee2ad | |||
| 04fb9beb20 | |||
| 3be559b763 | |||
| 88a1d4d4c2 | |||
| e0045688b3 | |||
| c645df564a | |||
| 8bac8a2da7 | |||
| 2e339446d8 | |||
| 1c9379ab09 | |||
| 24ad6848aa | |||
| da43ea4ef7 | |||
| f49f24c516 | |||
| 473cfc369d | |||
| 0ba9466281 | |||
| 9e6f6c0e7e | |||
| b87a75110b | |||
| 52bcdf9e06 | |||
| f113d4c116 | |||
| 16bac432fd | |||
| 4e28613a16 | |||
| 1df40c823e | |||
| 5dfec00bb1 | |||
| c00c6dd4cf | |||
| 0f687a9028 | |||
| 1d2b0ade40 | |||
| a5f2c1d591 | |||
| 5e59ad2b6e | |||
| 441003231f | |||
| 54c8336373 | |||
| e3ac233307 | |||
| 4d263b0505 | |||
| 25953f9c7f | |||
| b112cee58c | |||
| bc7dacecb6 | |||
| d1fcfd1de2 | |||
| 32bc50a135 | |||
| 02e893a575 | |||
| 59c75c928d | |||
| c2a74815c0 | |||
| 9ac38f5db6 | |||
| 04b35cb3db | |||
| c6cc0449ed | |||
| 30f1f0778f | |||
| f9edf5c1be | |||
| c4f7a44824 | |||
| 26dbb8466a | |||
| 2fb84af337 | |||
| 717c74a448 | |||
| 4a2e621012 | |||
| 732dc4f334 | |||
| c348119685 | |||
| ced8835b5f | |||
| a5cf13cd90 | |||
| 10f9d0d51a | |||
| 9bdfec812d | |||
| 78d90e3c16 | |||
| d5ec3594cc | |||
| 5303c032bb | |||
| b2cca9ef62 | |||
| 87736d9a0b | |||
| b1496fa5df | |||
| 411dc66fd9 | |||
| c58d3b4759 | |||
| 39a930f762 | |||
| 3493d9ebc3 | |||
| eff51c6001 | |||
| 7365bc38b2 | |||
| 856803faf8 | |||
| 97d935b337 | |||
| f46e5fc229 | |||
| 8a6c536b29 | |||
| 84da51dafd | |||
| 98448477d9 | |||
| 62076a191e | |||
| b1a7ae63c3 | |||
| 929ad63114 | |||
| 29a21ee986 | |||
| c721cc6803 | |||
| 2ccc6a2346 | |||
| cd3643a149 | |||
| 2138619b01 | |||
| 97f6672420 | |||
| f6afe82978 | |||
| 16f91dfdb3 | |||
| da46551bea | |||
| 861ce57a8a | |||
| 048070e25f | |||
| 223b1afabf | |||
| 92a57c95be | |||
| 680644b593 | |||
| bdaed5ec3c | |||
| 812219fcb2 | |||
| 514865575c | |||
| 1b1c1a56b1 | |||
| 33f62c3309 | |||
| 0f66c8fe5c | |||
| a56ca931aa | |||
| 453ea97395 | |||
| 4e9be8c131 | |||
| a7055da32d | |||
| bd85500e2a | |||
| e0e94dbf84 | |||
| 9de24c27c6 | |||
| 89215b79fb | |||
| 4065904ad1 | |||
| 3862ec015d | |||
| c516a6fc14 | |||
| fb14c67dec | |||
| b297347f12 | |||
| b89351f084 | |||
| b78a8983ed | |||
| 84ddb8c655 | |||
| cc3828c85a | |||
| 7af8bddad7 | |||
| 3bdfc594b6 | |||
| c6ff4c551e | |||
| b6531bf613 | |||
| 7c4fc1725f | |||
| 4f625c1e2e | |||
| 99e38b7118 | |||
| 5650cab0d6 | |||
| 4b23b98e16 | |||
| c7595a7169 | |||
| b97ffab51a | |||
| f1633ba0f8 | |||
| 3f4b46b16a | |||
| 32df1530ff | |||
| 20e1d18c29 | |||
| 01fd68f06d | |||
| a8a3855c6d | |||
| 950aa78611 | |||
| b2e36b6baa | |||
| 85662b721b | |||
| eb6c618fd4 | |||
| d43dc80d65 | |||
| 8e1583455e | |||
| 4bfce15818 | |||
| 6b8aeb5c90 | |||
| d9f71f0666 | |||
| 9e0d12e5ae | |||
| 86fd45f0de | |||
| ffd32bacba | |||
| 3ee04ecf78 | |||
| c675170c4e | |||
| 0c74bbfa06 | |||
| 4f7ccd0432 | |||
| 4806324231 | |||
| 9c779fefc7 | |||
| b82cc59782 | |||
| e38a933fa2 | |||
| 9ef5ed8527 | |||
| f1afcf6616 | |||
| 0a05336551 | |||
| a9c5734f04 | |||
| 1e81424bdd | |||
| 4aaaaa5f57 | |||
| 963b56cf7e | |||
| f3368a55b5 | |||
| 37564d61d5 | |||
| 94a87bc47e | |||
| e6615e587f |
@@ -1,12 +1,8 @@
|
||||
{
|
||||
'plugins': [
|
||||
'@babel',
|
||||
'babel',
|
||||
'promise',
|
||||
'jsdoc'
|
||||
],
|
||||
'extends': [
|
||||
'eslint:recommended',
|
||||
'plugin:promise/recommended'
|
||||
'jsdoc',
|
||||
],
|
||||
'env': {
|
||||
'browser': true,
|
||||
@@ -20,19 +16,42 @@
|
||||
'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)
|
||||
@@ -44,12 +63,15 @@
|
||||
'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',
|
||||
@@ -61,22 +83,28 @@
|
||||
'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' }],
|
||||
|
||||
@@ -109,6 +137,8 @@
|
||||
'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',
|
||||
@@ -116,6 +146,7 @@
|
||||
'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',
|
||||
@@ -137,6 +168,7 @@
|
||||
'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',
|
||||
@@ -149,19 +181,21 @@
|
||||
'unicode-bom': 'error',
|
||||
'wrap-regex': 'off',
|
||||
|
||||
// === Promises (override plugin:promise/recommended) ===
|
||||
// === Promises ===
|
||||
'promise/always-return': 'warn',
|
||||
'promise/no-return-wrap': 'warn',
|
||||
'promise/param-names': 'warn',
|
||||
'promise/catch-or-return': ['warn', {terminationMethod: ['catch', 'fail', 'always'], allowFinally: true}],
|
||||
'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 "core/str" module or M.util.get_string()'
|
||||
'message': 'Use AMD module "core/str" or M.util.get_string()'
|
||||
}],
|
||||
},
|
||||
overrides: [
|
||||
@@ -201,6 +235,7 @@
|
||||
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',
|
||||
@@ -212,37 +247,65 @@
|
||||
'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',
|
||||
'@babel/semi': 'error',
|
||||
'@babel/no-unused-expressions': 'error',
|
||||
'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',
|
||||
// === 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,
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
**/yui/build/** -diff
|
||||
**/amd/build/** -diff
|
||||
lib/dml/oci_native_moodle_package.sql text eol=lf
|
||||
**.js.map -diff
|
||||
**-min.js -diff
|
||||
**.min.js -diff
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
# Primary donations pages.
|
||||
custom: ["https://moodle.com/donations/", "https://fund.moodle.org"]
|
||||
custom: ["https://moodle.com/donations/", moodle.org]
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
*** PLEASE DO NOT OPEN PULL REQUESTS VIA GITHUB ***
|
||||
|
||||
The moodle.git repository at Github is just a mirror of the official repository. We do not accept pull requests at Github.
|
||||
|
||||
See CONTRIBUTING.md guidelines for how to contribute patches for Moodle. Thank you.
|
||||
|
||||
--
|
||||
@@ -1,6 +0,0 @@
|
||||
> [!IMPORTANT]
|
||||
> PLEASE DO NOT PUBLISH SECURITY FINDINGS PUBLICLY.
|
||||
|
||||
If you discover a potential security issue, please report it via our [Security Submission Form](https://moodle.org/security/report).
|
||||
|
||||
For more information about our security processes and responsible disclosure policy, see the [Security Procedures documentation](https://moodledev.io/general/development/process/security).
|
||||
@@ -1 +0,0 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1231.24 314.44" preserveAspectRatio="xMinYMid meet"><defs><style>.cls-1{fill:#f48020;}.cls-2,.cls-3{fill:#383738;stroke:#4a4a4c;}.cls-2{stroke-width:2.37px;}.cls-3{stroke-width:2.38px;}.cls-4{fill:#585858;}</style></defs><title>Moodle_Logo_UseonBlack_RGB</title><path class="cls-1" d="M1086.3,235.34a53.42,53.42,0,0,0,16.71,29q13.26,12.27,29.48,12.27,11.29,0,24.56-5.89,16.23-6.88,16.22-16.22l58-.49q-12.29,29-41.76,45.2-27.52,15.23-60.43,15.24-42.25,0-70-26.29t-27.75-68.54q0-43.73,27.75-69.51t72-25.8q48.15,0,75.17,31.94,25.07,29.47,25.06,79.1Zm89.91-33.91q-3.43-17.69-14.73-29-12.8-12.28-29.49-12.29-17.21,0-28.74,11.79t-15.47,29.48Z"/><path class="cls-1" d="M965.45,308.54V0h55V308.54Z"/><path class="cls-1" d="M892.69,308.54V290.36q-7.38,9.83-25.06,15.72a92.31,92.31,0,0,1-30,5.41q-39.31,0-63.13-27T750.7,217.65q0-39.3,23.34-65.84t62.15-26.53q43.71,0,56.5,23.58V0h53.55V308.54Zm0-102.68q0-14.75-14-28.26t-28.74-13.51q-21.12,0-33.41,17.2-10.8,15.24-10.8,37.33,0,21.63,10.8,36.85,12.29,17.71,33.41,17.69,12.78,0,27.76-12t15-24.81Z"/><path class="cls-1" d="M647,313.45q-41.77,0-69-26.53t-27.27-68.3q0-41.73,27.27-68.28t69-26.53q41.75,0,69.27,26.53t27.52,68.28q0,41.79-27.52,68.3T647,313.45ZM647,168q-19.89,0-30.58,15.07t-10.68,35.79q0,20.73,9.94,35.05,11.43,16.31,31.32,16.28t31.32-16.28q10.44-14.31,10.44-35.05t-9.94-35Q667.42,168,647,168Z"/><path class="cls-1" d="M448.07,313.45q-41.76,0-69-26.53t-27.27-68.3q0-41.73,27.27-68.28t69-26.53q41.74,0,69.28,26.53t27.5,68.28q0,41.79-27.5,68.3T448.07,313.45Zm0-145.43q-19.89,0-30.59,15.07t-10.69,35.79q0,20.73,9.94,35.05,11.43,16.31,31.34,16.28t31.32-16.28q10.42-14.31,10.44-35.05t-9.94-35Q468.45,168,448.07,168Z"/><path class="cls-1" d="M288.89,309.11V201q0-33.9-28-33.9t-28,33.9V309.11h-55V201q0-33.9-27.52-33.9-28,0-28,33.9V309.11h-55V194.65q0-35.37,24.56-53.54,21.61-16.22,58.46-16.22,37.35,0,55,19.16,15.23-19.16,55.51-19.16,36.86,0,58.45,16.22,24.57,18.16,24.57,53.54V309.11Z"/><path class="cls-2" d="M.76,145.76"/><path class="cls-3" d="M1.73,145.32"/><path class="cls-4" d="M173.86,116.85l55.08-40.1-.7-2.44C135,83.92,80.42,96.44,0,144.13l.73,2.38,21-.67c15.17.52,30.85,1.26,46,1.32-1,24.09.83,32.65.83,32.65,51.92,17.58,101.22,1.53,127.14-43.9A248.72,248.72,0,0,0,173.86,116.85Z"/><path class="cls-4" d="M113.78,110c-.3-1.08-2.23-1.48-4.28-.9a4.73,4.73,0,0,0-3.14,2.19c-18.53,5-49.16,13.72-70.73,21l-1.33.45c-16.68,5.61-24.73,8.49-25.22,12.55-2.29,18.44-.87,42.18-.86,42.48l.19,6.4c-9.74,27.56.06,49.6,8.38,69.77.06-24.92,1.07-45.22-6-71-.2-3.82-1.49-30.36.61-47.34.34-2.75,11.65-6.56,23.63-10.59l1.33-.44c22-7.42,53.57-16.39,71.9-21.29a6.75,6.75,0,0,0,2.29-.25C112.64,112.43,114.08,111.09,113.78,110Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 2.7 KiB |
@@ -1,31 +0,0 @@
|
||||
name: 'Close Pull Requests'
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: opened
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
close:
|
||||
# Avoid forks to run this job
|
||||
if: github.repository_owner == 'moodle'
|
||||
name: Close Pull Requests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/repo-lockdown@v4
|
||||
with:
|
||||
process-only: prs
|
||||
close-pr: true
|
||||
skip-closed-pr-comment: true
|
||||
pr-comment: >
|
||||
**Please, don't open pull requests via GitHub.**
|
||||
|
||||
|
||||
This repository (moodle.git) at GitHub is just a mirror of the official Moodle repository.
|
||||
|
||||
|
||||
For how to contribute patches for Moodle see
|
||||
[CONTRIBUTING.md](https://github.com/moodle/moodle/blob/main/CONTRIBUTING.md).
|
||||
|
||||
|
||||
Closing this pull request.
|
||||
@@ -1,143 +0,0 @@
|
||||
name: One by One Testing
|
||||
# Run all the individual unit tests one by one, with
|
||||
# fully independent PHPUnit executions. Useful to
|
||||
# detect issues with some tests that are using stuff
|
||||
# that has been made available by others, but is not
|
||||
# available when running individually.
|
||||
#
|
||||
# Note that we aren't using PHPUnit's own isolation
|
||||
# here but completely separated runs, one for each
|
||||
# test.
|
||||
#
|
||||
# The workflow will fail reporting all the tests
|
||||
# that have failed (and will pass if no failure is
|
||||
# detected, of course).
|
||||
#
|
||||
# It's only executed via workflow dispatch (automated
|
||||
# or manual), not by push/tag. And acceptd configuration
|
||||
# of phpunit, specially useful to run it with PHPUnit's
|
||||
# own isolation or any other option.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
phpunit_extra_options:
|
||||
description: Additional options to apply to PHPUnit
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
env:
|
||||
chunks: 7
|
||||
|
||||
jobs:
|
||||
collect:
|
||||
name: Collect individual unit tests
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{steps.individual-tests.outputs.matrix }}
|
||||
|
||||
steps:
|
||||
- name: Checking out code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Looking for all individual tests
|
||||
id: individual-tests
|
||||
run: |
|
||||
count=0 # Number of individual tests found.
|
||||
while read -r testfile; do # For each test file.
|
||||
while read -r testname; do # For each unit test in a file.
|
||||
count=$((count + 1))
|
||||
# Sent it to the correct chunk file.
|
||||
chunk=$(((($count % $chunks)) + 1))
|
||||
echo "$testname $testfile" >> ./chunk_$chunk.txt
|
||||
done < <(grep "function test_" "${testfile}" | sed -r "s/^.*function (test_[a-zA-Z0-9_]+).*/\1/")
|
||||
done < <(find . -name "*_test.php" -not -path "*/fixtures/*")
|
||||
# Generate the matrix to run tests.
|
||||
echo "matrix=$(ls -1 chunk_*.txt | jq -R -s -c 'split("\n")[:-1]')" >> $GITHUB_OUTPUT
|
||||
echo "$count individual tests collected in $chunks files"
|
||||
|
||||
- name: Upload individual tests files
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: individual_tests
|
||||
path: chunk_*.txt
|
||||
retention-days: 1
|
||||
|
||||
test:
|
||||
name: Run tests
|
||||
needs: collect
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
exttests:
|
||||
image: moodlehq/moodle-exttests
|
||||
ports:
|
||||
- 8080:80
|
||||
redis:
|
||||
image: redis
|
||||
ports:
|
||||
- 6379:6379
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
file: ${{ fromJson(needs.collect.outputs.matrix) }}
|
||||
|
||||
steps:
|
||||
- name: Setting up DB pgsql
|
||||
uses: m4nu56/postgresql-action@v1
|
||||
with:
|
||||
postgresql version: 13
|
||||
postgresql db: test
|
||||
postgresql user: test
|
||||
postgresql password: test
|
||||
|
||||
- name: Setting up PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: 8.3
|
||||
ini-values: max_input_vars=5000
|
||||
coverage: none
|
||||
|
||||
- name: Checking out code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download individual test files
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: individual_tests # Make all the chunk files available for the next steps.
|
||||
|
||||
- name: Setting up PHPUnit
|
||||
env:
|
||||
dbtype: pgsql
|
||||
run: |
|
||||
echo "pathtophp=$(which php)" >> $GITHUB_ENV
|
||||
cp .github/workflows/config-template.php config.php
|
||||
mkdir ../moodledata
|
||||
sudo locale-gen en_AU.UTF-8
|
||||
php admin/tool/phpunit/cli/init.php --no-composer-self-update
|
||||
|
||||
- name: Run PHPUnit test (one by one)
|
||||
env:
|
||||
dbtype: pgsql
|
||||
run: |
|
||||
status=0
|
||||
count=0
|
||||
while read -r line; do # For each line in the chunk file
|
||||
count=$((count + 1))
|
||||
filter="${line% *}"
|
||||
file="${line#* }"
|
||||
# Run the individual unit test and report problems if needed to.
|
||||
if ! php vendor/bin/phpunit \
|
||||
--fail-on-empty-test-suite \
|
||||
--fail-on-warning \
|
||||
--fail-on-risky \
|
||||
--filter "$filter" ${{ inputs.phpunit_extra_options }} \
|
||||
"$file" >/dev/null 2>&1; then
|
||||
if [ $status -eq 0 ]; then
|
||||
echo "Problems found, list of PHPUnit commands failing:"
|
||||
fi
|
||||
echo "vendor/bin/phpunit --filter '${filter}' ${{ inputs.phpunit_extra_options }} $file"
|
||||
status=$((status + 1))
|
||||
fi
|
||||
done < ${{ matrix.file }}
|
||||
echo "Finished: $count individual tests executed, $status tests failed"
|
||||
exit $status
|
||||
+11
-20
@@ -3,30 +3,24 @@ name: Core
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- main
|
||||
- master
|
||||
- MOODLE_[0-9]+_STABLE
|
||||
tags-ignore:
|
||||
- v[0-9]+.[0-9]+.[0-9]+*
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
phpunit_extra_options:
|
||||
description: Additional options to apply to PHPUnit
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
env:
|
||||
php: 8.3
|
||||
php: 8.1
|
||||
|
||||
jobs:
|
||||
Grunt:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- name: Checking out code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Configuring node & npm
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
@@ -61,13 +55,13 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
# MySQL builds always run with the lowest PHP supported version.
|
||||
- os: ubuntu-24.04
|
||||
php: 8.1
|
||||
- os: ubuntu-22.04
|
||||
php: 8.0
|
||||
extensions:
|
||||
db: mysqli
|
||||
# PostgreSQL builds always run with the highest PHP supported version.
|
||||
- os: ubuntu-24.04
|
||||
php: 8.3
|
||||
- os: ubuntu-22.04
|
||||
php: 8.1
|
||||
db: pgsql
|
||||
|
||||
steps:
|
||||
@@ -105,7 +99,7 @@ jobs:
|
||||
coverage: none
|
||||
|
||||
- name: Checking out code from ${{ env.GITHUB_REF_SLUG }}
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setting up PHPUnit
|
||||
env:
|
||||
@@ -121,7 +115,4 @@ jobs:
|
||||
env:
|
||||
dbtype: ${{ matrix.db }}
|
||||
phpunit_options: ${{ secrets.phpunit_options }}
|
||||
run: vendor/bin/phpunit $phpunit_options ${{ inputs.phpunit_extra_options }}
|
||||
|
||||
- name: Git status
|
||||
run: git status
|
||||
run: vendor/bin/phpunit $phpunit_options
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
name: Web Installer Testing
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
InstallationTest:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
php: [8.1, 8.3]
|
||||
steps:
|
||||
- name: Construct repository URL
|
||||
run: echo "REPOSITORY_URL=${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
|
||||
- uses: moodlehq/moodle-webinstaller-test@main
|
||||
with:
|
||||
repository: ${{ env.REPOSITORY_URL }}
|
||||
branch: ${{ github.ref_name }}
|
||||
php: ${{ matrix.php }}
|
||||
@@ -1,14 +1,9 @@
|
||||
name: Windows Testing
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
phpunit_extra_options:
|
||||
description: Additional options to apply to PHPUnit
|
||||
required: false
|
||||
default: ''
|
||||
workflow_dispatch
|
||||
env:
|
||||
php: 8.3
|
||||
php: 8.1
|
||||
|
||||
jobs:
|
||||
Grunt:
|
||||
@@ -21,10 +16,10 @@ jobs:
|
||||
git config --global core.eol lf
|
||||
|
||||
- name: Checking out code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Configuring node & npm
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
@@ -51,15 +46,15 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- os: windows-latest
|
||||
php: 8.3
|
||||
php: 8.1
|
||||
# Ideally we should use mysql/mariadb, but they are 4x slower without tweaks and configuration
|
||||
# so let's run only postgres (1.5h vs 6h) only, If some day we want to improve the mysql runs,
|
||||
# this is the place to enable them.
|
||||
# db: mysqli
|
||||
db: pgsql
|
||||
extensions: exif, fileinfo, gd, intl, pgsql, mysql, redis, soap, sodium, zip
|
||||
extensions: exif, fileinfo, gd, intl, pgsql, mysql, redis, soap, sodium
|
||||
- os: windows-latest
|
||||
php: 8.1
|
||||
php: 8.0
|
||||
db: pgsql
|
||||
extensions: exif, fileinfo, gd, intl, pgsql, mysql, redis, soap, sodium
|
||||
|
||||
@@ -117,7 +112,7 @@ jobs:
|
||||
git config --global core.eol lf
|
||||
|
||||
- name: Checking out code from ${{ env.GITHUB_REF_SLUG }}
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Needs to be done after php is available, git configured and Moodle checkout has happened.
|
||||
- name: Setting up moodle-exttests service
|
||||
@@ -128,7 +123,7 @@ jobs:
|
||||
|
||||
- name: Setting up redis service
|
||||
run: |
|
||||
choco install redis --version 5.0.7 --no-progress
|
||||
choco install redis-64 --version 3.0.503 --no-progress
|
||||
nssm install redis redis-server
|
||||
nssm start redis
|
||||
|
||||
@@ -146,4 +141,4 @@ jobs:
|
||||
env:
|
||||
dbtype: ${{ matrix.db }}
|
||||
phpunit_options: ${{ secrets.phpunit_options }}
|
||||
run: vendor/bin/phpunit $phpunit_options ${{ inputs.phpunit_extra_options }}
|
||||
run: vendor/bin/phpunit $phpunit_options
|
||||
|
||||
@@ -26,9 +26,6 @@
|
||||
/.patches/
|
||||
/.idea/
|
||||
.phpstorm.*
|
||||
!/.phpstorm.meta.php/
|
||||
/.phpstorm.meta.php/*
|
||||
!/.phpstorm.meta.php/di.php
|
||||
/nbproject/
|
||||
CVS
|
||||
.DS_Store
|
||||
@@ -58,4 +55,3 @@ moodle-plugin-ci.phar
|
||||
.hugo_build.lock
|
||||
phpcs.xml
|
||||
jsconfig.json
|
||||
UPGRADING-CURRENT.md
|
||||
|
||||
+2
-27
@@ -40,20 +40,9 @@ const fetchComponentData = () => {
|
||||
if (!Object.entries(componentData).length) {
|
||||
componentData.subsystems = {};
|
||||
componentData.pathList = [];
|
||||
componentData.components = {};
|
||||
componentData.standardComponents = {};
|
||||
|
||||
// Fetch the component definiitions from the distributed JSON file.
|
||||
const components = JSON.parse(fs.readFileSync(`${gruntFilePath}/lib/components.json`));
|
||||
const pluginData = JSON.parse(fs.readFileSync(`${gruntFilePath}/lib/plugins.json`));
|
||||
|
||||
componentData.pluginTypes = components.plugintypes;
|
||||
|
||||
const standardPlugins = Object.entries(pluginData.standard).map(
|
||||
([pluginType, pluginNames]) => {
|
||||
return pluginNames.map(pluginName => `${pluginType}_${pluginName}`);
|
||||
}
|
||||
).reduce((acc, val) => acc.concat(val), []);
|
||||
|
||||
// Build the list of moodle subsystems.
|
||||
componentData.subsystems.lib = 'core';
|
||||
@@ -66,8 +55,8 @@ const fetchComponentData = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// The list of components includes the list of subsystems.
|
||||
componentData.components = {...componentData.subsystems};
|
||||
// The list of components incldues the list of subsystems.
|
||||
componentData.components = componentData.subsystems;
|
||||
|
||||
// Go through each of the plugintypes.
|
||||
Object.entries(components.plugintypes).forEach(([pluginType, pluginTypePath]) => {
|
||||
@@ -98,20 +87,6 @@ const fetchComponentData = () => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Create a list of the standard subsystem and plugins.
|
||||
componentData.standardComponents = Object.fromEntries(
|
||||
Object.entries(componentData.components).filter(([, name]) => {
|
||||
if (name === 'core' || name.startsWith('core_')) {
|
||||
return true;
|
||||
}
|
||||
return standardPlugins.indexOf(name) !== -1;
|
||||
})
|
||||
);
|
||||
|
||||
componentData.componentMapping = Object.fromEntries(
|
||||
Object.entries(componentData.components).map(([path, name]) => [name, path])
|
||||
);
|
||||
}
|
||||
|
||||
return componentData;
|
||||
|
||||
@@ -16,4 +16,4 @@ Moodle - the world's open source learning platform
|
||||
This generated documentation includes API documentation for JavaScript written in the AMD and ES2015 module formats within Moodle.
|
||||
|
||||
## Related information
|
||||
See [https://moodledev.io](https://moodledev.io) for other related Developer Documentation.
|
||||
See [https://docs.moodle.org/dev](https://docs.moodle.org/dev) for other related Developer Documentation.
|
||||
|
||||
@@ -114,7 +114,7 @@ module.exports = {
|
||||
],
|
||||
"menu": {
|
||||
"Developer Docs": {
|
||||
href: "https://moodledev.io",
|
||||
href: "https://docs.moodle.org/dev",
|
||||
target: "_blank",
|
||||
"class": "menu-item",
|
||||
id: "devdocs"
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
// 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/>.
|
||||
|
||||
import * as Components from '../../components.js';
|
||||
|
||||
const componentData = Components.fetchComponentData();
|
||||
|
||||
/**
|
||||
* The standard components shipped with core Moodle.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
export const standardComponents = componentData.standardComponents;
|
||||
|
||||
/**
|
||||
* All components of the current Moodle instance.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
export const allComponents = componentData.components;
|
||||
|
||||
/**
|
||||
* Get all components of the current Moodle instance.
|
||||
*
|
||||
* @returns {Array}
|
||||
*/
|
||||
export const getAllComponents = () => {
|
||||
let components = new Map(Object.entries(componentData.pluginTypes).map(([value, path]) => ([path,{
|
||||
path,
|
||||
value,
|
||||
name: `${value} (plugin type)`,
|
||||
}])));
|
||||
|
||||
Object
|
||||
.entries(componentData.components)
|
||||
.filter(([path, value]) => Object.values(componentData.standardComponents).includes(value))
|
||||
.forEach(([path, value]) => {
|
||||
const entry = {
|
||||
path,
|
||||
value,
|
||||
name: value,
|
||||
};
|
||||
if (Object.values(componentData.subsystems).includes(value)) {
|
||||
if (components.has(path)) {
|
||||
entry.name = `${value} (subsystem / plugintype)`;
|
||||
} else {
|
||||
entry.name = `${value} (subsystem)`;
|
||||
}
|
||||
}
|
||||
|
||||
components.set(path, entry);
|
||||
});
|
||||
|
||||
return Array.from(components.values());
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether the specified component is a standard component shipped with core Moodle.
|
||||
*
|
||||
* @param {string} componentName
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const isStandardComponent = (componentName) => {
|
||||
if (Object.values(componentData.standardComponents).includes(componentName)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Object.keys(componentData.pluginTypes).includes(componentName)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
export const rewritePlugintypeAsSubsystem = (componentName) => {
|
||||
if (Object.keys(componentData.pluginTypes).includes(componentName)) {
|
||||
const pluginTypePath = componentData.pluginTypes[componentName];
|
||||
if (Object.keys(componentData.subsystems).includes(pluginTypePath)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether the specified component is a community component.
|
||||
*
|
||||
* @param {string} componentName
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const isCommunityComponent = (componentName) => {
|
||||
if (isStandardComponent(componentName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Object.values(componentData.components).indexOf(componentName) !== -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sort method for components.
|
||||
*
|
||||
* This method sorts components putting `core` first, followed by core subsystems, then everything else.
|
||||
*
|
||||
* @param {String} a
|
||||
* @param {String} b
|
||||
* @returns {Number}
|
||||
*/
|
||||
export const sortComponents = (a, b) => {
|
||||
// Always put 'core' first.
|
||||
if (a === 'core') {
|
||||
return -1;
|
||||
} else if (b === 'core') {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Put core subsystems next.
|
||||
if (a.startsWith('core_') && !b.startsWith('core_')) {
|
||||
return -1;
|
||||
} else if (b.startsWith('core_') && !a.startsWith('core_')) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Sort alphabetically for everything else.
|
||||
return a.localeCompare(b);
|
||||
};
|
||||
@@ -1,85 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
// 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/>.
|
||||
|
||||
import inquirer from 'inquirer';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { createNote } from './note.mjs';
|
||||
import { getInitialValues } from './helpers.mjs';
|
||||
import * as Prompts from './prompts.mjs';
|
||||
import logger from './logger.mjs';
|
||||
|
||||
export default async (options) => {
|
||||
// Processs the initial values.
|
||||
const initialValues = getInitialValues(options);
|
||||
|
||||
// Fetch information.
|
||||
const messages = [];
|
||||
const { issueNumber } = await inquirer.prompt([
|
||||
Prompts.getIssuePrompt(),
|
||||
], initialValues);
|
||||
|
||||
let selection = {};
|
||||
let notePath;
|
||||
do {
|
||||
selection = {};
|
||||
selection = await inquirer.prompt([
|
||||
Prompts.getComponentsPrompt(),
|
||||
Prompts.getTypePrompt(),
|
||||
Prompts.getMessagePromptInput(),
|
||||
], initialValues);
|
||||
if (selection.message === '') {
|
||||
selection = Object.assign(
|
||||
selection,
|
||||
await inquirer.prompt([
|
||||
Prompts.getMessagePromptEditor(),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
logger.info(`
|
||||
Creating upgrade note with the following options:
|
||||
|
||||
- Issue: ${chalk.bold(issueNumber)}
|
||||
- Component: ${chalk.bold(selection.components)}
|
||||
- Type: ${chalk.bold(selection.type)}
|
||||
- Message:
|
||||
${chalk.bold(selection.message)}
|
||||
`);
|
||||
|
||||
messages.push({
|
||||
components: [selection.components],
|
||||
type: selection.type,
|
||||
message: selection.message,
|
||||
});
|
||||
|
||||
// Save the note so far.
|
||||
if (notePath) {
|
||||
await createNote(issueNumber, messages, notePath);
|
||||
logger.info(`Updated note at: ${chalk.underline(chalk.bold(notePath))}`);
|
||||
} else {
|
||||
notePath = await createNote(issueNumber, messages);
|
||||
logger.info(`Note created at: ${chalk.underline(chalk.bold(notePath))}`);
|
||||
}
|
||||
|
||||
selection = Object.assign(
|
||||
selection,
|
||||
await inquirer.prompt([
|
||||
Prompts.getAddAnotherPrompt(),
|
||||
], initialValues),
|
||||
);
|
||||
} while (selection.addAnother);
|
||||
};
|
||||
@@ -1,357 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
// 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/>.
|
||||
|
||||
import chalk from 'chalk';
|
||||
import { getAllComponents } from './components.mjs';
|
||||
import { getCombinedNotesByComponent, deleteAllNotes } from './note.mjs';
|
||||
import { getNoteName } from './noteTypes.mjs';
|
||||
import { writeFile, readFile, unlink } from 'fs/promises';
|
||||
import { join as joinPath } from 'path';
|
||||
import logger from './logger.mjs';
|
||||
import { getCurrentVersion } from './helpers.mjs';
|
||||
|
||||
/**
|
||||
* Helper to fetch the current notes from a file.
|
||||
*
|
||||
* @param {string} file
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
const getCurrentNotes = async (file) => {
|
||||
try {
|
||||
return await readFile(file, 'utf8');
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the UPGRADING.md file.
|
||||
*
|
||||
* @param {string} upgradeNotes
|
||||
* @param {Object} options
|
||||
* @param {boolean} options.deleteNotes
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const updateUpgradeNotes = async (upgradeNotes, options) => {
|
||||
const fileName = 'UPGRADING.md';
|
||||
// Write the notes to a file.
|
||||
logger.info(`Writing notes to ${chalk.underline(chalk.bold(fileName))}`);
|
||||
// Prepend to the existing file.
|
||||
const existingContent = await getCurrentNotes(fileName);
|
||||
if (existingContent) {
|
||||
await writeFile(fileName, getUpdatedNotes(existingContent, upgradeNotes));
|
||||
} else {
|
||||
// This should not normally happen.
|
||||
await writeFile(fileName, upgradeNotes);
|
||||
}
|
||||
|
||||
if (options.deleteNotes) {
|
||||
logger.warn(`>>> Deleting all notes <<<`)
|
||||
// Delete the notes.
|
||||
deleteAllNotes();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the current summary notes.
|
||||
*
|
||||
* @param {string} upgradeNotes
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const createCurrentSummary = async (upgradeNotes) => {
|
||||
const fileName = 'UPGRADING-CURRENT.md';
|
||||
const notes = `# Moodle upgrade notes\n\n${upgradeNotes}`;
|
||||
await writeFile(fileName, notes);
|
||||
|
||||
logger.info(`Running upgrade notes written to ${chalk.underline(chalk.bold(fileName))}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the indexes of the lines that contain the version headings.
|
||||
*
|
||||
* @param {array<string>} lines
|
||||
* @returns {array<object>}
|
||||
*/
|
||||
const getVersionLineIndexes = (lines) => {
|
||||
const h2Indexes = [];
|
||||
lines.forEach((line, index) => {
|
||||
const matches = line.match(/^##\s(?<version>.*)$/);
|
||||
if (matches) {
|
||||
h2Indexes.push({
|
||||
index,
|
||||
line,
|
||||
version: matches.groups.version,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return h2Indexes;
|
||||
};
|
||||
|
||||
/**
|
||||
* Find the index of the Unreleased heading.
|
||||
*
|
||||
* @param {array<object>} versionHeadings
|
||||
* @returns {number}
|
||||
*/
|
||||
const findUnreleasedHeadingIndex = (versionHeadings) => versionHeadings.findIndex((heading) => {
|
||||
if (heading.version === 'Unreleased') {
|
||||
// Used if version cannot be guessed.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (heading.version.endsWith('+')) {
|
||||
// Weekly release for a stable branch.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (heading.version.match(/beta|rc\d/)) {
|
||||
// Beta and RC rolls are treated as weeklies.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (heading.version.endsWith('dev')) {
|
||||
// Development version.
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
/**
|
||||
* Get the before and after content, to facilitate replacing any existing Unreleased notes.
|
||||
*
|
||||
* @param {array<string>} lines
|
||||
* @returns {Object} {beforeContent: string, afterContent: string}
|
||||
*/
|
||||
const getBeforeAndAfterContent = (lines) => {
|
||||
const existingLines = lines.split('\n');
|
||||
const versionHeadings = getVersionLineIndexes(existingLines);
|
||||
|
||||
if (versionHeadings.length > 0) {
|
||||
const unreleasedHeadingIndex = findUnreleasedHeadingIndex(versionHeadings);
|
||||
if (unreleasedHeadingIndex !== -1) {
|
||||
const beforeContent = existingLines.slice(0, versionHeadings[unreleasedHeadingIndex].index).join('\n');
|
||||
if (versionHeadings.length > unreleasedHeadingIndex + 1) {
|
||||
const afterContent = existingLines.slice(versionHeadings[unreleasedHeadingIndex + 1].index).join('\n');
|
||||
return {
|
||||
beforeContent,
|
||||
afterContent,
|
||||
};
|
||||
}
|
||||
return {
|
||||
beforeContent,
|
||||
afterContent: '',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
beforeContent: existingLines.slice(0, versionHeadings[0].index).join('\n'),
|
||||
afterContent: existingLines.slice(versionHeadings[0].index).join('\n'),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
beforeContent: existingLines.join('\n'),
|
||||
afterContent: '',
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the notes for the component.
|
||||
*
|
||||
* @param {string} types
|
||||
* @param {Number} headingLevel
|
||||
* @returns {string}
|
||||
*/
|
||||
const getNotesForComponent = (types, headingLevel) => {
|
||||
let upgradeNotes = '';
|
||||
Object.entries(types).forEach(([type, notes]) => {
|
||||
upgradeNotes += '#'.repeat(headingLevel);
|
||||
upgradeNotes += ` ${getNoteName(type)}\n\n`;
|
||||
notes.forEach(({ message, issueNumber }) => {
|
||||
// Split the message into lines, removing empty lines.
|
||||
const messageLines = message
|
||||
.split('\n')
|
||||
// Remove empty lines between tables, and list entries, but not after lists.
|
||||
.filter((line, index, lines) => {
|
||||
if (line.trim().length === 0) {
|
||||
// This line is empty.
|
||||
|
||||
// If it's the first line in the file, remove it.
|
||||
if (index === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// This is the last line in the file, remove it.
|
||||
if (index === lines.length - 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the previous line relates to a table, remove this line.
|
||||
if (lines[index - 1].match(/^\s*\|/)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the next line is also empty, do not remove this line.
|
||||
if (lines[index + 1].trim().length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Do not remove the line if the previous line was a list item.
|
||||
if (lines[index - 1].match(/^\s*[-*]\s/)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lines[index - 1].match(/^\s*\d+\.\s/)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Preserve all other empty lines by default.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Keep any line which has content.
|
||||
return true;
|
||||
});
|
||||
|
||||
|
||||
const firstLine = messageLines.shift().trim();
|
||||
upgradeNotes += `- ${firstLine}\n`;
|
||||
|
||||
messageLines
|
||||
.forEach((line) => {
|
||||
upgradeNotes += ` ${line}`.trimEnd() + `\n`;
|
||||
});
|
||||
upgradeNotes += `\n For more information see [${issueNumber}](https://tracker.moodle.org/browse/${issueNumber})\n`;
|
||||
});
|
||||
upgradeNotes += '\n';
|
||||
});
|
||||
|
||||
return upgradeNotes;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the updated notes mixed with existing content.
|
||||
*
|
||||
* @param {string} existingContent
|
||||
* @param {string} upgradeNotes
|
||||
*/
|
||||
const getUpdatedNotes = (existingContent, upgradeNotes) => {
|
||||
const { beforeContent, afterContent } = getBeforeAndAfterContent(existingContent);
|
||||
const newContent = `${beforeContent}\n${upgradeNotes}\n${afterContent}`
|
||||
.split('\n')
|
||||
.filter((line, index, lines) => {
|
||||
if (line === '' && lines[index - 1] === '') {
|
||||
// Remove multiple consecutive empty lines.
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.join('\n');
|
||||
|
||||
return newContent;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the notes for each component.
|
||||
*/
|
||||
const updateComponentNotes = (
|
||||
notes,
|
||||
version,
|
||||
notesFileName = 'UPGRADING.md',
|
||||
removeEmpty = false,
|
||||
) => {
|
||||
return getAllComponents().map(async (component) => {
|
||||
logger.verbose(`Updating notes for ${component.name} into ${component.path}`);
|
||||
const fileName = joinPath(component.path, notesFileName);
|
||||
|
||||
const existingContent = await getCurrentNotes(fileName);
|
||||
|
||||
if (!existingContent) {
|
||||
if (!notes[component.value]) {
|
||||
// No existing notes, and no new notes to add.
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (!notes[component.value]) {
|
||||
// There is existing content, but nothing to add.
|
||||
if (removeEmpty) {
|
||||
logger.verbose(`Removing empty notes file ${fileName}`);
|
||||
await unlink(fileName);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const componentNotes = notes[component.value];
|
||||
let upgradeNotes = `## ${version}\n\n`;
|
||||
upgradeNotes += getNotesForComponent(componentNotes, 3);
|
||||
|
||||
if (existingContent) {
|
||||
await writeFile(fileName, getUpdatedNotes(existingContent, upgradeNotes));
|
||||
} else {
|
||||
await writeFile(
|
||||
fileName,
|
||||
`# ${component.name} Upgrade notes\n\n${upgradeNotes}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the upgrade notes for a new release.
|
||||
*
|
||||
* @param {string|undefined} version
|
||||
* @param {Object} options
|
||||
* @param {boolean} options.generateUpgradeNotes
|
||||
* @param {boolean} options.deleteNotes
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export default async (version, options = {}) => {
|
||||
const notes = await getCombinedNotesByComponent();
|
||||
|
||||
if (Object.keys(notes).length === 0) {
|
||||
logger.warn('No notes to generate');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!version) {
|
||||
version = await getCurrentVersion();
|
||||
}
|
||||
|
||||
// Generate the upgrade notes for this release.
|
||||
// We have
|
||||
// - a title with the release name
|
||||
// - the change types
|
||||
// - which contain the components
|
||||
// - which document each change
|
||||
let upgradeNotes = `## ${version}\n\n`;
|
||||
|
||||
Object.entries(notes).forEach(([component, types]) => {
|
||||
upgradeNotes += `### ${component}\n\n`;
|
||||
upgradeNotes += getNotesForComponent(types, 4);
|
||||
});
|
||||
|
||||
await Promise.all([
|
||||
createCurrentSummary(upgradeNotes),
|
||||
...updateComponentNotes(notes, version, 'UPGRADING-CURRENT.md', true),
|
||||
]);
|
||||
if (options.generateUpgradeNotes) {
|
||||
await Promise.all(updateComponentNotes(notes, version));
|
||||
await updateUpgradeNotes(upgradeNotes, options);
|
||||
}
|
||||
};
|
||||
@@ -1,230 +0,0 @@
|
||||
// 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/>.
|
||||
|
||||
import chalk from 'chalk';
|
||||
import { isStandardComponent, isCommunityComponent, rewritePlugintypeAsSubsystem } from './components.mjs';
|
||||
import { isValidNoteName } from './noteTypes.mjs';
|
||||
import logger from './logger.mjs';
|
||||
import { readFile } from 'fs/promises';
|
||||
|
||||
/**
|
||||
* Validate an issue number input
|
||||
*
|
||||
* @param {string} input
|
||||
* @returns {string|boolean}
|
||||
*/
|
||||
export const validateIssueNumber = (input) => {
|
||||
if (!input) {
|
||||
return 'You must provide a tracker issue number';
|
||||
}
|
||||
|
||||
if (input.match(/^[a-zA-Z]*-\d+$/)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (input.match(/^\d+$/)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return 'The issue number was not recognised as a valid issue number';
|
||||
};
|
||||
|
||||
/**
|
||||
* Format an issue number input.
|
||||
*
|
||||
* @param {string} input
|
||||
* @returns {string}
|
||||
*/
|
||||
export const formatIssueNumber = (input) => {
|
||||
if (input.match(/^[a-zA-Z]*-\d+$/)) {
|
||||
return input;
|
||||
}
|
||||
|
||||
if (input.match(/^\d+$/)) {
|
||||
return `MDL-${input}`;
|
||||
}
|
||||
|
||||
return input;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate a component.
|
||||
*
|
||||
* @param {string} input
|
||||
* @returns {string|boolean}
|
||||
*/
|
||||
export const validateComponent = (input) => {
|
||||
if (isStandardComponent(input)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isCommunityComponent(input)) {
|
||||
return 'Currently only core plugins are supported.';
|
||||
}
|
||||
|
||||
return 'The component was not recognised as a standard component';
|
||||
};
|
||||
|
||||
export const formatComponent = (input) => {
|
||||
if (rewritePlugintypeAsSubsystem(input)) {
|
||||
return `core_${input}`;
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the initial values from the options.
|
||||
*
|
||||
* @param {object} options
|
||||
* @returns {object}
|
||||
*/
|
||||
export const getInitialValues = (options) => {
|
||||
const initialValues = {};
|
||||
|
||||
const type = getInitialTypeValue(options);
|
||||
if (type) {
|
||||
initialValues.type = type;
|
||||
}
|
||||
|
||||
const issueNumber = getInitialIssueValue(options);
|
||||
if (issueNumber) {
|
||||
initialValues.issueNumber = issueNumber;
|
||||
}
|
||||
|
||||
const component = getInitialComponentValue(options);
|
||||
if (component) {
|
||||
initialValues.components = component;
|
||||
}
|
||||
|
||||
const message = getInitialMessageValue(options);
|
||||
if (message) {
|
||||
initialValues.message = message
|
||||
initialValues.addAnother = false;
|
||||
}
|
||||
|
||||
return initialValues;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the initial type value.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @returns {string|undefined}
|
||||
*/
|
||||
const getInitialTypeValue = (options) => {
|
||||
if (!options.type) {
|
||||
return;
|
||||
}
|
||||
|
||||
options.type = options.type.trim().toLowerCase();
|
||||
|
||||
if (isValidNoteName(options.type)) {
|
||||
return options.type;
|
||||
}
|
||||
|
||||
logger.warn(`Note type "${chalk.underline(chalk.red(options.type))}" is not valid.`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the initial issue number value.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @returns {string|undefined}
|
||||
*/
|
||||
|
||||
const getInitialIssueValue = (options) => {
|
||||
if (!options.issue) {
|
||||
return;
|
||||
}
|
||||
options.issue = options.issue.trim().toUpperCase();
|
||||
|
||||
const issueNumberValidated = validateIssueNumber(options.issue);
|
||||
if (issueNumberValidated === true) {
|
||||
const issueNumber = formatIssueNumber(options.issue);
|
||||
if (issueNumber !== options.issue) {
|
||||
logger.warn(
|
||||
`Issue number "${chalk.underline(chalk.red(options.issue))}" was updated to ` +
|
||||
`"${chalk.underline(chalk.green(issueNumber))}"`
|
||||
);
|
||||
}
|
||||
|
||||
return issueNumber;
|
||||
} else {
|
||||
logger.warn(`Issue number "${chalk.underline(chalk.red(options.issue))}" is not valid: ${issueNumberValidated}`);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the initial component value.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @returns {string|undefined}
|
||||
*/
|
||||
const getInitialComponentValue = (options) => {
|
||||
if (!options.component) {
|
||||
return;
|
||||
}
|
||||
|
||||
options.component = options.component.trim().toLowerCase();
|
||||
const componentValidated = validateComponent(options.component);
|
||||
if (componentValidated === true) {
|
||||
const component = formatComponent(options.component);
|
||||
if (component !== options.component) {
|
||||
logger.warn(
|
||||
`Component "${chalk.underline(chalk.red(options.component))}" was updated to ` +
|
||||
`"${chalk.underline(chalk.green(component))}"`
|
||||
);
|
||||
}
|
||||
|
||||
return component;
|
||||
} else {
|
||||
logger.warn(`Component "${chalk.underline(chalk.red(options.component))}" is not valid: ${componentValidated}`);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the initial message value.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @returns {string|undefined}
|
||||
*/
|
||||
|
||||
const getInitialMessageValue = (options) => {
|
||||
if (!options.message) {
|
||||
return;
|
||||
}
|
||||
|
||||
return options.message.trim();
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the current version from the project /version.php file.
|
||||
*
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
export const getCurrentVersion = async () => {
|
||||
const versionRegex = new RegExp(/^ *\$release *= *['\"](?<release>[^ \+]+\+?) *\(Build:.*/m);
|
||||
try {
|
||||
const versionFile = await readFile('version.php', 'utf8');
|
||||
const match = versionFile.match(versionRegex);
|
||||
if (match) {
|
||||
return match.groups.release;
|
||||
}
|
||||
} catch(error) {
|
||||
logger.error('Unable to read the version file');
|
||||
}
|
||||
|
||||
return "Unreleased";
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
// 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/>.
|
||||
|
||||
import winston from 'winston';
|
||||
|
||||
const logger = winston.createLogger({
|
||||
level: 'info',
|
||||
format: winston.format.combine(
|
||||
winston.format.colorize(),
|
||||
winston.format.simple()
|
||||
),
|
||||
transports: [
|
||||
new winston.transports.Console(),
|
||||
],
|
||||
});
|
||||
|
||||
export default logger;
|
||||
@@ -1,194 +0,0 @@
|
||||
// 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/>.
|
||||
|
||||
import yaml from 'js-yaml';
|
||||
import path from 'path';
|
||||
import { writeFile, mkdir, readdir, readFile, unlink } from 'fs/promises';
|
||||
import { isValidNoteName, sortNoteTypes } from './noteTypes.mjs';
|
||||
import { sortComponents } from './components.mjs';
|
||||
|
||||
const unreleasedPath = path.resolve('.upgradenotes');
|
||||
|
||||
/**
|
||||
* Get the filename for the note.
|
||||
*
|
||||
* @param {string} issueNumnber The issue number
|
||||
* @returns {string}
|
||||
*/
|
||||
const getFilename = (issueNumber) => {
|
||||
const dateTimeFormat = new Intl.DateTimeFormat(undefined, {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
fractionalSecondDigits: 2,
|
||||
timeZone: 'UTC',
|
||||
});
|
||||
|
||||
const date = Object.fromEntries(
|
||||
dateTimeFormat.formatToParts(new Date())
|
||||
.filter((p) => p.type !== 'literal')
|
||||
.map((p) => ([p.type, p.value]))
|
||||
);
|
||||
|
||||
const dateString = [
|
||||
date.year,
|
||||
date.month,
|
||||
date.day,
|
||||
date.hour,
|
||||
date.minute,
|
||||
date.second,
|
||||
date.fractionalSecond,
|
||||
].join('');
|
||||
|
||||
return `${issueNumber}-${dateString}.yml`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new note.
|
||||
*
|
||||
* @param {string} issueNumber
|
||||
* @param {[type: string]: {message: string}} messages
|
||||
* @returns {string} The path to the note on disk
|
||||
*/
|
||||
export const createNote = async (
|
||||
issueNumber,
|
||||
messages,
|
||||
notePath,
|
||||
) => {
|
||||
const note = {
|
||||
issueNumber,
|
||||
notes: {},
|
||||
};
|
||||
|
||||
messages.forEach(({components, type, message}) => {
|
||||
note.notes[components] = note.notes[components] || [];
|
||||
note.notes[components].push({message, type});
|
||||
});
|
||||
|
||||
if (!notePath) {
|
||||
notePath = path.resolve(unreleasedPath, getFilename(issueNumber));
|
||||
}
|
||||
const noteContent = yaml.dump(note);
|
||||
|
||||
await mkdir(unreleasedPath, {recursive: true});
|
||||
await writeFile(notePath, noteContent);
|
||||
|
||||
return notePath;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all unreleased notes.
|
||||
*
|
||||
* @returns {Promise<{issueNumber: string, components: string[], types: {[type: string]: {message: string}[]}}[]>
|
||||
*/
|
||||
export const getAllNotes = async () => {
|
||||
const files = await readdir(unreleasedPath);
|
||||
const notes = files
|
||||
.filter((file) => file.endsWith('.yml'))
|
||||
.map(async (file) => {
|
||||
const filePath = path.resolve(unreleasedPath, file);
|
||||
const fileContent = await readFile(filePath, 'utf8');
|
||||
|
||||
return {
|
||||
...yaml.load(fileContent),
|
||||
filePath,
|
||||
};
|
||||
});
|
||||
|
||||
return Promise.all(notes);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the list of notes, grouped by note type, then component.
|
||||
*
|
||||
* @returns {Promise<{[type: string]: {[components: string]: {message: string, issueNumber: string}[]}}>}
|
||||
*/
|
||||
export const getCombinedNotes = async () => {
|
||||
const notes = await getAllNotes();
|
||||
const combinedNotes = {};
|
||||
|
||||
notes.forEach((note) => {
|
||||
Object.entries(note.notes).forEach(([components, data]) => {
|
||||
data.forEach((entry) => {
|
||||
if (!isValidNoteName(entry.type)) {
|
||||
throw new Error(`Invalid note type: "${entry.type}" in file ${note.filePath}`);
|
||||
}
|
||||
combinedNotes[entry.type] = combinedNotes[entry.type] || {};
|
||||
combinedNotes[entry.type][components] = combinedNotes[entry.type][components] || [];
|
||||
combinedNotes[entry.type][components].push({message: entry.message, issueNumber: note.issueNumber});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return combinedNotes;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the list of notes, grouped by component, then by note type.
|
||||
*
|
||||
* @returns {Promise<{[component: string]: {[type: string]: {message: string, issueNumber: string}[]}}>}
|
||||
*/
|
||||
export const getCombinedNotesByComponent = async () => {
|
||||
const notes = await getAllNotes();
|
||||
const combinedNotes = {};
|
||||
|
||||
notes.forEach((note) => {
|
||||
Object.entries(note.notes).forEach(([component, data]) => {
|
||||
combinedNotes[component] = combinedNotes[component] || {};
|
||||
data.forEach((entry) => {
|
||||
if (!isValidNoteName(entry.type)) {
|
||||
throw new Error(`Invalid note type: "${entry.type}" in file ${note.filePath}`);
|
||||
}
|
||||
combinedNotes[component][entry.type] = combinedNotes[component][entry.type] || [];
|
||||
combinedNotes[component][entry.type].push({
|
||||
component,
|
||||
message: entry.message,
|
||||
issueNumber: note.issueNumber,
|
||||
type: entry.type,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Sort notes by note type.
|
||||
Object.entries(combinedNotes).forEach(([component, types]) => {
|
||||
combinedNotes[component] = Object.fromEntries(
|
||||
Object.entries(types).sort(([a], [b]) => sortNoteTypes(a, b))
|
||||
);
|
||||
});
|
||||
|
||||
// Sort components.
|
||||
return Object.fromEntries(
|
||||
Object.entries(combinedNotes).sort(([a], [b]) => sortComponents(a, b))
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete all unreleased notes.
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export const deleteAllNotes = async () => {
|
||||
const files = await readdir(unreleasedPath);
|
||||
return Promise.all(
|
||||
files
|
||||
.filter((item, index) => files.indexOf(item) === index)
|
||||
.filter((file) => file.endsWith('.yml'))
|
||||
.map((file) => unlink(`${unreleasedPath}/${file}`))
|
||||
);
|
||||
};
|
||||
@@ -1,80 +0,0 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* The note types.
|
||||
*
|
||||
* Matches the suggested order in https://keepachangelog.com/en/1.1.0/.
|
||||
* @type {Object<string, string>}
|
||||
*/
|
||||
const noteTypes = {
|
||||
'improved': 'Added',
|
||||
'changed': 'Changed',
|
||||
'deprecated': 'Deprecated',
|
||||
'removed': 'Removed',
|
||||
'fixed': 'Fixed',
|
||||
};
|
||||
|
||||
/**
|
||||
* The preferred order of note types.
|
||||
*
|
||||
* @type {string[]}
|
||||
*/
|
||||
const preferredOrder = Object.keys(noteTypes);
|
||||
|
||||
/**
|
||||
* Comparison method to sort note types.
|
||||
*
|
||||
* @param {String} a
|
||||
* @param {String} b
|
||||
* @returns {Number}
|
||||
*/
|
||||
export const sortNoteTypes = (a, b) => {
|
||||
const aIndex = preferredOrder.indexOf(a);
|
||||
const bIndex = preferredOrder.indexOf(b);
|
||||
|
||||
if (aIndex === -1) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (bIndex === -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return aIndex - bIndex;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the note names.
|
||||
*
|
||||
* @returns {string[]}
|
||||
*/
|
||||
export const getNoteNames = () => Object.keys(noteTypes);
|
||||
|
||||
/**
|
||||
* Get the human-readable note name.
|
||||
*
|
||||
* @param {string} type
|
||||
* @returns {string}
|
||||
*/
|
||||
export const getNoteName = (type) => noteTypes[type];
|
||||
|
||||
/**
|
||||
* Whether the note name is valid.
|
||||
*
|
||||
* @param {string} type
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const isValidNoteName = (type) => noteTypes[type] !== undefined;
|
||||
@@ -1,153 +0,0 @@
|
||||
// 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/>.
|
||||
|
||||
import inquirer from 'inquirer';
|
||||
import SearchList from 'inquirer-search-list';
|
||||
|
||||
import { getNoteNames } from './noteTypes.mjs';
|
||||
import { getAllComponents } from './components.mjs';
|
||||
import {
|
||||
formatComponent,
|
||||
formatIssueNumber,
|
||||
validateComponent,
|
||||
validateIssueNumber,
|
||||
} from './helpers.mjs';
|
||||
|
||||
/**
|
||||
* A Search List which accepts an initial value.
|
||||
*/
|
||||
class SearchListWithInitialValue extends SearchList {
|
||||
constructor(options, ...args) {
|
||||
super(options, ...args);
|
||||
|
||||
if (options.default) {
|
||||
const pointer = this.filterList.findIndex((item) => {
|
||||
return item.value === options.default;
|
||||
});
|
||||
if (pointer > -1) {
|
||||
this.pointer = pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inquirer.registerPrompt('search-list', SearchListWithInitialValue);
|
||||
|
||||
/**
|
||||
* Get the issue type prompt.
|
||||
*
|
||||
* @param {string} defaultData The initially selected value
|
||||
* @returns {Object}
|
||||
*/
|
||||
export const getTypePrompt = (defaultData) => ({
|
||||
default: defaultData,
|
||||
type: 'search-list',
|
||||
message: 'Type of change',
|
||||
name: 'type',
|
||||
choices: getNoteNames(),
|
||||
validate: (selection) => {
|
||||
if (selection.length < 1) {
|
||||
return 'You must select at least one type of change';
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Get the component prompt.
|
||||
*
|
||||
* @param {string} [defaultValue='core'] The initally selected value.
|
||||
* @returns
|
||||
*/
|
||||
export const getComponentsPrompt = (defaultValue) => {
|
||||
if (!defaultValue ) {
|
||||
defaultValue = 'core';
|
||||
}
|
||||
|
||||
return {
|
||||
choices: getAllComponents(),
|
||||
default: defaultValue,
|
||||
type: 'search-list',
|
||||
message: 'Component',
|
||||
name: 'components',
|
||||
validate: validateComponent,
|
||||
filter: formatComponent,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the issue number prompt as an inline input.
|
||||
*
|
||||
* @param {string} defaultData
|
||||
* @returns {object}
|
||||
*/
|
||||
export const getIssuePrompt = (defaultData) => ({
|
||||
default: defaultData,
|
||||
type: 'input',
|
||||
message: 'Tracker issue number',
|
||||
name: 'issueNumber',
|
||||
validate: validateIssueNumber,
|
||||
filter: formatIssueNumber,
|
||||
});
|
||||
|
||||
/**
|
||||
* Get a message prompt.
|
||||
*
|
||||
* @param {string} defaultData
|
||||
* @returns
|
||||
*/
|
||||
export const getMessagePromptEditor = (defaultData) => ({
|
||||
default: defaultData,
|
||||
type: process.stdin.isTTY ? 'editor' : 'input',
|
||||
postfix: '.md',
|
||||
message: 'Message',
|
||||
name: 'message',
|
||||
waitUserInput: false,
|
||||
validate: (input) => {
|
||||
if (!input) {
|
||||
return 'You must provide a message';
|
||||
}
|
||||
return true;
|
||||
},
|
||||
// Remove any trailing whitespace.
|
||||
filter: (input) => input.split('\n').map((line) => line.trimEnd()).join('\n'),
|
||||
});
|
||||
|
||||
/**
|
||||
* Get a message prompt.
|
||||
*
|
||||
* @param {string} defaultData
|
||||
* @returns
|
||||
*/
|
||||
export const getMessagePromptInput = (defaultData) => ({
|
||||
default: defaultData,
|
||||
type: 'input',
|
||||
message: 'Message (leave empty to use editor)',
|
||||
name: 'message',
|
||||
filter: (input) => input.trim(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Get a prompt to ask the user if they wish to add another entry.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
export const getAddAnotherPrompt = () => ({
|
||||
type: 'confirm',
|
||||
message: 'Do you want to add another note?',
|
||||
default: false,
|
||||
name: 'addAnother',
|
||||
});
|
||||
@@ -21,7 +21,7 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* Function to generate the destination for the minification task
|
||||
* Function to generate the destination for the uglify 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,6 +60,7 @@ 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');
|
||||
|
||||
@@ -103,7 +104,6 @@ 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,17 +113,15 @@ 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) => {
|
||||
options: async() => {
|
||||
return new Promise(resolve => {
|
||||
queue.push(resolve);
|
||||
startQueue();
|
||||
return options;
|
||||
});
|
||||
},
|
||||
|
||||
// When an item in the queue completes, start the next item in the queue.
|
||||
generateBundle: (options, bundle) => {
|
||||
grunt.log.debug(`Finished output phase for ${Object.keys(bundle).join(', ')}`);
|
||||
buildEnd: () => {
|
||||
runQueue();
|
||||
},
|
||||
};
|
||||
@@ -153,10 +151,24 @@ 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')
|
||||
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'
|
||||
],
|
||||
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
|
||||
}]
|
||||
|
||||
+12
-28
@@ -20,33 +20,17 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
module.exports = (grunt) => {
|
||||
const path = require('path');
|
||||
|
||||
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();
|
||||
});
|
||||
module.exports = grunt => {
|
||||
// Project configuration.
|
||||
grunt.config.merge({
|
||||
jsdoc: {
|
||||
dist: {
|
||||
options: {
|
||||
configure: ".grunt/jsdoc/jsdoc.conf.js",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
grunt.loadNpmTasks('grunt-jsdoc');
|
||||
};
|
||||
|
||||
@@ -29,7 +29,6 @@ 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.
|
||||
@@ -46,10 +45,7 @@ module.exports = grunt => {
|
||||
return {
|
||||
stylelint: {
|
||||
scss: {
|
||||
options: {
|
||||
quietDeprecationWarnings: true,
|
||||
customSyntax: 'postcss-scss',
|
||||
},
|
||||
options: {syntax: 'scss'},
|
||||
src: files,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
// 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/>.
|
||||
|
||||
import { Argument, Option, program } from 'commander';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { getNoteNames } from './notes/src/noteTypes.mjs';
|
||||
import createAction from './notes/src/create.mjs';
|
||||
import generateAction from './notes/src/generate.mjs';
|
||||
import logger from './notes/src/logger.mjs';
|
||||
|
||||
console.log(`
|
||||
${chalk.bold(chalk.underline(chalk.green('Moodle Upgrade Notes Generator')))}
|
||||
|
||||
This tool is used to generate the upgrade notes for changes you make in Moodle.
|
||||
|
||||
Please remember that the intended audience of these changes is
|
||||
${chalk.italic('plugin developers')} who need to know how to update their plugins
|
||||
for a new Moodle version.
|
||||
|
||||
Upgrade notes should not be used to document changes for site administrators, or
|
||||
for internal API changes which are not expected to be used outside of the
|
||||
relevant component.
|
||||
`)
|
||||
|
||||
program.configureHelp({
|
||||
helpWidth: 100,
|
||||
});
|
||||
|
||||
program.on('option:verbose', () => {
|
||||
logger.level = 'verbose';
|
||||
});
|
||||
|
||||
program.addOption(
|
||||
new Option(
|
||||
'-v, --verbose',
|
||||
'Output more information during the generation process',
|
||||
)
|
||||
.default(false)
|
||||
);
|
||||
|
||||
// Define the command line options.
|
||||
program
|
||||
.command('create')
|
||||
.summary('Generate a new upgrade note')
|
||||
.addOption(
|
||||
new Option('-t, --type <type>', `The type of change to document. Valid types are: ${getNoteNames().join(', ')}`)
|
||||
)
|
||||
.addOption(new Option('-i, --issue <issue>', 'The tracker issue number'))
|
||||
.addOption(new Option('-c, --component <component>', 'The component to write a note for'))
|
||||
.addOption(new Option(
|
||||
'-m, --message <message>',
|
||||
'The message to use for the upgrade note',
|
||||
))
|
||||
.action((options) => createAction(options));
|
||||
|
||||
program
|
||||
.command('summary')
|
||||
.summary('Generate a local copy of the upgrade notes summary')
|
||||
.addArgument(
|
||||
new Argument('[version]', 'The Moodle version to create the summary notes for')
|
||||
)
|
||||
.action((version) => generateAction(version));
|
||||
|
||||
program
|
||||
.command('release')
|
||||
.summary('Generate the markdown copies of the upgrade notes for a Moodle release')
|
||||
.addArgument(
|
||||
new Argument('[version]', 'The Moodle version to create the release notes for')
|
||||
)
|
||||
.addOption(
|
||||
new Option(
|
||||
'--generate-upgrade-notes',
|
||||
'Generate the UPGRADING.md notes for the release. ' +
|
||||
'Note: This option is intended for use by the release manager when generating the upgrade notes.',
|
||||
)
|
||||
.default(true)
|
||||
)
|
||||
.addOption(
|
||||
new Option(
|
||||
'-d, --delete-notes',
|
||||
'Delete the notes after generating the UPGRADING.md notes for the release. ' +
|
||||
'Note: This option is intended for use by the release manager when generating the upgrade notes.' +
|
||||
'This option has no effect unless --generate-upgrade-notes is also set.'
|
||||
)
|
||||
.default(false)
|
||||
)
|
||||
.action((version, options) => generateAction(version, options));
|
||||
|
||||
program.parse();
|
||||
@@ -1,40 +0,0 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Helper file for PhpStorm, and other IDEs, to provide better code completion.
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2024 Andrew Lyons <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace PHPSTORM_META;
|
||||
|
||||
// Assume that anything returned by \core\di::get('...') is an instance of the first argument.
|
||||
override(\core\di::get(0), map([
|
||||
'' => '@',
|
||||
]));
|
||||
|
||||
// Assume that anything returned by \Psr\Container\ContainerInterface::get('...') is an instance of the first argument.
|
||||
override(\Psr\Container\ContainerInterface::get(0), map([
|
||||
'' => '@',
|
||||
]));
|
||||
|
||||
// Assume that anything returned by \DI\Container::get('...') is an instance of the first argument.
|
||||
override(\DI\Container::get(0), map([
|
||||
'' => '@',
|
||||
]));
|
||||
+4
-16
@@ -1,14 +1,9 @@
|
||||
{
|
||||
"customSyntax": "postcss-scss",
|
||||
"plugins": [
|
||||
"stylelint-csstree-validator"
|
||||
],
|
||||
"rules": {
|
||||
"csstree/validator": {
|
||||
"syntaxExtensions": [
|
||||
"sass"
|
||||
]
|
||||
},
|
||||
"csstree/validator": true,
|
||||
"at-rule-empty-line-before": [ "always",
|
||||
{"except": [ "blockless-after-blockless"], ignore: ["after-comment", "inside-block"]}
|
||||
],
|
||||
@@ -50,7 +45,7 @@
|
||||
"function-name-case": "lower",
|
||||
"function-parentheses-newline-inside": "always-multi-line",
|
||||
"function-parentheses-space-inside": "never-single-line",
|
||||
"function-url-scheme-disallowed-list": ["data"],
|
||||
"function-url-scheme-blacklist": ["data"],
|
||||
"function-whitespace-after": "always",
|
||||
"indentation": 4,
|
||||
"keyframe-declaration-no-important": true,
|
||||
@@ -89,19 +84,12 @@
|
||||
"selector-type-no-unknown": true,
|
||||
"string-no-newline": true,
|
||||
"time-min-milliseconds": 100,
|
||||
"unit-disallowed-list": ["pt"],
|
||||
"unit-blacklist": ["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": {
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
# Contributing to Moodle
|
||||
|
||||
[Moodle][1] is made by people like you. We are members of a big worldwide community of developers, designers, teachers, testers, translators and many more. We work in universities, schools, companies and other places. You are very welcome to join us and contribute to the project.
|
||||
|
||||
There are many ways that you can contribute to Moodle, not just through development. See our [community contribution guide][2] for some of the many ways that you can help.
|
||||
|
||||
## Github
|
||||
|
||||
> [!NOTE]
|
||||
> Please do not open pull requests via Github.
|
||||
|
||||
All issues should be reported via, and patched provided to the [Moodle Tracker][3].
|
||||
|
||||
The Moodle [Github][4] repository is a clone of the official Moodle repository, whcih can be found at https://git.moodle.org.
|
||||
|
||||
## Moodle core bug fixes and new features
|
||||
|
||||
Over the years, Moodle has developed a mature process for reporting, reviewing, and accepting patches. This is fully documented in our [documentation on development processes][6], but in summary:
|
||||
|
||||
- Every bug fix or new feature must have a tracker issue.
|
||||
- You publish the branch implementing the fix or new feature in your public clone of the moodle.git repository (typically on Github).
|
||||
- Your patch is peer-reviewed, discussed, integrated, tested and then released as a part of one of our weekly releases.
|
||||
- New features are developed on the `main` branch. Bug fixes are also backported to currently supported maintenance (stable) branches.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Please do not publish security issues, or patches releating to them publicly.
|
||||
> See our [Responsible Disclosure Policy][5] for more information.
|
||||
|
||||
|
||||
## Moodle plugins
|
||||
|
||||
Moodle has a framework for additional plugins to extend its functionality. We
|
||||
have a Moodle plugins directory <https://moodle.org/plugins/> where you can
|
||||
register and maintain your plugin. Plugins hosted in the plugins directory can
|
||||
be easily installed and updated via the Moodle administration interface.
|
||||
|
||||
* You are expected to have a public source code repository with your plugin
|
||||
code.
|
||||
* After registering your plugin in the plugins directory it is reviewed before
|
||||
being published.
|
||||
* You are expected to continuously release updated versions of the plugin via
|
||||
the plugins directory. We do not pull from your code repository; you must do
|
||||
it explicitly.
|
||||
|
||||
For further details, see <https://moodledev.io/general/community/plugincontribution>.
|
||||
|
||||
[1]: https://moodle.org
|
||||
[2]: https://moodledev.io/general/community/contribute
|
||||
[3]: https://tracker.moodle.org
|
||||
[4]: https://github.com/moodle/moodle
|
||||
[5]: https://moodledev.io/general/development/process/security
|
||||
[6]: https://moodledev.io/general/development/process
|
||||
@@ -0,0 +1,56 @@
|
||||
CONTRIBUTING TO MOODLE
|
||||
======================
|
||||
|
||||
Moodle is made by people like you. We are members of a big worldwide community
|
||||
of developers, designers, teachers, testers, translators and other users. We
|
||||
work in universities, schools, companies and other places. You are very welcome
|
||||
to join us and contribute to the project.
|
||||
|
||||
See <https://docs.moodle.org/dev/Contributing_to_Moodle> for the many ways you
|
||||
can help, not only with coding.
|
||||
|
||||
Moodle is open to community contributions to core, though all code must go
|
||||
through peer-review, automated behaviour testing, continuous integration and
|
||||
human post-integration checks.
|
||||
|
||||
Pull requests
|
||||
-------------
|
||||
|
||||
Please do not open pull requests via Github. The repository there is just a
|
||||
mirror of the official repository at <https://git.moodle.org>. Issues are
|
||||
reported and patches provided via <https://tracker.moodle.org>. See below for
|
||||
more information.
|
||||
|
||||
Moodle core bug fixes and new features
|
||||
--------------------------------------
|
||||
|
||||
During the years of intensive development, a mature process of including
|
||||
submitted patches has evolved.
|
||||
|
||||
* Every bug fix or new feature must have a tracker issue.
|
||||
* You publish the branch implementing the fix or new feature in your public
|
||||
clone of the moodle.git repository (typically on Github).
|
||||
* Your patch is peer-reviewed, discussed, integrated, tested and then released
|
||||
as a part of moodle.git.
|
||||
* New features are developed on the master branch. Bug fixes are also
|
||||
backported to currently supported maintenance (stable) branches.
|
||||
|
||||
For further details, see <https://docs.moodle.org/dev/Process>.
|
||||
|
||||
Moodle plugins
|
||||
--------------
|
||||
|
||||
Moodle has a framework for additional plugins to extend its functionality. We
|
||||
have a Moodle plugins directory <https://moodle.org/plugins/> where you can
|
||||
register and maintain your plugin. Plugins hosted in the plugins directory can
|
||||
be easily installed and updated via the Moodle administration interface.
|
||||
|
||||
* You are expected to have a public source code repository with your plugin
|
||||
code.
|
||||
* After registering your plugin in the plugins directory it is reviewed before
|
||||
being published.
|
||||
* You are expected to continuously release updated versions of the plugin via
|
||||
the plugins directory. We do not pull from your code repository; you must do
|
||||
it explicitly.
|
||||
|
||||
For further details, see <https://docs.moodle.org/dev/Plugin_contribution>.
|
||||
@@ -1,7 +0,0 @@
|
||||
issueNumber: MDL-87443
|
||||
notes:
|
||||
core:
|
||||
- message: >-
|
||||
There is a new Behat `toast_message` named selector to more easily
|
||||
assert the presence of Toast messages on the page
|
||||
type: improved
|
||||
@@ -0,0 +1,7 @@
|
||||
*** PLEASE DO NOT OPEN PULL REQUESTS VIA GITHUB ***
|
||||
|
||||
The moodle.git repository at Github is just a mirror of the official repository. We do not accept pull requests at Github.
|
||||
|
||||
See CONTRIBUTING.txt guidelines for how to contribute patches for Moodle. Thank you.
|
||||
|
||||
--
|
||||
@@ -1,48 +0,0 @@
|
||||
# Moodle
|
||||
|
||||
<p align="center"><a href="https://moodle.org" target="_blank" title="Moodle Website">
|
||||
<img src="https://raw.githubusercontent.com/moodle/moodle/main/.github/moodlelogo.svg" alt="The Moodle Logo">
|
||||
</a></p>
|
||||
|
||||
[Moodle][1] is the World's Open Source Learning Platform, widely used around the world by countless universities, schools, companies, and all manner of organisations and individuals.
|
||||
|
||||
Moodle is designed to allow educators, administrators and learners to create personalised learning environments with a single robust, secure and integrated system.
|
||||
|
||||
## Documentation
|
||||
|
||||
- Read our [User documentation][3]
|
||||
- Discover our [developer documentation][5]
|
||||
- Take a look at our [demo site][4]
|
||||
|
||||
## Community
|
||||
|
||||
[moodle.org][1] is the central hub for the Moodle Community, with spaces for educators, administrators and developers to meet and work together.
|
||||
|
||||
You may also be interested in:
|
||||
|
||||
- attending a [Moodle Moot][6]
|
||||
- our regular series of [developer meetings][7]
|
||||
- the [Moodle User Association][8]
|
||||
|
||||
## Installation and hosting
|
||||
|
||||
Moodle is Free, and Open Source software. You can easily [download Moodle][9] and run it on your own web server, however you may prefer to work with one of our experienced [Moodle Partners][10].
|
||||
|
||||
Moodle also offers hosting through both [MoodleCloud][11], and our [partner network][10].
|
||||
|
||||
## License
|
||||
|
||||
Moodle is provided freely as open source software, under version 3 of the GNU General Public License. For more information on our license see
|
||||
|
||||
[1]: https://moodle.org
|
||||
[2]: https://moodle.com
|
||||
[3]: https://docs.moodle.org/
|
||||
[4]: https://sandbox.moodledemo.net/
|
||||
[5]: https://moodledev.io
|
||||
[6]: https://moodle.com/events/mootglobal/
|
||||
[7]: https://moodledev.io/general/community/meetings
|
||||
[8]: https://moodleassociation.org/
|
||||
[9]: https://download.moodle.org
|
||||
[10]: https://moodle.com/partners
|
||||
[11]: https://moodle.com/cloud
|
||||
[12]: https://moodledev.io/general/license
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
.-..-.
|
||||
_____ | || |
|
||||
/____/-.---_ .---. .---. .-.| || | .---.
|
||||
| | _ _ |/ _ \/ _ \/ _ || |/ __ \
|
||||
* | | | | | || |_| || |_| || |_| || || |___/
|
||||
|_| |_| |_|\_____/\_____/\_____||_|\_____)
|
||||
|
||||
Moodle - the world's open source learning platform
|
||||
|
||||
Moodle <https://moodle.org> is a learning platform designed to provide
|
||||
educators, administrators and learners with a single robust, secure and
|
||||
integrated system to create personalised learning environments.
|
||||
|
||||
You can download Moodle <https://download.moodle.org> and run it on your own
|
||||
web server, ask one of our Moodle Partners <https://moodle.com/partners/> to
|
||||
assist you, or have a MoodleCloud site <https://moodle.com/cloud/> set up for
|
||||
you.
|
||||
|
||||
Moodle is widely used around the world by universities, schools, companies and
|
||||
all manner of organisations and individuals.
|
||||
|
||||
Moodle is provided freely as open source software, under the GNU General Public
|
||||
License <https://docs.moodle.org/dev/License>.
|
||||
|
||||
Moodle is written in PHP and JavaScript and uses an SQL database for storing
|
||||
the data.
|
||||
|
||||
See <https://docs.moodle.org> for details of Moodle's many features.
|
||||
-1601
File diff suppressed because it is too large
Load Diff
@@ -1,9 +0,0 @@
|
||||
# core_admin (subsystem) Upgrade notes
|
||||
|
||||
## 4.5rc1
|
||||
|
||||
### Removed
|
||||
|
||||
- The HTTP Server setting "Use slash arguments" (slasharguments) configuration setting and related to it has been removed. Calling the option with $CFG->slasharguments or get_config('slasharguments') is no longer available.
|
||||
|
||||
For more information see [MDL-62640](https://tracker.moodle.org/browse/MDL-62640)
|
||||
@@ -1,65 +0,0 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Manage AI subsystem plugins settings.
|
||||
*
|
||||
* @package core_admin
|
||||
* @copyright 2024 Matt Porritt <matt.porritt@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
require_once('../config.php');
|
||||
require_once($CFG->libdir . '/adminlib.php');
|
||||
|
||||
$action = required_param('action', PARAM_ALPHANUMEXT);
|
||||
$type = required_param('type', PARAM_PLUGIN); // Must be provider or placement.
|
||||
$name = required_param('name', PARAM_PLUGIN); // Plugin name, e.g openai.
|
||||
|
||||
$syscontext = context_system::instance();
|
||||
$PAGE->set_url('/admin/ai.php');
|
||||
$PAGE->set_context($syscontext);
|
||||
|
||||
require_admin();
|
||||
require_sesskey();
|
||||
|
||||
$return = new moodle_url('/admin/settings.php', ['section' => "aisettings$type"]);
|
||||
|
||||
$plugins = core_plugin_manager::instance()->get_plugins_of_type("ai$type");
|
||||
$sortorder = array_flip(array_keys($plugins));
|
||||
|
||||
if (!isset($plugins[$name])) {
|
||||
throw new moodle_exception('aipluginnotfound', 'core_ai', $return, $name);
|
||||
}
|
||||
|
||||
$plugintypename = $plugins[$name]->type . '_' . $plugins[$name]->name;
|
||||
|
||||
switch ($action) {
|
||||
case 'disable':
|
||||
if ($plugins[$name]->is_enabled()) {
|
||||
$class = core_plugin_manager::resolve_plugininfo_class("aisettings$type");
|
||||
$class::enable_plugin($name, false);
|
||||
}
|
||||
break;
|
||||
case 'enable':
|
||||
if (!$plugins[$name]->is_enabled()) {
|
||||
$class = core_plugin_manager::resolve_plugininfo_class("aisettings$type");
|
||||
$class::enable_plugin($name, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
redirect($return);
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
define("core_admin/bulk_user_actions",["exports","core_reportbuilder/local/selectors","core_table/local/dynamic/events","core_form/changechecker","core/custom_interaction_events","jquery"],(function(_exports,reportSelectors,tableEvents,FormChangeChecker,CustomEvents,_jquery){var obj;function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}
|
||||
/**
|
||||
* Add bulk actions to the users list report
|
||||
*
|
||||
* @module core_admin/bulk_user_actions
|
||||
* @copyright 2024 Marina Glancy
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,reportSelectors=_interopRequireWildcard(reportSelectors),tableEvents=_interopRequireWildcard(tableEvents),FormChangeChecker=_interopRequireWildcard(FormChangeChecker),CustomEvents=_interopRequireWildcard(CustomEvents),_jquery=(obj=_jquery)&&obj.__esModule?obj:{default:obj};const Selectors_bulkActionsForm="form#user-bulk-action-form",Selectors_userReportWrapper='[data-region="report-user-list-wrapper"]',Selectors_checkbox='input[type="checkbox"][data-togglegroup="report-select-all"][data-toggle="slave"]',Selectors_masterCheckbox='input[type="checkbox"][data-togglegroup="report-select-all"][data-toggle="master"]',Selectors_checkedRows='[data-togglegroup="report-select-all"][data-toggle="slave"]:checked';_exports.init=()=>{var _userBulkForm$closest;const userBulkForm=document.querySelector(Selectors_bulkActionsForm),userReport=null==userBulkForm||null===(_userBulkForm$closest=userBulkForm.closest(Selectors_userReportWrapper))||void 0===_userBulkForm$closest?void 0:_userBulkForm$closest.querySelector(reportSelectors.regions.report);if(!userBulkForm||!userReport)return;const actionSelect=userBulkForm.querySelector("select");CustomEvents.define(actionSelect,[CustomEvents.events.accessibleChange]),(0,_jquery.default)(actionSelect).on(CustomEvents.events.accessibleChange,(event=>{if(event.target.value&&"0"!=="".concat(event.target.value)){const e=new Event("submit",{cancelable:!0});userBulkForm.dispatchEvent(e),e.defaultPrevented||(FormChangeChecker.markFormSubmitted(userBulkForm),userBulkForm.submit())}}));const updateUserIds=()=>{const selectedUsers=[...userReport.querySelectorAll(Selectors_checkedRows)],selectedUserIds=selectedUsers.map((check=>parseInt(check.value)));userBulkForm.querySelector('[name="userids"]').value=selectedUserIds.join(","),actionSelect.disabled=0===selectedUsers.length,actionSelect.disabled&&(actionSelect.value="0");const selectedUsersNames=selectedUsers.map((check=>document.querySelector('label[for="'.concat(check.id,'"]')).textContent));userBulkForm.data={userids:selectedUserIds,usernames:selectedUsersNames}};updateUserIds(),document.addEventListener("change",(event=>{(event.target.matches(Selectors_checkbox)||event.target.matches(Selectors_masterCheckbox))&&userReport.contains(event.target)&&updateUserIds()})),document.addEventListener(tableEvents.tableContentRefreshed,(event=>{userReport.contains(event.target)&&updateUserIds()}))}}));
|
||||
|
||||
//# sourceMappingURL=bulk_user_actions.min.js.map
|
||||
File diff suppressed because one or more lines are too long
Vendored
-12
@@ -1,12 +0,0 @@
|
||||
define("core_admin/expand_hash",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=_exports.expandSection=void 0;
|
||||
/**
|
||||
* Expand the collapse section element.
|
||||
*
|
||||
* @module core_admin/expand_hash
|
||||
* @copyright Meirza <meirza.arson@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @since 4.5
|
||||
*/
|
||||
const SELECTORS_COLLAPSE_ELEMENTS='[data-toggle="collapse"]',SELECTORS_FOCUSTHENEXPAND_ELEMENTS=".focus-expand";_exports.init=()=>{const focusexpand=document.querySelector(SELECTORS_FOCUSTHENEXPAND_ELEMENTS);null==focusexpand||focusexpand.addEventListener("click",(()=>{expandSection("".concat(focusexpand.getAttribute("href")))}))};const expandSection=hash=>{const container=document.querySelector(hash),targetContainer=null==container?void 0:container.querySelector(SELECTORS_COLLAPSE_ELEMENTS);if("false"===(null==targetContainer?void 0:targetContainer.getAttribute("aria-expanded"))){const collapseId=targetContainer.getAttribute("aria-controls"),collapseContainer=document.getElementById(collapseId);collapseContainer.classList.remove("collapse"),collapseContainer.classList.add("show"),targetContainer.setAttribute("aria-expanded","true"),targetContainer.classList.remove("collapsed");const collapseElement=document.querySelector(".collapseexpand.collapsemenu");collapseElement.setAttribute("aria-expanded","true"),collapseElement.classList.remove("collapsed")}};_exports.expandSection=expandSection}));
|
||||
|
||||
//# sourceMappingURL=expand_hash.min.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"expand_hash.min.js","sources":["../src/expand_hash.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Expand the collapse section element.\n *\n * @module core_admin/expand_hash\n * @copyright Meirza <meirza.arson@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 4.5\n */\n\nconst SELECTORS = {\n COLLAPSE_ELEMENTS: '[data-toggle=\"collapse\"]',\n FOCUSTHENEXPAND_ELEMENTS: '.focus-expand',\n};\n\n/**\n * Initializes the focus and expand functionality.\n */\nexport const init = () => {\n // Select all collapsible elements only.\n const focusexpand = document.querySelector(SELECTORS.FOCUSTHENEXPAND_ELEMENTS);\n\n // Add click event listener to the anchor element\n focusexpand?.addEventListener('click', () => {\n expandSection(`${focusexpand.getAttribute('href')}`);\n });\n};\n\n/**\n * Expands a section based on the provided URL hash.\n *\n * This function takes a hash string, finds the corresponding element in the DOM,\n * and expands it if it is currently collapsed. It updates the necessary ARIA\n * attributes and classes to reflect the expanded state.\n *\n * @param {string} hash - The hash (e.g., '#elementId') of the element to expand.\n */\nexport const expandSection = (hash) => {\n const container = document.querySelector(hash);\n const targetContainer = container?.querySelector(SELECTORS.COLLAPSE_ELEMENTS);\n\n if (targetContainer?.getAttribute('aria-expanded') === 'false') {\n const collapseId = targetContainer.getAttribute('aria-controls');\n const collapseContainer = document.getElementById(collapseId);\n\n // Show the content.\n collapseContainer.classList.remove('collapse');\n collapseContainer.classList.add('show');\n\n // Update aria-expanded attribute to reflect the new state.\n targetContainer.setAttribute('aria-expanded', 'true');\n targetContainer.classList.remove('collapsed');\n\n // Get collapse expand menu element.\n const collapseElement = document.querySelector('.collapseexpand.collapsemenu');\n // Ensure it gets noticed to make it work.\n collapseElement.setAttribute('aria-expanded', 'true');\n collapseElement.classList.remove('collapsed');\n }\n};\n"],"names":["SELECTORS","focusexpand","document","querySelector","addEventListener","expandSection","getAttribute","hash","container","targetContainer","collapseId","collapseContainer","getElementById","classList","remove","add","setAttribute","collapseElement"],"mappings":";;;;;;;;;MAwBMA,4BACiB,2BADjBA,mCAEwB,8BAMV,WAEVC,YAAcC,SAASC,cAAcH,oCAG3CC,MAAAA,aAAAA,YAAaG,iBAAiB,SAAS,KACnCC,wBAAiBJ,YAAYK,aAAa,oBAarCD,cAAiBE,aACpBC,UAAYN,SAASC,cAAcI,MACnCE,gBAAkBD,MAAAA,iBAAAA,UAAWL,cAAcH,gCAEM,WAAnDS,MAAAA,uBAAAA,gBAAiBH,aAAa,kBAA8B,OACtDI,WAAaD,gBAAgBH,aAAa,iBAC1CK,kBAAoBT,SAASU,eAAeF,YAGlDC,kBAAkBE,UAAUC,OAAO,YACnCH,kBAAkBE,UAAUE,IAAI,QAGhCN,gBAAgBO,aAAa,gBAAiB,QAC9CP,gBAAgBI,UAAUC,OAAO,mBAG3BG,gBAAkBf,SAASC,cAAc,gCAE/Cc,gBAAgBD,aAAa,gBAAiB,QAC9CC,gBAAgBJ,UAAUC,OAAO"}
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
define("core_admin/plugin_management_table",["exports","core_table/dynamic","core_table/local/dynamic/selectors","core/ajax","core/pending","core/notification"],(function(_exports,_dynamic,Selectors,_ajax,_pending,_notification){var obj;function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,Selectors=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(Selectors),_pending=(obj=_pending)&&obj.__esModule?obj:{default:obj};let watching=!1;return _exports.default=class{constructor(){!function(obj,key,value){key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value}(this,"clickHandlers",[]),this.addClickHandler(this.handleStateToggle),this.addClickHandler(this.handleMoveUpDown),this.registerEventListeners()}static init(){watching||(watching=!0,new this)}addClickHandler(handler){this.clickHandlers.push(handler.bind(this))}registerEventListeners(){document.addEventListener("click",function(e){const tableRoot=this.getTableRoot(e);tableRoot&&this.clickHandlers.forEach((handler=>handler(tableRoot,e)))}.bind(this))}getTableRoot(e){const tableRoot=e.target.closest(Selectors.main.region);return tableRoot||!1}setPluginState(methodname,plugin,state){return(0,_ajax.call)([{methodname:methodname,args:{plugin:plugin,state:state}}])[0]}setPluginOrder(methodname,plugin,direction){return(0,_ajax.call)([{methodname:methodname,args:{plugin:plugin,direction:direction}}])[0]}async handleStateToggle(tableRoot,e){const stateToggle=e.target.closest('[data-action="togglestate"][data-toggle-method]');if(stateToggle){e.preventDefault();const pendingPromise=new _pending.default("core_table/dynamic:togglestate");await this.setPluginState(stateToggle.dataset.toggleMethod,stateToggle.dataset.plugin,"1"===stateToggle.dataset.state?0:1);const[updatedRoot]=await Promise.all([(0,_dynamic.refreshTableContent)(tableRoot),(0,_notification.fetchNotifications)()]);updatedRoot.querySelector('[data-action="togglestate"][data-plugin="'.concat(stateToggle.dataset.plugin,'"]')).focus(),this.removeTooltips(),pendingPromise.resolve()}}async handleMoveUpDown(tableRoot,e){const actionLink=e.target.closest('[data-action="move"][data-method][data-direction]');if(!actionLink)return;e.preventDefault();const pendingPromise=new _pending.default("core_table/dynamic:processAction");await this.setPluginOrder(actionLink.dataset.method,actionLink.dataset.plugin,"up"===actionLink.dataset.direction?-1:1);const[updatedRoot]=await Promise.all([(0,_dynamic.refreshTableContent)(tableRoot),(0,_notification.fetchNotifications)()]),exactMatch=updatedRoot.querySelector('[data-action="move"][data-plugin="'.concat(actionLink.dataset.plugin,'"][data-direction="').concat(actionLink.dataset.direction,'"]'));var _updatedRoot$querySel;exactMatch?exactMatch.focus():null===(_updatedRoot$querySel=updatedRoot.querySelector('[data-action="move"][data-plugin="'.concat(actionLink.dataset.plugin,'"]')))||void 0===_updatedRoot$querySel||_updatedRoot$querySel.focus();pendingPromise.resolve()}removeTooltips(){const tooltips=document.querySelectorAll('[id*="tooltip"]');tooltips.length>0&&tooltips.forEach((tooltip=>{tooltip.remove()}))}},_exports.default}));
|
||||
define("core_admin/plugin_management_table",["exports","core_table/dynamic","core_table/local/dynamic/selectors","core/ajax","core/pending","core/notification"],(function(_exports,_dynamic,Selectors,_ajax,_pending,_notification){var obj;function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,Selectors=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(Selectors),_pending=(obj=_pending)&&obj.__esModule?obj:{default:obj};let watching=!1;return _exports.default=class{constructor(){!function(obj,key,value){key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value}(this,"clickHandlers",[]),this.addClickHandler(this.handleStateToggle),this.addClickHandler(this.handleMoveUpDown),this.registerEventListeners()}static init(){watching||(watching=!0,new this)}addClickHandler(handler){this.clickHandlers.push(handler.bind(this))}registerEventListeners(){document.addEventListener("click",function(e){const tableRoot=this.getTableRoot(e);tableRoot&&this.clickHandlers.forEach((handler=>handler(tableRoot,e)))}.bind(this))}getTableRoot(e){const tableRoot=e.target.closest(Selectors.main.region);return tableRoot||!1}setPluginState(methodname,plugin,state){return(0,_ajax.call)([{methodname:methodname,args:{plugin:plugin,state:state}}])[0]}setPluginOrder(methodname,plugin,direction){return(0,_ajax.call)([{methodname:methodname,args:{plugin:plugin,direction:direction}}])[0]}async handleStateToggle(tableRoot,e){const stateToggle=e.target.closest('[data-action="togglestate"][data-toggle-method]');if(stateToggle){e.preventDefault();const pendingPromise=new _pending.default("core_table/dynamic:togglestate");await this.setPluginState(stateToggle.dataset.toggleMethod,stateToggle.dataset.plugin,"1"===stateToggle.dataset.state?0:1);const[updatedRoot]=await Promise.all([(0,_dynamic.refreshTableContent)(tableRoot),(0,_notification.fetchNotifications)()]);updatedRoot.querySelector('[data-action="togglestate"][data-plugin="'.concat(stateToggle.dataset.plugin,'"]')).focus(),pendingPromise.resolve()}}async handleMoveUpDown(tableRoot,e){const actionLink=e.target.closest('[data-action="move"][data-method][data-direction]');if(!actionLink)return;e.preventDefault();const pendingPromise=new _pending.default("core_table/dynamic:processAction");await this.setPluginOrder(actionLink.dataset.method,actionLink.dataset.plugin,"up"===actionLink.dataset.direction?-1:1);const[updatedRoot]=await Promise.all([(0,_dynamic.refreshTableContent)(tableRoot),(0,_notification.fetchNotifications)()]),exactMatch=updatedRoot.querySelector('[data-action="move"][data-plugin="'.concat(actionLink.dataset.plugin,'"][data-direction="').concat(actionLink.dataset.direction,'"]'));var _updatedRoot$querySel;exactMatch?exactMatch.focus():null===(_updatedRoot$querySel=updatedRoot.querySelector('[data-action="move"][data-plugin="'.concat(actionLink.dataset.plugin,'"]')))||void 0===_updatedRoot$querySel||_updatedRoot$querySel.focus();pendingPromise.resolve()}},_exports.default}));
|
||||
|
||||
//# sourceMappingURL=plugin_management_table.min.js.map
|
||||
File diff suppressed because one or more lines are too long
-11
@@ -1,11 +0,0 @@
|
||||
define("core_admin/plugins_overview",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=function(){const filters=document.querySelectorAll(SELECTORS_PLUGIN_FILTERS),pluginRows=document.querySelectorAll(SELECTORS_PLUGIN_ROWS),pluginTypeRows=document.querySelectorAll(SELECTORS_PLUGIN_TYPE_ROWS),filterPlugins=target=>{const filterBy=target.getAttribute("data-filterby"),headerVisibility={};for(const row of pluginRows){const type=[...row.classList].find((s=>s.startsWith("type-"))),visible="all"===filterBy||row.classList.contains(filterBy);row.style.display=visible?null:"none",visible&&type&&(headerVisibility[type]=!0)}for(const row of pluginTypeRows){const type=[...row.classList].find((s=>s.startsWith("type-")));if(type){const visible="all"===filterBy||headerVisibility[type];row.style.display=visible?null:"none"}}filters.forEach((el=>el.classList.remove("active"))),target.classList.add("active")};if(filters.forEach((target=>target.addEventListener("click",(e=>{e.preventDefault(),window.history.replaceState({},null,e.target.href),filterPlugins(target)})))),window.location.hash.length>1){const anchor=window.location.hash.substring(1),target=[...filters].find((t=>t.getAttribute("data-filterby")===anchor));target&&filterPlugins(target)}};
|
||||
/**
|
||||
* Allows to filter the plugin list on plugins overview page
|
||||
*
|
||||
* @module core_admin/plugins_overview
|
||||
* @copyright 2024 Marina Glancy
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
const SELECTORS_PLUGIN_FILTERS="#plugins-overview-panel [data-filterby]",SELECTORS_PLUGIN_ROWS="table#plugins-control-panel tbody tr:not(.plugintypeheader)",SELECTORS_PLUGIN_TYPE_ROWS="table#plugins-control-panel tbody tr.plugintypeheader"}));
|
||||
|
||||
//# sourceMappingURL=plugins_overview.min.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"plugins_overview.min.js","sources":["../src/plugins_overview.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Allows to filter the plugin list on plugins overview page\n *\n * @module core_admin/plugins_overview\n * @copyright 2024 Marina Glancy\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nconst SELECTORS = {\n PLUGIN_FILTERS: '#plugins-overview-panel [data-filterby]',\n PLUGIN_ROWS: 'table#plugins-control-panel tbody tr:not(.plugintypeheader)',\n PLUGIN_TYPE_ROWS: 'table#plugins-control-panel tbody tr.plugintypeheader',\n};\n\n/**\n * Initialise filters for the \"Plugins overview\" page\n */\nexport function init() {\n const filters = document.querySelectorAll(SELECTORS.PLUGIN_FILTERS);\n const pluginRows = document.querySelectorAll(SELECTORS.PLUGIN_ROWS);\n const pluginTypeRows = document.querySelectorAll(SELECTORS.PLUGIN_TYPE_ROWS);\n\n const filterPlugins = (target) => {\n const filterBy = target.getAttribute('data-filterby');\n const headerVisibility = {};\n\n // Hide all plugin rows in the plugin table that do not match the filter and show all others.\n for (const row of pluginRows) {\n const type = [...row.classList].find(s => s.startsWith('type-'));\n const visible = filterBy === 'all' ? true : row.classList.contains(filterBy);\n row.style.display = visible ? null : 'none';\n if (visible && type) {\n headerVisibility[type] = true;\n }\n }\n\n // Hide all the plugin type headers that do not have any visible plugins and show all others.\n for (const row of pluginTypeRows) {\n const type = [...row.classList].find(s => s.startsWith('type-'));\n if (type) {\n const visible = filterBy === 'all' || headerVisibility[type];\n row.style.display = visible ? null : 'none';\n }\n }\n\n // Toggle 'active' class for the selected filter.\n filters.forEach(el => el.classList.remove('active'));\n target.classList.add('active');\n };\n\n // Add event listeners for the links changing plugins filters.\n filters\n .forEach(target => target.addEventListener('click', (e) => {\n e.preventDefault();\n window.history.replaceState({}, null, e.target.href);\n filterPlugins(target);\n }));\n\n // Pre-filter plugins based on the current url anchor.\n if (window.location.hash.length > 1) {\n const anchor = window.location.hash.substring(1);\n const target = [...filters].find(t => t.getAttribute('data-filterby') === anchor);\n if (target) {\n filterPlugins(target);\n }\n }\n}\n"],"names":["filters","document","querySelectorAll","SELECTORS","pluginRows","pluginTypeRows","filterPlugins","target","filterBy","getAttribute","headerVisibility","row","type","classList","find","s","startsWith","visible","contains","style","display","forEach","el","remove","add","addEventListener","e","preventDefault","window","history","replaceState","href","location","hash","length","anchor","substring","t"],"mappings":"4JAiCUA,QAAUC,SAASC,iBAAiBC,0BACpCC,WAAaH,SAASC,iBAAiBC,uBACvCE,eAAiBJ,SAASC,iBAAiBC,4BAE3CG,cAAiBC,eACbC,SAAWD,OAAOE,aAAa,iBAC/BC,iBAAmB,OAGpB,MAAMC,OAAOP,WAAY,OACpBQ,KAAO,IAAID,IAAIE,WAAWC,MAAKC,GAAKA,EAAEC,WAAW,WACjDC,QAAuB,QAAbT,UAA4BG,IAAIE,UAAUK,SAASV,UACnEG,IAAIQ,MAAMC,QAAUH,QAAU,KAAO,OACjCA,SAAWL,OACXF,iBAAiBE,OAAQ,OAK5B,MAAMD,OAAON,eAAgB,OACxBO,KAAO,IAAID,IAAIE,WAAWC,MAAKC,GAAKA,EAAEC,WAAW,cACnDJ,KAAM,OACAK,QAAuB,QAAbT,UAAsBE,iBAAiBE,MACvDD,IAAIQ,MAAMC,QAAUH,QAAU,KAAO,QAK7CjB,QAAQqB,SAAQC,IAAMA,GAAGT,UAAUU,OAAO,YAC1ChB,OAAOM,UAAUW,IAAI,cAIzBxB,QACCqB,SAAQd,QAAUA,OAAOkB,iBAAiB,SAAUC,IACjDA,EAAEC,iBACFC,OAAOC,QAAQC,aAAa,GAAI,KAAMJ,EAAEnB,OAAOwB,MAC/CzB,cAAcC,aAIdqB,OAAOI,SAASC,KAAKC,OAAS,EAAG,OAC3BC,OAASP,OAAOI,SAASC,KAAKG,UAAU,GACxC7B,OAAS,IAAIP,SAASc,MAAKuB,GAAKA,EAAE5B,aAAa,mBAAqB0B,SACtE5B,QACAD,cAAcC;;;;;;;;MAvDpBJ,yBACc,0CADdA,sBAEW,8DAFXA,2BAGgB"}
|
||||
@@ -1,10 +0,0 @@
|
||||
define("core_admin/themeselector/preview_modal",["exports","core/modal_events","core/modal_cancel","core/modal_save_cancel","core/notification","core/templates","core/str"],(function(_exports,_modal_events,_modal_cancel,_modal_save_cancel,_notification,_templates,_str){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
|
||||
/**
|
||||
* Modal for theme previews.
|
||||
*
|
||||
* @module core_admin/themeselector/preview_modal
|
||||
* @copyright 2023 David Woloszyn <david.woloszyn@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_modal_events=_interopRequireDefault(_modal_events),_modal_cancel=_interopRequireDefault(_modal_cancel),_modal_save_cancel=_interopRequireDefault(_modal_save_cancel),_notification=_interopRequireDefault(_notification),_templates=_interopRequireDefault(_templates);const SELECTORS_THEMES_CONTAINER="themelist",SELECTORS_PREVIEW='[data-action="preview"]';_exports.init=()=>{registerListenerEvents()};const registerListenerEvents=()=>{document.addEventListener("click",(e=>{const preview=e.target.closest(SELECTORS_PREVIEW);preview&&buildModal(preview).catch(_notification.default.exception)}))},buildModal=async element=>{let description=await(0,_str.getString)("choosereadme","theme_"+element.getAttribute("data-choose"));const themesContainer=document.getElementById(SELECTORS_THEMES_CONTAINER),definedInConfig=parseInt(themesContainer.dataset.definedinconfig),data={name:element.getAttribute("data-name"),image:element.getAttribute("data-image"),description:description.replace(/<[^>]+>/g," "),current:element.getAttribute("data-current"),actionurl:element.getAttribute("data-actionurl"),choose:element.getAttribute("data-choose"),sesskey:element.getAttribute("data-sesskey"),definedinconfig:definedInConfig};let modalTemplate=_modal_save_cancel.default;(data.current||data.definedinconfig)&&(modalTemplate=_modal_cancel.default);const modal=await modalTemplate.create({title:data.name,body:_templates.default.render("core_admin/themeselector/theme_preview_modal",data),large:!0,buttons:{save:(0,_str.getString)("selecttheme","moodle"),cancel:(0,_str.getString)("closebuttontitle","moodle")},show:!0});modal.getRoot().on(_modal_events.default.save,(()=>{modal.getRoot().find("form").submit()}))}}));
|
||||
|
||||
//# sourceMappingURL=preview_modal.min.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"preview_modal.min.js","sources":["../../src/themeselector/preview_modal.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Modal for theme previews.\n *\n * @module core_admin/themeselector/preview_modal\n * @copyright 2023 David Woloszyn <david.woloszyn@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport ModalEvents from 'core/modal_events';\nimport ModalCancel from 'core/modal_cancel';\nimport ModalSaveCancel from 'core/modal_save_cancel';\nimport Notification from 'core/notification';\nimport Templates from 'core/templates';\nimport {getString} from 'core/str';\n\nconst SELECTORS = {\n THEMES_CONTAINER: 'themelist',\n PREVIEW: '[data-action=\"preview\"]',\n};\n\n/**\n * Entrypoint of the js.\n *\n * @method init\n */\nexport const init = () => {\n registerListenerEvents();\n};\n\n/**\n * Register theme related event listeners.\n *\n * @method registerListenerEvents\n */\nconst registerListenerEvents = () => {\n document.addEventListener('click', (e) => {\n const preview = e.target.closest(SELECTORS.PREVIEW);\n if (preview) {\n buildModal(preview).catch(Notification.exception);\n }\n });\n};\n\n/**\n * Build the modal with the provided data.\n *\n * @method buildModal\n * @param {object} element\n */\nconst buildModal = async(element) => {\n\n // This string can be long. We will fetch it with JS as opposed to passing it as an attribute.\n let description = await getString('choosereadme', 'theme_' + element.getAttribute('data-choose'));\n\n const themesContainer = document.getElementById(SELECTORS.THEMES_CONTAINER);\n const definedInConfig = parseInt(themesContainer.dataset.definedinconfig);\n // Prepare data for modal.\n const data = {\n name: element.getAttribute('data-name'),\n image: element.getAttribute('data-image'),\n description: description.replace(/<[^>]+>/g, ' '), // Strip out HTML tags.\n current: element.getAttribute('data-current'),\n actionurl: element.getAttribute('data-actionurl'),\n choose: element.getAttribute('data-choose'),\n sesskey: element.getAttribute('data-sesskey'),\n definedinconfig: definedInConfig,\n };\n\n // Determine which modal template we should use.\n let modalTemplate = ModalSaveCancel;\n if (data.current || data.definedinconfig) {\n modalTemplate = ModalCancel;\n }\n\n const modal = await modalTemplate.create({\n title: data.name,\n body: Templates.render('core_admin/themeselector/theme_preview_modal', data),\n large: true,\n buttons: {\n 'save': getString('selecttheme', 'moodle'),\n 'cancel': getString('closebuttontitle', 'moodle'),\n },\n show: true,\n });\n\n modal.getRoot().on(ModalEvents.save, () => {\n modal.getRoot().find('form').submit();\n });\n};\n"],"names":["SELECTORS","registerListenerEvents","document","addEventListener","e","preview","target","closest","buildModal","catch","Notification","exception","async","description","element","getAttribute","themesContainer","getElementById","definedInConfig","parseInt","dataset","definedinconfig","data","name","image","replace","current","actionurl","choose","sesskey","modalTemplate","ModalSaveCancel","ModalCancel","modal","create","title","body","Templates","render","large","buttons","show","getRoot","on","ModalEvents","save","find","submit"],"mappings":";;;;;;;gWA8BMA,2BACgB,YADhBA,kBAEO,wCAQO,KAChBC,gCAQEA,uBAAyB,KAC3BC,SAASC,iBAAiB,SAAUC,UAC1BC,QAAUD,EAAEE,OAAOC,QAAQP,mBAC7BK,SACAG,WAAWH,SAASI,MAAMC,sBAAaC,eAW7CH,WAAaI,MAAAA,cAGXC,kBAAoB,kBAAU,eAAgB,SAAWC,QAAQC,aAAa,sBAE5EC,gBAAkBd,SAASe,eAAejB,4BAC1CkB,gBAAkBC,SAASH,gBAAgBI,QAAQC,iBAEnDC,KAAO,CACTC,KAAMT,QAAQC,aAAa,aAC3BS,MAAOV,QAAQC,aAAa,cAC5BF,YAAaA,YAAYY,QAAQ,WAAY,KAC7CC,QAASZ,QAAQC,aAAa,gBAC9BY,UAAWb,QAAQC,aAAa,kBAChCa,OAAQd,QAAQC,aAAa,eAC7Bc,QAASf,QAAQC,aAAa,gBAC9BM,gBAAiBH,qBAIjBY,cAAgBC,4BAChBT,KAAKI,SAAWJ,KAAKD,mBACrBS,cAAgBE,6BAGdC,YAAcH,cAAcI,OAAO,CACrCC,MAAOb,KAAKC,KACZa,KAAMC,mBAAUC,OAAO,+CAAgDhB,MACvEiB,OAAO,EACPC,QAAS,OACG,kBAAU,cAAe,kBACvB,kBAAU,mBAAoB,WAE5CC,MAAM,IAGVR,MAAMS,UAAUC,GAAGC,sBAAYC,MAAM,KACjCZ,MAAMS,UAAUI,KAAK,QAAQC"}
|
||||
@@ -1,97 +0,0 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* Add bulk actions to the users list report
|
||||
*
|
||||
* @module core_admin/bulk_user_actions
|
||||
* @copyright 2024 Marina Glancy
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
import * as reportSelectors from 'core_reportbuilder/local/selectors';
|
||||
import * as tableEvents from 'core_table/local/dynamic/events';
|
||||
import * as FormChangeChecker from 'core_form/changechecker';
|
||||
import * as CustomEvents from 'core/custom_interaction_events';
|
||||
import jQuery from 'jquery';
|
||||
|
||||
const Selectors = {
|
||||
bulkActionsForm: 'form#user-bulk-action-form',
|
||||
userReportWrapper: '[data-region="report-user-list-wrapper"]',
|
||||
checkbox: 'input[type="checkbox"][data-togglegroup="report-select-all"][data-toggle="slave"]',
|
||||
masterCheckbox: 'input[type="checkbox"][data-togglegroup="report-select-all"][data-toggle="master"]',
|
||||
checkedRows: '[data-togglegroup="report-select-all"][data-toggle="slave"]:checked',
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialise module
|
||||
*/
|
||||
export const init = () => {
|
||||
|
||||
const userBulkForm = document.querySelector(Selectors.bulkActionsForm);
|
||||
const userReport = userBulkForm?.closest(Selectors.userReportWrapper)?.querySelector(reportSelectors.regions.report);
|
||||
if (!userBulkForm || !userReport) {
|
||||
return;
|
||||
}
|
||||
const actionSelect = userBulkForm.querySelector('select');
|
||||
CustomEvents.define(actionSelect, [CustomEvents.events.accessibleChange]);
|
||||
|
||||
jQuery(actionSelect).on(CustomEvents.events.accessibleChange, event => {
|
||||
if (event.target.value && `${event.target.value}` !== "0") {
|
||||
const e = new Event('submit', {cancelable: true});
|
||||
userBulkForm.dispatchEvent(e);
|
||||
if (!e.defaultPrevented) {
|
||||
FormChangeChecker.markFormSubmitted(userBulkForm);
|
||||
userBulkForm.submit();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Every time the checkboxes in the report are changed, update the list of users in the form values
|
||||
// and enable/disable the action select.
|
||||
const updateUserIds = () => {
|
||||
const selectedUsers = [...userReport.querySelectorAll(Selectors.checkedRows)];
|
||||
const selectedUserIds = selectedUsers.map(check => parseInt(check.value));
|
||||
userBulkForm.querySelector('[name="userids"]').value = selectedUserIds.join(',');
|
||||
|
||||
// Disable the action selector if nothing selected, and reset the current selection.
|
||||
actionSelect.disabled = selectedUsers.length === 0;
|
||||
if (actionSelect.disabled) {
|
||||
actionSelect.value = "0";
|
||||
}
|
||||
|
||||
const selectedUsersNames = selectedUsers.map(check => document.querySelector(`label[for="${check.id}"]`).textContent);
|
||||
// Add the user ids and names to the form data attributes so they can be available from the
|
||||
// other JS modules that listen to the form submit event.
|
||||
userBulkForm.data = {userids: selectedUserIds, usernames: selectedUsersNames};
|
||||
};
|
||||
|
||||
updateUserIds();
|
||||
|
||||
document.addEventListener('change', event => {
|
||||
// When checkboxes are checked next to individual users or the master toggle (Select all/none).
|
||||
if ((event.target.matches(Selectors.checkbox) || event.target.matches(Selectors.masterCheckbox))
|
||||
&& userReport.contains(event.target)) {
|
||||
updateUserIds();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener(tableEvents.tableContentRefreshed, event => {
|
||||
// When the report contents is updated (i.e. page is changed, filters applied, etc).
|
||||
if (userReport.contains(event.target)) {
|
||||
updateUserIds();
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -1,74 +0,0 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* Expand the collapse section element.
|
||||
*
|
||||
* @module core_admin/expand_hash
|
||||
* @copyright Meirza <meirza.arson@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @since 4.5
|
||||
*/
|
||||
|
||||
const SELECTORS = {
|
||||
COLLAPSE_ELEMENTS: '[data-toggle="collapse"]',
|
||||
FOCUSTHENEXPAND_ELEMENTS: '.focus-expand',
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes the focus and expand functionality.
|
||||
*/
|
||||
export const init = () => {
|
||||
// Select all collapsible elements only.
|
||||
const focusexpand = document.querySelector(SELECTORS.FOCUSTHENEXPAND_ELEMENTS);
|
||||
|
||||
// Add click event listener to the anchor element
|
||||
focusexpand?.addEventListener('click', () => {
|
||||
expandSection(`${focusexpand.getAttribute('href')}`);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Expands a section based on the provided URL hash.
|
||||
*
|
||||
* This function takes a hash string, finds the corresponding element in the DOM,
|
||||
* and expands it if it is currently collapsed. It updates the necessary ARIA
|
||||
* attributes and classes to reflect the expanded state.
|
||||
*
|
||||
* @param {string} hash - The hash (e.g., '#elementId') of the element to expand.
|
||||
*/
|
||||
export const expandSection = (hash) => {
|
||||
const container = document.querySelector(hash);
|
||||
const targetContainer = container?.querySelector(SELECTORS.COLLAPSE_ELEMENTS);
|
||||
|
||||
if (targetContainer?.getAttribute('aria-expanded') === 'false') {
|
||||
const collapseId = targetContainer.getAttribute('aria-controls');
|
||||
const collapseContainer = document.getElementById(collapseId);
|
||||
|
||||
// Show the content.
|
||||
collapseContainer.classList.remove('collapse');
|
||||
collapseContainer.classList.add('show');
|
||||
|
||||
// Update aria-expanded attribute to reflect the new state.
|
||||
targetContainer.setAttribute('aria-expanded', 'true');
|
||||
targetContainer.classList.remove('collapsed');
|
||||
|
||||
// Get collapse expand menu element.
|
||||
const collapseElement = document.querySelector('.collapseexpand.collapsemenu');
|
||||
// Ensure it gets noticed to make it work.
|
||||
collapseElement.setAttribute('aria-expanded', 'true');
|
||||
collapseElement.classList.remove('collapsed');
|
||||
}
|
||||
};
|
||||
@@ -138,10 +138,6 @@ export default class {
|
||||
|
||||
// Refocus on the link that as pressed in the first place.
|
||||
updatedRoot.querySelector(`[data-action="togglestate"][data-plugin="${stateToggle.dataset.plugin}"]`).focus();
|
||||
|
||||
// When clicking the toggle and it remains focused, a new tooltip will be generated.
|
||||
// Therefore, the old tooltip should be removed.
|
||||
this.removeTooltips();
|
||||
pendingPromise.resolve();
|
||||
}
|
||||
}
|
||||
@@ -180,16 +176,4 @@ export default class {
|
||||
|
||||
pendingPromise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove tooltips.
|
||||
*/
|
||||
removeTooltips() {
|
||||
const tooltips = document.querySelectorAll('[id*="tooltip"]');
|
||||
if (tooltips.length > 0) {
|
||||
tooltips.forEach(tooltip => {
|
||||
tooltip.remove();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* Allows to filter the plugin list on plugins overview page
|
||||
*
|
||||
* @module core_admin/plugins_overview
|
||||
* @copyright 2024 Marina Glancy
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
const SELECTORS = {
|
||||
PLUGIN_FILTERS: '#plugins-overview-panel [data-filterby]',
|
||||
PLUGIN_ROWS: 'table#plugins-control-panel tbody tr:not(.plugintypeheader)',
|
||||
PLUGIN_TYPE_ROWS: 'table#plugins-control-panel tbody tr.plugintypeheader',
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialise filters for the "Plugins overview" page
|
||||
*/
|
||||
export function init() {
|
||||
const filters = document.querySelectorAll(SELECTORS.PLUGIN_FILTERS);
|
||||
const pluginRows = document.querySelectorAll(SELECTORS.PLUGIN_ROWS);
|
||||
const pluginTypeRows = document.querySelectorAll(SELECTORS.PLUGIN_TYPE_ROWS);
|
||||
|
||||
const filterPlugins = (target) => {
|
||||
const filterBy = target.getAttribute('data-filterby');
|
||||
const headerVisibility = {};
|
||||
|
||||
// Hide all plugin rows in the plugin table that do not match the filter and show all others.
|
||||
for (const row of pluginRows) {
|
||||
const type = [...row.classList].find(s => s.startsWith('type-'));
|
||||
const visible = filterBy === 'all' ? true : row.classList.contains(filterBy);
|
||||
row.style.display = visible ? null : 'none';
|
||||
if (visible && type) {
|
||||
headerVisibility[type] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Hide all the plugin type headers that do not have any visible plugins and show all others.
|
||||
for (const row of pluginTypeRows) {
|
||||
const type = [...row.classList].find(s => s.startsWith('type-'));
|
||||
if (type) {
|
||||
const visible = filterBy === 'all' || headerVisibility[type];
|
||||
row.style.display = visible ? null : 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle 'active' class for the selected filter.
|
||||
filters.forEach(el => el.classList.remove('active'));
|
||||
target.classList.add('active');
|
||||
};
|
||||
|
||||
// Add event listeners for the links changing plugins filters.
|
||||
filters
|
||||
.forEach(target => target.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
window.history.replaceState({}, null, e.target.href);
|
||||
filterPlugins(target);
|
||||
}));
|
||||
|
||||
// Pre-filter plugins based on the current url anchor.
|
||||
if (window.location.hash.length > 1) {
|
||||
const anchor = window.location.hash.substring(1);
|
||||
const target = [...filters].find(t => t.getAttribute('data-filterby') === anchor);
|
||||
if (target) {
|
||||
filterPlugins(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* Modal for theme previews.
|
||||
*
|
||||
* @module core_admin/themeselector/preview_modal
|
||||
* @copyright 2023 David Woloszyn <david.woloszyn@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
import ModalEvents from 'core/modal_events';
|
||||
import ModalCancel from 'core/modal_cancel';
|
||||
import ModalSaveCancel from 'core/modal_save_cancel';
|
||||
import Notification from 'core/notification';
|
||||
import Templates from 'core/templates';
|
||||
import {getString} from 'core/str';
|
||||
|
||||
const SELECTORS = {
|
||||
THEMES_CONTAINER: 'themelist',
|
||||
PREVIEW: '[data-action="preview"]',
|
||||
};
|
||||
|
||||
/**
|
||||
* Entrypoint of the js.
|
||||
*
|
||||
* @method init
|
||||
*/
|
||||
export const init = () => {
|
||||
registerListenerEvents();
|
||||
};
|
||||
|
||||
/**
|
||||
* Register theme related event listeners.
|
||||
*
|
||||
* @method registerListenerEvents
|
||||
*/
|
||||
const registerListenerEvents = () => {
|
||||
document.addEventListener('click', (e) => {
|
||||
const preview = e.target.closest(SELECTORS.PREVIEW);
|
||||
if (preview) {
|
||||
buildModal(preview).catch(Notification.exception);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Build the modal with the provided data.
|
||||
*
|
||||
* @method buildModal
|
||||
* @param {object} element
|
||||
*/
|
||||
const buildModal = async(element) => {
|
||||
|
||||
// This string can be long. We will fetch it with JS as opposed to passing it as an attribute.
|
||||
let description = await getString('choosereadme', 'theme_' + element.getAttribute('data-choose'));
|
||||
|
||||
const themesContainer = document.getElementById(SELECTORS.THEMES_CONTAINER);
|
||||
const definedInConfig = parseInt(themesContainer.dataset.definedinconfig);
|
||||
// Prepare data for modal.
|
||||
const data = {
|
||||
name: element.getAttribute('data-name'),
|
||||
image: element.getAttribute('data-image'),
|
||||
description: description.replace(/<[^>]+>/g, ' '), // Strip out HTML tags.
|
||||
current: element.getAttribute('data-current'),
|
||||
actionurl: element.getAttribute('data-actionurl'),
|
||||
choose: element.getAttribute('data-choose'),
|
||||
sesskey: element.getAttribute('data-sesskey'),
|
||||
definedinconfig: definedInConfig,
|
||||
};
|
||||
|
||||
// Determine which modal template we should use.
|
||||
let modalTemplate = ModalSaveCancel;
|
||||
if (data.current || data.definedinconfig) {
|
||||
modalTemplate = ModalCancel;
|
||||
}
|
||||
|
||||
const modal = await modalTemplate.create({
|
||||
title: data.name,
|
||||
body: Templates.render('core_admin/themeselector/theme_preview_modal', data),
|
||||
large: true,
|
||||
buttons: {
|
||||
'save': getString('selecttheme', 'moodle'),
|
||||
'cancel': getString('closebuttontitle', 'moodle'),
|
||||
},
|
||||
show: true,
|
||||
});
|
||||
|
||||
modal.getRoot().on(ModalEvents.save, () => {
|
||||
modal.getRoot().find('form').submit();
|
||||
});
|
||||
};
|
||||
+2
-5
@@ -118,10 +118,6 @@ foreach ($settingspage->children as $childpage) {
|
||||
}
|
||||
$outputhtml .= html_writer::tag('div', '<!-- -->', array('class' => 'clearer'));
|
||||
$outputhtml .= $setting->output_html($data);
|
||||
if ($childpage->has_dependencies()) {
|
||||
$opts = ['dependencies' => $childpage->get_dependencies_for_javascript()];
|
||||
$PAGE->requires->js_call_amd('core/showhidesettings', 'init', [$opts]);
|
||||
}
|
||||
}
|
||||
$outputhtml .= html_writer::end_tag('fieldset');
|
||||
}
|
||||
@@ -135,7 +131,8 @@ if ($savebutton) {
|
||||
$outputhtml .= html_writer::end_tag('div');
|
||||
}
|
||||
|
||||
$PAGE->set_title(implode(moodle_page::TITLE_SEPARATOR, $settingspage->visiblepath));
|
||||
$visiblepathtosection = array_reverse($settingspage->visiblepath);
|
||||
$PAGE->set_title("$SITE->shortname: " . implode(": ",$visiblepathtosection));
|
||||
$PAGE->set_heading($SITE->fullname);
|
||||
if ($buttons) {
|
||||
$PAGE->set_button($buttons);
|
||||
|
||||
@@ -62,7 +62,7 @@ class filesize extends \admin_setting {
|
||||
* @param int|null $defaultunit GB, MB, etc. (in bytes)
|
||||
*/
|
||||
public function __construct(string $name, string $visiblename, string $description,
|
||||
?int $defaultvalue = null, ?int $defaultunit = null) {
|
||||
int $defaultvalue = null, int $defaultunit = null) {
|
||||
|
||||
$defaultsetting = self::parse_bytes($defaultvalue);
|
||||
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
<?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 core_admin\output;
|
||||
|
||||
use moodle_url;
|
||||
use renderable;
|
||||
use renderer_base;
|
||||
use stdClass;
|
||||
use templatable;
|
||||
|
||||
/**
|
||||
* Theme selector renderable.
|
||||
*
|
||||
* @package core_admin
|
||||
* @copyright 2023 David Woloszyn <david.woloszyn@moodle.com>
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class theme_selector implements renderable, templatable {
|
||||
|
||||
/** @var array $themedata Theme data to pass to the template. */
|
||||
private $themedata = null;
|
||||
|
||||
/** @var bool Whether $CFG->theme is defined in config.php. */
|
||||
private $definedinconfig;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $themedata Theme data used for template.
|
||||
* @param bool $definedinconfig Whether $CFG->theme is defined in config.php.
|
||||
*/
|
||||
public function __construct(array $themedata, bool $definedinconfig = false) {
|
||||
$this->themedata = $themedata;
|
||||
$this->definedinconfig = $definedinconfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export this data so it can be used as the context for a mustache template.
|
||||
*
|
||||
* @param renderer_base $output Renderer base.
|
||||
* @return stdClass
|
||||
*/
|
||||
public function export_for_template(renderer_base $output): stdClass {
|
||||
|
||||
$data = new stdClass();
|
||||
// Theme data used to populate cards and modal.
|
||||
$data->themes = $this->themedata;
|
||||
// Reset theme caches button.
|
||||
$reseturl = new moodle_url('/admin/themeselector.php', ['sesskey' => sesskey(), 'reset' => 1]);
|
||||
$resetbutton = new \single_button($reseturl, get_string('themeresetcaches', 'admin'), 'post',
|
||||
\single_button::BUTTON_SECONDARY);
|
||||
$data->resetbutton = $resetbutton->export_for_template($output);
|
||||
$data->definedinconfig = $this->definedinconfig;
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ class provider implements \core_privacy\local\metadata\null_provider {
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_reason(): string {
|
||||
public static function get_reason() : string {
|
||||
return 'privacy:metadata';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,17 +78,6 @@ class task_logs extends datasource {
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the column sorting that will be added to the report upon creation
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
public function get_default_column_sorting(): array {
|
||||
return [
|
||||
'task_log:starttime' => SORT_DESC,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the filters that will be added to the report upon creation
|
||||
*
|
||||
|
||||
@@ -46,14 +46,12 @@ class task_log extends base {
|
||||
protected const FAILED = 1;
|
||||
|
||||
/**
|
||||
* Database tables that this entity uses
|
||||
* Database tables that this entity uses and their default aliases
|
||||
*
|
||||
* @return string[]
|
||||
* @return array
|
||||
*/
|
||||
protected function get_default_tables(): array {
|
||||
return [
|
||||
'task_log',
|
||||
];
|
||||
protected function get_default_table_aliases(): array {
|
||||
return ['task_log' => 'tl'];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,7 +103,7 @@ class task_log extends base {
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->set_type(column::TYPE_TEXT)
|
||||
->add_field("{$tablealias}.classname")
|
||||
->add_field("$tablealias.classname")
|
||||
->set_is_sortable(true)
|
||||
->add_callback(static function(string $classname): string {
|
||||
$output = '';
|
||||
@@ -183,7 +181,15 @@ class task_log extends base {
|
||||
->set_type(column::TYPE_FLOAT)
|
||||
->add_field("{$tablealias}.timeend - {$tablealias}.timestart", 'duration')
|
||||
->set_is_sortable(true)
|
||||
->add_callback([format::class, 'format_time'], 2);
|
||||
->add_callback(static function(float $value): string {
|
||||
$duration = round($value, 2);
|
||||
if (empty($duration)) {
|
||||
// The format_time function returns 'now' when the difference is exactly 0.
|
||||
// Note: format_time performs concatenation in exactly this fashion so we should do this for consistency.
|
||||
return '0 ' . get_string('secs', 'moodle');
|
||||
}
|
||||
return format_time($duration);
|
||||
});
|
||||
|
||||
// Hostname column.
|
||||
$columns[] = (new column(
|
||||
@@ -203,8 +209,11 @@ class task_log extends base {
|
||||
$this->get_entity_name()
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->set_type(column::TYPE_INTEGER)
|
||||
->add_field("{$tablealias}.pid")
|
||||
->set_is_sortable(true);
|
||||
->set_is_sortable(true)
|
||||
// Although this is an integer column, it doesn't make sense to perform numeric aggregation on it.
|
||||
->set_disabled_aggregation(['avg', 'count', 'countdistinct', 'max', 'min', 'sum']);
|
||||
|
||||
// Database column.
|
||||
$columns[] = (new column(
|
||||
@@ -213,14 +222,17 @@ class task_log extends base {
|
||||
$this->get_entity_name()
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->set_type(column::TYPE_INTEGER)
|
||||
->add_fields("{$tablealias}.dbreads, {$tablealias}.dbwrites")
|
||||
->set_is_sortable(true, ["{$tablealias}.dbreads", "{$tablealias}.dbwrites"])
|
||||
->add_callback(static function($value, stdClass $row): string {
|
||||
->add_callback(static function(int $value, stdClass $row): string {
|
||||
$output = '';
|
||||
$output .= \html_writer::div(get_string('task_stats:dbreads', 'admin', $row->dbreads));
|
||||
$output .= \html_writer::div(get_string('task_stats:dbwrites', 'admin', $row->dbwrites));
|
||||
return $output;
|
||||
});
|
||||
})
|
||||
// Although this is an integer column, it doesn't make sense to perform numeric aggregation on it.
|
||||
->set_disabled_aggregation(['avg', 'count', 'countdistinct', 'max', 'min', 'sum']);
|
||||
|
||||
// Database reads column.
|
||||
$columns[] = (new column(
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
<?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/>.
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace core_admin\reportbuilder\local\filters;
|
||||
|
||||
use core\context\system;
|
||||
use core_course_category;
|
||||
use MoodleQuickForm;
|
||||
use core_reportbuilder\local\filters\base;
|
||||
use core_reportbuilder\local\helpers\database;
|
||||
|
||||
/**
|
||||
* Course role report filter (by role, category, course)
|
||||
*
|
||||
* The provided filter field SQL must refer/return an expression for the user ID (e.g. "{$user}.id")
|
||||
*
|
||||
* @package core_admin
|
||||
* @copyright 2023 Paul Holden <paulh@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class courserole extends base {
|
||||
|
||||
/**
|
||||
* Setup form
|
||||
*
|
||||
* @param MoodleQuickForm $mform
|
||||
*/
|
||||
public function setup_form(MoodleQuickForm $mform): void {
|
||||
$elements = [];
|
||||
|
||||
// Role.
|
||||
$elements['role'] = $mform->createElement('select', "{$this->name}_role", get_string('rolefullname', 'core_role'),
|
||||
[0 => get_string('anyrole', 'core_filters')] + get_default_enrol_roles(system::instance()));
|
||||
|
||||
// Category.
|
||||
$elements['category'] = $mform->createElement('select', "{$this->name}_category", get_string('category'),
|
||||
[0 => get_string('anycategory', 'core_filters')] + core_course_category::make_categories_list());
|
||||
|
||||
// Course.
|
||||
$elements['course'] = $mform->createElement('text', "{$this->name}_course", get_string('shortnamecourse'));
|
||||
$mform->setType("{$this->name}_course", PARAM_RAW_TRIMMED);
|
||||
|
||||
$mform->addGroup($elements, "{$this->name}_group", $this->get_header(), '', false)
|
||||
->setHiddenLabel(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return filter SQL
|
||||
*
|
||||
* @param array $values
|
||||
* @return array
|
||||
*/
|
||||
public function get_sql_filter(array $values): array {
|
||||
[$fieldsql, $params] = $this->filter->get_field_sql_and_params();
|
||||
|
||||
[$contextalias, $rolealias, $coursealias] = database::generate_aliases(3);
|
||||
[$roleparam, $categoryparam, $courseparam] = database::generate_param_names(3);
|
||||
|
||||
// Role.
|
||||
$role = (int) ($values["{$this->name}_role"] ?? 0);
|
||||
if ($role > 0) {
|
||||
$selects[] = "{$rolealias}.roleid = :{$roleparam}";
|
||||
$params[$roleparam] = $role;
|
||||
}
|
||||
|
||||
// Category.
|
||||
$category = (int) ($values["{$this->name}_category"] ?? 0);
|
||||
if ($category > 0) {
|
||||
$selects[] = "{$coursealias}.category = :{$categoryparam}";
|
||||
$params[$categoryparam] = $category;
|
||||
}
|
||||
|
||||
// Course.
|
||||
$course = trim($values["{$this->name}_course"] ?? '');
|
||||
if ($course !== '') {
|
||||
$selects[] = "{$coursealias}.shortname = :{$courseparam}";
|
||||
$params[$courseparam] = $course;
|
||||
}
|
||||
|
||||
// Filter values are not set.
|
||||
if (empty($selects)) {
|
||||
return ['', []];
|
||||
}
|
||||
|
||||
return ["{$fieldsql} IN (
|
||||
SELECT {$rolealias}.userid
|
||||
FROM {role_assignments} {$rolealias}
|
||||
JOIN {context} {$contextalias} ON {$contextalias}.id = {$rolealias}.contextid AND {$contextalias}.contextlevel = 50
|
||||
JOIN {course} {$coursealias} ON {$coursealias}.id = {$contextalias}.instanceid
|
||||
WHERE " . implode(' AND ', $selects) . "
|
||||
)", $params];
|
||||
}
|
||||
}
|
||||
@@ -20,12 +20,10 @@ use context_system;
|
||||
use core_admin\reportbuilder\local\entities\task_log;
|
||||
use core_reportbuilder\local\entities\user;
|
||||
use core_reportbuilder\local\report\action;
|
||||
use core_reportbuilder\system_report;
|
||||
use html_writer;
|
||||
use lang_string;
|
||||
use moodle_url;
|
||||
use pix_icon;
|
||||
use stdClass;
|
||||
use core_reportbuilder\system_report;
|
||||
|
||||
/**
|
||||
* Task logs system report class implementation
|
||||
@@ -91,9 +89,7 @@ class task_logs extends system_report {
|
||||
* unique identifier
|
||||
*/
|
||||
public function add_columns(): void {
|
||||
$entitymainalias = $this->get_entity('task_log')->get_table_alias('task_log');
|
||||
|
||||
$this->add_columns_from_entities([
|
||||
$columns = [
|
||||
'task_log:name',
|
||||
'task_log:type',
|
||||
'user:fullname',
|
||||
@@ -103,18 +99,14 @@ class task_logs extends system_report {
|
||||
'task_log:pid',
|
||||
'task_log:database',
|
||||
'task_log:result',
|
||||
]);
|
||||
];
|
||||
|
||||
// Wrap the task name in a link.
|
||||
$this->get_column('task_log:name')
|
||||
->add_field("{$entitymainalias}.id")
|
||||
->add_callback(static function(string $output, stdClass $row): string {
|
||||
return html_writer::link(new moodle_url('/admin/tasklogs.php', ['logid' => $row->id]), $output);
|
||||
});
|
||||
$this->add_columns_from_entities($columns);
|
||||
|
||||
// Rename the user fullname column.
|
||||
$this->get_column('user:fullname')
|
||||
->set_title(new lang_string('user', 'admin'));
|
||||
// It's possible to override the display name of a column, if you don't want to use the value provided by the entity.
|
||||
if ($column = $this->get_column('user:fullname')) {
|
||||
$column->set_title(new lang_string('user', 'admin'));
|
||||
}
|
||||
|
||||
// It's possible to set a default initial sort direction for one column.
|
||||
$this->set_initial_sort_column('task_log:starttime', SORT_DESC);
|
||||
@@ -127,14 +119,16 @@ class task_logs extends system_report {
|
||||
* unique identifier
|
||||
*/
|
||||
protected function add_filters(): void {
|
||||
$this->add_filters_from_entities([
|
||||
$filters = [
|
||||
'task_log:name',
|
||||
'task_log:type',
|
||||
'task_log:output',
|
||||
'task_log:result',
|
||||
'task_log:timestart',
|
||||
'task_log:duration',
|
||||
]);
|
||||
];
|
||||
|
||||
$this->add_filters_from_entities($filters);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -149,7 +143,7 @@ class task_logs extends system_report {
|
||||
new moodle_url('/admin/tasklogs.php', ['logid' => ':id']),
|
||||
new pix_icon('e/search', ''),
|
||||
[],
|
||||
false,
|
||||
true,
|
||||
new lang_string('view'),
|
||||
)));
|
||||
|
||||
|
||||
@@ -1,417 +0,0 @@
|
||||
<?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 core_admin\reportbuilder\local\systemreports;
|
||||
|
||||
use core_admin\reportbuilder\local\filters\courserole;
|
||||
use core\context\system;
|
||||
use core_cohort\reportbuilder\local\entities\cohort;
|
||||
use core_cohort\reportbuilder\local\entities\cohort_member;
|
||||
use core_reportbuilder\local\entities\user;
|
||||
use core_reportbuilder\local\filters\boolean_select;
|
||||
use core_reportbuilder\local\helpers\database;
|
||||
use core_reportbuilder\local\helpers\user_profile_fields;
|
||||
use core_reportbuilder\local\report\action;
|
||||
use core_reportbuilder\local\report\filter;
|
||||
use core_reportbuilder\system_report;
|
||||
use core_role\reportbuilder\local\entities\role;
|
||||
use core_user\fields;
|
||||
use lang_string;
|
||||
use moodle_url;
|
||||
use pix_icon;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->libdir.'/adminlib.php');
|
||||
require_once($CFG->libdir.'/authlib.php');
|
||||
require_once($CFG->libdir.'/enrollib.php');
|
||||
require_once($CFG->dirroot.'/user/lib.php');
|
||||
|
||||
/**
|
||||
* Browse users system report class implementation
|
||||
*
|
||||
* @package core_admin
|
||||
* @copyright 2023 David Carrillo <davidmc@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class users extends system_report {
|
||||
|
||||
/**
|
||||
* Initialise report, we need to set the main table, load our entities and set columns/filters
|
||||
*/
|
||||
protected function initialise(): void {
|
||||
global $CFG;
|
||||
|
||||
// Our main entity, it contains all of the column definitions that we need.
|
||||
$entityuser = new user();
|
||||
$entityuseralias = $entityuser->get_table_alias('user');
|
||||
|
||||
$this->set_main_table('user', $entityuseralias);
|
||||
$this->add_entity($entityuser);
|
||||
|
||||
// Any columns required by actions should be defined here to ensure they're always available.
|
||||
$fullnamefields = array_map(fn($field) => "{$entityuseralias}.{$field}", fields::get_name_fields());
|
||||
$this->add_base_fields("{$entityuseralias}.id, {$entityuseralias}.confirmed, {$entityuseralias}.mnethostid,
|
||||
{$entityuseralias}.suspended, {$entityuseralias}.username, " . implode(', ', $fullnamefields));
|
||||
|
||||
if ($this->get_parameter('withcheckboxes', false, PARAM_BOOL)) {
|
||||
$canviewfullnames = has_capability('moodle/site:viewfullnames', \context_system::instance());
|
||||
$this->set_checkbox_toggleall(static function(\stdClass $row) use ($canviewfullnames): array {
|
||||
return [$row->id, fullname($row, $canviewfullnames)];
|
||||
});
|
||||
}
|
||||
|
||||
$paramguest = database::generate_param_name();
|
||||
$this->add_base_condition_sql("{$entityuseralias}.deleted <> 1 AND {$entityuseralias}.id <> :{$paramguest}",
|
||||
[$paramguest => $CFG->siteguest]);
|
||||
|
||||
$entitycohortmember = new cohort_member();
|
||||
$entitycohortmemberalias = $entitycohortmember->get_table_alias('cohort_members');
|
||||
$this->add_entity($entitycohortmember
|
||||
->add_joins($entitycohortmember->get_joins())
|
||||
->add_join("LEFT JOIN {cohort_members} {$entitycohortmemberalias}
|
||||
ON {$entityuseralias}.id = {$entitycohortmemberalias}.userid")
|
||||
);
|
||||
|
||||
$entitycohort = new cohort();
|
||||
$entitycohortalias = $entitycohort->get_table_alias('cohort');
|
||||
$this->add_entity($entitycohort
|
||||
->add_joins($entitycohort->get_joins())
|
||||
->add_joins($entitycohortmember->get_joins())
|
||||
->add_join("LEFT JOIN {cohort} {$entitycohortalias}
|
||||
ON {$entitycohortalias}.id = {$entitycohortmemberalias}.cohortid")
|
||||
);
|
||||
|
||||
// Join the role entity (Needed for the system role filter).
|
||||
$roleentity = new role();
|
||||
$role = $roleentity->get_table_alias('role');
|
||||
$this->add_entity($roleentity
|
||||
->add_join("LEFT JOIN (
|
||||
SELECT DISTINCT r0.id, ras.userid
|
||||
FROM {role} r0
|
||||
JOIN {role_assignments} ras ON ras.roleid = r0.id
|
||||
WHERE ras.contextid = ".SYSCONTEXTID."
|
||||
) {$role} ON {$role}.userid = {$entityuseralias}.id")
|
||||
);
|
||||
|
||||
// Now we can call our helper methods to add the content we want to include in the report.
|
||||
$this->add_columns();
|
||||
$this->add_filters();
|
||||
$this->add_actions();
|
||||
|
||||
// Set if report can be downloaded.
|
||||
$this->set_downloadable(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates access to view this report
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function can_view(): bool {
|
||||
return has_any_capability(['moodle/user:update', 'moodle/user:delete'], system::instance());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the columns we want to display in the report
|
||||
*
|
||||
* They are all provided by the entities we previously added in the {@see initialise} method, referencing each by their
|
||||
* unique identifier
|
||||
*/
|
||||
public function add_columns(): void {
|
||||
$entityuser = $this->get_entity('user');
|
||||
$entityuseralias = $entityuser->get_table_alias('user');
|
||||
|
||||
$this->add_column($entityuser->get_column('fullnamewithpicturelink'));
|
||||
|
||||
// Include identity field columns.
|
||||
$identitycolumns = $entityuser->get_identity_columns($this->get_context());
|
||||
foreach ($identitycolumns as $identitycolumn) {
|
||||
$this->add_column($identitycolumn);
|
||||
}
|
||||
|
||||
// Add "Last access" column.
|
||||
$this->add_column(($entityuser->get_column('lastaccess'))
|
||||
->set_callback(static function ($value, \stdClass $row): string {
|
||||
if ($row->lastaccess) {
|
||||
return format_time(time() - $row->lastaccess);
|
||||
}
|
||||
return get_string('never');
|
||||
})
|
||||
);
|
||||
|
||||
if ($column = $this->get_column('user:fullnamewithpicturelink')) {
|
||||
$column
|
||||
->add_fields("{$entityuseralias}.suspended, {$entityuseralias}.confirmed")
|
||||
->add_callback(static function(string $fullname, \stdClass $row): string {
|
||||
if ($row->suspended) {
|
||||
$fullname .= ' ' . \html_writer::tag('span', get_string('suspended', 'moodle'),
|
||||
['class' => 'badge badge-secondary ms-1']);
|
||||
}
|
||||
if (!$row->confirmed) {
|
||||
$fullname .= ' ' . \html_writer::tag('span', get_string('confirmationpending', 'admin'),
|
||||
['class' => 'badge badge-danger ms-1']);
|
||||
}
|
||||
return $fullname;
|
||||
});
|
||||
}
|
||||
|
||||
$this->set_initial_sort_column('user:fullnamewithpicturelink', SORT_ASC);
|
||||
$this->set_default_no_results_notice(new lang_string('nousersfound', 'moodle'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the filters we want to display in the report
|
||||
*
|
||||
* They are all provided by the entities we previously added in the {@see initialise} method, referencing each by their
|
||||
* unique identifier
|
||||
*/
|
||||
protected function add_filters(): void {
|
||||
$entityuser = $this->get_entity('user');
|
||||
$entityuseralias = $entityuser->get_table_alias('user');
|
||||
|
||||
$filters = [
|
||||
'user:fullname',
|
||||
'user:firstname',
|
||||
'user:lastname',
|
||||
'user:username',
|
||||
'user:idnumber',
|
||||
'user:email',
|
||||
'user:department',
|
||||
'user:institution',
|
||||
'user:city',
|
||||
'user:country',
|
||||
'user:confirmed',
|
||||
'user:suspended',
|
||||
'user:timecreated',
|
||||
'user:lastaccess',
|
||||
'user:timemodified',
|
||||
'user:auth',
|
||||
'user:lastip',
|
||||
'cohort:idnumber',
|
||||
'role:name',
|
||||
];
|
||||
$this->add_filters_from_entities($filters);
|
||||
|
||||
// Enrolled in any course filter.
|
||||
$ue = database::generate_alias();
|
||||
[$now1, $now2] = database::generate_param_names(2);
|
||||
$now = time();
|
||||
$sql = "CASE WHEN ({$entityuseralias}.id IN (
|
||||
SELECT userid FROM {user_enrolments} {$ue}
|
||||
WHERE {$ue}.status = " . ENROL_USER_ACTIVE . "
|
||||
AND ({$ue}.timestart = 0 OR {$ue}.timestart < :{$now1})
|
||||
AND ({$ue}.timeend = 0 OR {$ue}.timeend > :{$now2})
|
||||
)) THEN 1 ELSE 0 END";
|
||||
|
||||
$this->add_filter((new filter(
|
||||
boolean_select::class,
|
||||
'enrolledinanycourse',
|
||||
new lang_string('anycourses', 'filters'),
|
||||
$this->get_entity('user')->get_entity_name(),
|
||||
))
|
||||
->set_field_sql($sql, [
|
||||
$now1 => $now,
|
||||
$now2 => $now,
|
||||
])
|
||||
);
|
||||
|
||||
// Course role filter.
|
||||
$this->add_filter((new filter(
|
||||
courserole::class,
|
||||
'courserole',
|
||||
new lang_string('courserole', 'filters'),
|
||||
$this->get_entity('user')->get_entity_name(),
|
||||
))
|
||||
->set_field_sql("{$entityuseralias}.id")
|
||||
);
|
||||
|
||||
// Add user profile fields filters.
|
||||
$userprofilefields = new user_profile_fields($entityuseralias . '.id', $entityuser->get_entity_name());
|
||||
foreach ($userprofilefields->get_filters() as $filter) {
|
||||
$this->add_filter($filter);
|
||||
}
|
||||
|
||||
// Set options for system role filter.
|
||||
if ($filter = $this->get_filter('role:name')) {
|
||||
$filter
|
||||
->set_header(new lang_string('globalrole', 'role'))
|
||||
->set_options(get_assignable_roles(system::instance()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the system report actions. An extra column will be appended to each row, containing all actions added here
|
||||
*
|
||||
* Note the use of ":id" placeholder which will be substituted according to actual values in the row
|
||||
*/
|
||||
protected function add_actions(): void {
|
||||
global $DB, $USER;
|
||||
|
||||
$contextsystem = system::instance();
|
||||
|
||||
// Action to edit users.
|
||||
$this->add_action((new action(
|
||||
new moodle_url('/user/editadvanced.php', ['id' => ':id', 'course' => get_site()->id]),
|
||||
new pix_icon('t/edit', ''),
|
||||
[],
|
||||
false,
|
||||
new lang_string('edit', 'moodle'),
|
||||
))->add_callback(static function(\stdclass $row) use ($USER, $contextsystem): bool {
|
||||
return has_capability('moodle/user:update', $contextsystem) && (is_siteadmin($USER) || !is_siteadmin($row));
|
||||
}));
|
||||
|
||||
// Action to suspend users (non mnet remote users).
|
||||
$this->add_action((new action(
|
||||
new moodle_url('/admin/user.php', ['suspend' => ':id', 'sesskey' => sesskey()]),
|
||||
new pix_icon('t/show', ''),
|
||||
[],
|
||||
false,
|
||||
new lang_string('suspenduser', 'admin'),
|
||||
))->add_callback(static function(\stdclass $row) use ($USER, $contextsystem): bool {
|
||||
return has_capability('moodle/user:update', $contextsystem) && !$row->suspended && !is_mnet_remote_user($row) &&
|
||||
!($row->id == $USER->id || is_siteadmin($row));
|
||||
}));
|
||||
|
||||
// Action to unsuspend users (non mnet remote users).
|
||||
$this->add_action((new action(
|
||||
new moodle_url('/admin/user.php', ['unsuspend' => ':id', 'sesskey' => sesskey()]),
|
||||
new pix_icon('t/hide', ''),
|
||||
[],
|
||||
false,
|
||||
new lang_string('unsuspenduser', 'admin'),
|
||||
))->add_callback(static function(\stdclass $row) use ($USER, $contextsystem): bool {
|
||||
return has_capability('moodle/user:update', $contextsystem) && $row->suspended && !is_mnet_remote_user($row) &&
|
||||
!($row->id == $USER->id || is_siteadmin($row));
|
||||
}));
|
||||
|
||||
// Action to unlock users (non mnet remote users).
|
||||
$this->add_action((new action(
|
||||
new moodle_url('/admin/user.php', ['unlock' => ':id', 'sesskey' => sesskey()]),
|
||||
new pix_icon('t/unlock', ''),
|
||||
[],
|
||||
false,
|
||||
new lang_string('unlockaccount', 'admin'),
|
||||
))->add_callback(static function(\stdclass $row) use ($contextsystem): bool {
|
||||
return has_capability('moodle/user:update', $contextsystem) && !is_mnet_remote_user($row) &&
|
||||
login_is_lockedout($row);
|
||||
}));
|
||||
|
||||
// Action to suspend users (mnet remote users).
|
||||
$this->add_action((new action(
|
||||
new moodle_url('/admin/user.php', ['acl' => ':id', 'sesskey' => sesskey(), 'accessctrl' => 'deny']),
|
||||
new pix_icon('t/show', ''),
|
||||
[],
|
||||
false,
|
||||
new lang_string('denyaccess', 'mnet'),
|
||||
))->add_callback(static function(\stdclass $row) use ($DB, $contextsystem): bool {
|
||||
if (!$accessctrl = $DB->get_field(table: 'mnet_sso_access_control', return: 'accessctrl',
|
||||
conditions: ['username' => $row->username, 'mnet_host_id' => $row->mnethostid]
|
||||
)) {
|
||||
$accessctrl = 'allow';
|
||||
}
|
||||
|
||||
return has_capability('moodle/user:update', $contextsystem) && !$row->suspended &&
|
||||
is_mnet_remote_user($row) && $accessctrl == 'allow';
|
||||
}));
|
||||
|
||||
// Action to unsuspend users (mnet remote users).
|
||||
$this->add_action((new action(
|
||||
new moodle_url('/admin/user.php', ['acl' => ':id', 'sesskey' => sesskey(), 'accessctrl' => 'allow']),
|
||||
new pix_icon('t/hide', ''),
|
||||
[],
|
||||
false,
|
||||
new lang_string('allowaccess', 'mnet'),
|
||||
))->add_callback(static function(\stdclass $row) use ($DB, $contextsystem): bool {
|
||||
if (!$accessctrl = $DB->get_field(table: 'mnet_sso_access_control', return: 'accessctrl',
|
||||
conditions: ['username' => $row->username, 'mnet_host_id' => $row->mnethostid]
|
||||
)) {
|
||||
$accessctrl = 'allow';
|
||||
}
|
||||
|
||||
return has_capability('moodle/user:update', $contextsystem) && !$row->suspended &&
|
||||
is_mnet_remote_user($row) && $accessctrl == 'deny';
|
||||
}));
|
||||
|
||||
// Action to delete users.
|
||||
$this->add_action((new action(
|
||||
new moodle_url('/admin/user.php', ['delete' => ':id', 'sesskey' => sesskey()]),
|
||||
new pix_icon('t/delete', ''),
|
||||
[
|
||||
'class' => 'text-danger',
|
||||
'data-modal' => 'confirmation',
|
||||
'data-modal-title-str' => json_encode(['deleteuser', 'admin']),
|
||||
'data-modal-content-str' => ':deletestr',
|
||||
'data-modal-yes-button-str' => json_encode(['delete', 'core']),
|
||||
'data-modal-destination' => ':deleteurl',
|
||||
|
||||
],
|
||||
false,
|
||||
new lang_string('delete', 'moodle'),
|
||||
))->add_callback(static function(\stdclass $row) use ($USER, $contextsystem): bool {
|
||||
|
||||
// Populate deletion modal attributes.
|
||||
$row->deletestr = json_encode([
|
||||
'deletecheckfull',
|
||||
'moodle',
|
||||
fullname($row, true),
|
||||
]);
|
||||
|
||||
$row->deleteurl = (new moodle_url('/admin/user.php', [
|
||||
'delete' => $row->id,
|
||||
'confirm' => md5($row->id),
|
||||
'sesskey' => sesskey(),
|
||||
]))->out(false);
|
||||
|
||||
return has_capability('moodle/user:delete', $contextsystem) &&
|
||||
!is_mnet_remote_user($row) && $row->id != $USER->id && !is_siteadmin($row);
|
||||
}));
|
||||
|
||||
$this->add_action_divider();
|
||||
|
||||
// Action to confirm users.
|
||||
$this->add_action((new action(
|
||||
new moodle_url('/admin/user.php', ['confirmuser' => ':id', 'sesskey' => sesskey()]),
|
||||
new pix_icon('t/check', ''),
|
||||
[],
|
||||
false,
|
||||
new lang_string('confirmaccount', 'moodle'),
|
||||
))->add_callback(static function(\stdclass $row) use ($contextsystem): bool {
|
||||
return has_capability('moodle/user:update', $contextsystem) && !$row->confirmed;
|
||||
}));
|
||||
|
||||
// Action to resend email.
|
||||
$this->add_action((new action(
|
||||
new moodle_url('/admin/user.php', ['resendemail' => ':id', 'sesskey' => sesskey()]),
|
||||
new pix_icon('t/email', ''),
|
||||
[],
|
||||
false,
|
||||
new lang_string('resendemail', 'moodle'),
|
||||
))->add_callback(static function(\stdclass $row): bool {
|
||||
return !$row->confirmed && !is_mnet_remote_user($row);
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Row class
|
||||
*
|
||||
* @param \stdClass $row
|
||||
* @return string
|
||||
*/
|
||||
public function get_row_class(\stdClass $row): string {
|
||||
return $row->suspended ? 'text-muted' : '';
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
<?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 core_admin\table;
|
||||
|
||||
use moodle_url;
|
||||
use stdClass;
|
||||
use html_writer;
|
||||
use get_string_manager;
|
||||
|
||||
/**
|
||||
* Availability admin settings.
|
||||
*
|
||||
* @package core_admin
|
||||
* @copyright 2024 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class availability_management_table extends plugin_management_table {
|
||||
|
||||
protected function get_table_id(): string {
|
||||
return 'availabilityconditions_administration_table';
|
||||
}
|
||||
|
||||
protected function get_plugintype(): string {
|
||||
return 'availability';
|
||||
}
|
||||
|
||||
protected function get_action_url(array $params = []): moodle_url {
|
||||
return new moodle_url('/admin/tool/availabilityconditions/', $params);
|
||||
}
|
||||
|
||||
public function guess_base_url(): void {
|
||||
$this->define_baseurl(
|
||||
new moodle_url('/admin/tool/availabilityconditions/')
|
||||
);
|
||||
}
|
||||
|
||||
protected function get_column_list(): array {
|
||||
return [
|
||||
'name' => get_string('plugin'),
|
||||
'version' => get_string('version'),
|
||||
'enabled' => get_string('enabled', 'admin'),
|
||||
'defaultdisplaymode' => get_string('defaultdisplaymode', 'tool_availabilityconditions'),
|
||||
];
|
||||
}
|
||||
|
||||
public function setup(): void {
|
||||
$this->set_attribute('id', 'availabilityconditions_administration_table');
|
||||
$this->set_attribute('class', 'admintable generaltable');
|
||||
parent::setup();
|
||||
}
|
||||
|
||||
protected function col_name(stdClass $row): string {
|
||||
return html_writer::span(
|
||||
get_string('pluginname', 'availability_' . $row->plugininfo->name)
|
||||
);
|
||||
}
|
||||
|
||||
protected function col_defaultdisplaymode(stdClass $row): string {
|
||||
global $OUTPUT, $CFG;
|
||||
$displaymode = get_config('availability_' . $row->plugininfo->name, 'defaultdisplaymode') ? 'show' : 'hide';
|
||||
$paramsdisplaymode = [
|
||||
'sesskey' => sesskey(),
|
||||
'plugin' => $row->plugininfo->name,
|
||||
'displaymode' => $displaymode,
|
||||
];
|
||||
$urldisplaymode = new moodle_url('/' . $CFG->admin . '/tool/availabilityconditions/', $paramsdisplaymode);
|
||||
|
||||
return html_writer::link($urldisplaymode, $OUTPUT->pix_icon('t/' . $displaymode,
|
||||
get_string($displaymode)), ['class' => 'display-mode-' . $row->plugininfo->name]);
|
||||
}
|
||||
}
|
||||
@@ -35,29 +35,28 @@ class block_management_table extends \core_admin\table\plugin_management_table {
|
||||
/** @var stdClass[] A list of basic block data */
|
||||
protected array $blockdata;
|
||||
|
||||
/** @var array<string,int> A list of course counts */
|
||||
protected array $courseblocks;
|
||||
|
||||
public function __construct() {
|
||||
global $DB;
|
||||
parent::__construct();
|
||||
$this->undeletableblocktypes = \block_manager::get_undeletable_block_types();
|
||||
|
||||
$sql = 'SELECT b.name,
|
||||
b.id,
|
||||
COUNT(DISTINCT binst.id) as totalcount
|
||||
FROM {block} b
|
||||
LEFT JOIN {block_instances} binst ON binst.blockname = b.name
|
||||
GROUP BY b.id,
|
||||
b.name
|
||||
ORDER BY b.name ASC';
|
||||
$this->blockdata = $DB->get_records_sql($sql);
|
||||
$sql = <<<EOF
|
||||
SELECT
|
||||
b.name,
|
||||
b.id,
|
||||
b.name,
|
||||
COUNT(DISTINCT binst.id) as totalcount,
|
||||
COUNT(DISTINCT bcinst.id) as courseviewcount
|
||||
FROM {block} b
|
||||
LEFT JOIN {block_instances} binst ON binst.blockname = b.name
|
||||
LEFT JOIN {block_instances} bcinst ON
|
||||
bcinst.blockname = b.name AND
|
||||
bcinst.pagetypepattern = 'course-view-*'
|
||||
GROUP BY b.id, b.name, b.visible, binst.blockname, bcinst.blockname
|
||||
ORDER BY b.name ASC
|
||||
EOF;
|
||||
|
||||
$sql = "SELECT blockname
|
||||
FROM {block_instances}
|
||||
WHERE pagetypepattern = 'course-view-*'
|
||||
GROUP BY blockname";
|
||||
$this->courseblocks = $DB->get_records_sql($sql);
|
||||
$this->blockdata = $DB->get_records_sql($sql);
|
||||
}
|
||||
|
||||
protected function get_plugintype(): string {
|
||||
@@ -103,10 +102,11 @@ class block_management_table extends \core_admin\table\plugin_management_table {
|
||||
*/
|
||||
protected function col_instances(stdClass $row): string {
|
||||
$blockdata = $this->blockdata[$row->plugininfo->name];
|
||||
if (array_key_exists($blockdata->name, $this->courseblocks)) {
|
||||
if ($blockdata->courseviewcount > 0) {
|
||||
return html_writer::link(
|
||||
new moodle_url('/course/search.php', [
|
||||
'blocklist' => $blockdata->id,
|
||||
'blocklist' => $row->plugininfo->name,
|
||||
'sesskey' => sesskey(),
|
||||
]),
|
||||
$blockdata->totalcount,
|
||||
);
|
||||
|
||||
@@ -1,281 +0,0 @@
|
||||
<?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 core_admin\table;
|
||||
|
||||
use core_plugin_manager;
|
||||
use flexible_table;
|
||||
use html_writer;
|
||||
use stdClass;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
require_once("{$CFG->libdir}/tablelib.php");
|
||||
|
||||
/**
|
||||
* Plugin Management table.
|
||||
*
|
||||
* @package core_admin
|
||||
* @copyright 2023 Andrew Lyons <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class hook_list_table extends flexible_table {
|
||||
|
||||
/** @var \core\plugininfo\base[] The plugin list */
|
||||
protected array $plugins = [];
|
||||
|
||||
/** @var int The number of enabled plugins of this type */
|
||||
protected int $enabledplugincount = 0;
|
||||
|
||||
/** @var core_plugin_manager */
|
||||
protected core_plugin_manager $pluginmanager;
|
||||
|
||||
/** @var string The plugininfo class for this plugintype */
|
||||
protected string $plugininfoclass;
|
||||
|
||||
/** @var stdClass[] The list of emitted hooks with metadata */
|
||||
protected array $emitters;
|
||||
|
||||
public function __construct() {
|
||||
global $CFG;
|
||||
|
||||
$this->define_baseurl('/admin/hooks.php');
|
||||
parent::__construct('core_admin-hook_list_table');
|
||||
|
||||
// Add emitted hooks.
|
||||
$this->emitters = \core\hook\manager::discover_known_hooks();
|
||||
|
||||
$this->setup_column_configuration();
|
||||
$this->setup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the column configuration for this table.
|
||||
*/
|
||||
protected function setup_column_configuration(): void {
|
||||
$columnlist = [
|
||||
'details' => get_string('hookname', 'core_admin'),
|
||||
'callbacks' => get_string('hookcallbacks', 'core_admin'),
|
||||
'deprecates' => get_string('hookdeprecates', 'core_admin'),
|
||||
];
|
||||
$this->define_columns(array_keys($columnlist));
|
||||
$this->define_headers(array_values($columnlist));
|
||||
|
||||
$columnswithhelp = [
|
||||
'callbacks' => new \help_icon('hookcallbacks', 'admin'),
|
||||
];
|
||||
$columnhelp = array_map(function (string $column) use ($columnswithhelp): ?\renderable {
|
||||
if (array_key_exists($column, $columnswithhelp)) {
|
||||
return $columnswithhelp[$column];
|
||||
}
|
||||
|
||||
return null;
|
||||
}, array_keys($columnlist));
|
||||
$this->define_help_for_headers($columnhelp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the table.
|
||||
*/
|
||||
public function out(): void {
|
||||
// All hook consumers referenced from the db/hooks.php files.
|
||||
$hookmanager = \core\di::get(\core\hook\manager::class);
|
||||
$allhooks = (array)$hookmanager->get_all_callbacks();
|
||||
|
||||
// Add any unused hooks.
|
||||
foreach (array_keys($this->emitters) as $classname) {
|
||||
if (isset($allhooks[$classname])) {
|
||||
continue;
|
||||
}
|
||||
$allhooks[$classname] = [];
|
||||
}
|
||||
|
||||
// Order rows by hook name, putting core first.
|
||||
\core_collator::ksort($allhooks);
|
||||
$corehooks = [];
|
||||
foreach ($allhooks as $classname => $consumers) {
|
||||
if (str_starts_with($classname, 'core\\')) {
|
||||
$corehooks[$classname] = $consumers;
|
||||
unset($allhooks[$classname]);
|
||||
}
|
||||
}
|
||||
$allhooks = array_merge($corehooks, $allhooks);
|
||||
|
||||
foreach ($allhooks as $classname => $consumers) {
|
||||
$this->add_data_keyed(
|
||||
$this->format_row((object) [
|
||||
'classname' => $classname,
|
||||
'callbacks' => $consumers,
|
||||
]),
|
||||
$this->get_row_class($classname),
|
||||
);
|
||||
}
|
||||
|
||||
$this->finish_output(false);
|
||||
}
|
||||
|
||||
protected function col_details(stdClass $row): string {
|
||||
return $row->classname .
|
||||
$this->get_description($row) .
|
||||
html_writer::div($this->get_tags_for_row($row));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the name column content.
|
||||
*
|
||||
* @param stdClass $row
|
||||
* @return string
|
||||
*/
|
||||
protected function get_description(stdClass $row): string {
|
||||
if (!array_key_exists($row->classname, $this->emitters)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return html_writer::tag(
|
||||
'small',
|
||||
clean_text(markdown_to_html($this->emitters[$row->classname]['description']), FORMAT_HTML),
|
||||
);
|
||||
}
|
||||
|
||||
protected function col_deprecates(stdClass $row): string {
|
||||
if (!class_exists($row->classname)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$deprecates = \core\hook\manager::get_replaced_callbacks($row->classname);
|
||||
if (count($deprecates) === 0) {
|
||||
return '';
|
||||
}
|
||||
$content = html_writer::start_tag('ul');
|
||||
|
||||
foreach ($deprecates as $deprecatedmethod) {
|
||||
$content .= html_writer::tag('li', $deprecatedmethod);
|
||||
}
|
||||
$content .= html_writer::end_tag('ul');
|
||||
return $content;
|
||||
}
|
||||
|
||||
protected function col_callbacks(stdClass $row): string {
|
||||
global $CFG;
|
||||
|
||||
$hookclass = $row->classname;
|
||||
$cbinfo = [];
|
||||
foreach ($row->callbacks as $definition) {
|
||||
$iscallable = is_callable($definition['callback'], false, $callbackname);
|
||||
$isoverridden = isset($CFG->hooks_callback_overrides[$hookclass][$definition['callback']]);
|
||||
$info = "{$callbackname} ({$definition['priority']})";
|
||||
if (!$iscallable) {
|
||||
$info .= ' ';
|
||||
$info .= $this->get_tag(
|
||||
get_string('error'),
|
||||
'danger',
|
||||
get_string('hookcallbacknotcallable', 'core_admin', $callbackname),
|
||||
);
|
||||
}
|
||||
if ($isoverridden) {
|
||||
// The lang string meaning should be close enough here.
|
||||
$info .= $this->get_tag(
|
||||
get_string('hookconfigoverride', 'core_admin'),
|
||||
'warning',
|
||||
get_string('hookconfigoverride_help', 'core_admin'),
|
||||
);
|
||||
}
|
||||
|
||||
$cbinfo[] = $info;
|
||||
}
|
||||
|
||||
if ($cbinfo) {
|
||||
$output = html_writer::start_tag('ol');
|
||||
foreach ($cbinfo as $callback) {
|
||||
$class = '';
|
||||
if ($definition['disabled']) {
|
||||
$class = 'dimmed_text';
|
||||
}
|
||||
$output .= html_writer::tag('li', $callback, ['class' => $class]);
|
||||
}
|
||||
$output .= html_writer::end_tag('ol');
|
||||
return $output;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the HTML to display the badge with tooltip.
|
||||
*
|
||||
* @param string $tag The main text to display
|
||||
* @param null|string $type The pill type
|
||||
* @param null|string $tooltip The content of the tooltip
|
||||
* @return string
|
||||
*/
|
||||
protected function get_tag(
|
||||
string $tag,
|
||||
?string $type = null,
|
||||
?string $tooltip = null,
|
||||
): string {
|
||||
$attributes = [];
|
||||
|
||||
if ($type === null) {
|
||||
$type = 'info';
|
||||
}
|
||||
|
||||
if ($tooltip) {
|
||||
$attributes['data-toggle'] = 'tooltip';
|
||||
$attributes['title'] = $tooltip;
|
||||
}
|
||||
return html_writer::span($tag, "badge badge-{$type}", $attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the code to display a set of tags for this table row.
|
||||
*
|
||||
* @param stdClass $row
|
||||
* @return string
|
||||
*/
|
||||
protected function get_tags_for_row(stdClass $row): string {
|
||||
if (!array_key_exists($row->classname, $this->emitters)) {
|
||||
// This hook has been defined in the db/hooks.php file
|
||||
// but does not refer to a hook in this version of Moodle.
|
||||
return $this->get_tag(
|
||||
get_string('hookunknown', 'core_admin'),
|
||||
'warning',
|
||||
get_string('hookunknown_desc', 'core_admin'),
|
||||
);
|
||||
}
|
||||
|
||||
if (!class_exists($row->classname)) {
|
||||
// This hook has been defined in a hook discovery agent, but the class it refers to could not be found.
|
||||
return $this->get_tag(
|
||||
get_string('hookclassmissing', 'core_admin'),
|
||||
'warning',
|
||||
get_string('hookclassmissing_desc', 'core_admin'),
|
||||
);
|
||||
}
|
||||
|
||||
$tags = $this->emitters[$row->classname]['tags'] ?? [];
|
||||
$taglist = array_map(function($tag): string {
|
||||
if (is_array($tag)) {
|
||||
return $this->get_tag(...$tag);
|
||||
}
|
||||
return $this->get_tag($tag, 'badge bg-info text-white');
|
||||
}, $tags);
|
||||
|
||||
return implode("\n", $taglist);
|
||||
}
|
||||
|
||||
protected function get_row_class(string $classname): string {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -303,29 +303,28 @@ abstract class plugin_management_table extends flexible_table implements dynamic
|
||||
global $OUTPUT;
|
||||
|
||||
$enabled = $row->plugininfo->is_enabled();
|
||||
if ($enabled) {
|
||||
$labelstr = get_string('disableplugin', 'core_admin', $row->plugininfo->displayname);
|
||||
} else {
|
||||
$labelstr = get_string('enableplugin', 'core_admin', $row->plugininfo->displayname);
|
||||
}
|
||||
|
||||
$params = [
|
||||
'id' => 'admin-toggle-' . $row->plugininfo->name,
|
||||
'checked' => $enabled,
|
||||
'dataattributes' => [
|
||||
'name' => 'id',
|
||||
'value' => $row->plugininfo->name,
|
||||
'toggle-method' => $this->get_toggle_service(),
|
||||
'action' => 'togglestate',
|
||||
'plugin' => $row->plugin,
|
||||
'state' => $enabled ? 1 : 0,
|
||||
],
|
||||
'title' => $labelstr,
|
||||
'label' => $labelstr,
|
||||
'labelclasses' => 'sr-only',
|
||||
'sesskey' => sesskey(),
|
||||
'plugin' => $row->plugininfo->name,
|
||||
'action' => $enabled ? 'disable' : 'enable',
|
||||
];
|
||||
|
||||
return $OUTPUT->render_from_template('core_admin/setting_configtoggle', $params);
|
||||
if ($enabled) {
|
||||
$icon = $OUTPUT->pix_icon('t/hide', get_string('disableplugin', 'core_admin', $row->plugininfo->displayname));
|
||||
} else {
|
||||
$icon = $OUTPUT->pix_icon('t/show', get_string('enableplugin', 'core_admin', $row->plugininfo->displayname));
|
||||
}
|
||||
|
||||
return html_writer::link(
|
||||
$this->get_action_url($params),
|
||||
$icon,
|
||||
[
|
||||
'data-toggle-method' => $this->get_toggle_service(),
|
||||
'data-action' => 'togglestate',
|
||||
'data-plugin' => $row->plugin,
|
||||
'data-state' => $enabled ? 1 : 0,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
protected function col_order(stdClass $row): string {
|
||||
@@ -501,15 +500,4 @@ abstract class plugin_management_table extends flexible_table implements dynamic
|
||||
protected function supports_ordering(): bool {
|
||||
return $this->plugininfoclass::plugintype_supports_ordering();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user has the capability to access this table.
|
||||
*
|
||||
* Default implementation for plugin management tables is to require 'moodle/site:config' capability
|
||||
*
|
||||
* @return bool Return true if capability check passed.
|
||||
*/
|
||||
public function has_capability(): bool {
|
||||
return has_capability('moodle/site:config', $this->get_context());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace core_admin\table;
|
||||
use moodle_url;
|
||||
|
||||
/**
|
||||
* Admin tool settings.
|
||||
* Tiny admin settings.
|
||||
*
|
||||
* @package core_admin
|
||||
* @copyright 2023 Andrew Lyons <andrew@nicols.co.uk>
|
||||
@@ -30,6 +30,12 @@ class tool_plugin_management_table extends \core_admin\table\plugin_management_t
|
||||
return 'tool';
|
||||
}
|
||||
|
||||
public function guess_base_url(): void {
|
||||
$this->define_baseurl(
|
||||
new moodle_url('/admin/tools.php')
|
||||
);
|
||||
}
|
||||
|
||||
protected function get_column_list(): array {
|
||||
$columns = parent::get_column_list();
|
||||
|
||||
@@ -39,6 +45,6 @@ class tool_plugin_management_table extends \core_admin\table\plugin_management_t
|
||||
}
|
||||
|
||||
protected function get_action_url(array $params = []): moodle_url {
|
||||
return new moodle_url('/admin/settings.php', array_merge(['section' => 'toolsmanagement'], $params));
|
||||
return new moodle_url('/admin/tool.php', $params);
|
||||
}
|
||||
}
|
||||
|
||||
+7
-23
@@ -33,7 +33,6 @@ require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
|
||||
list($options, $unrecognized) = cli_get_params(array(
|
||||
'courseid' => false,
|
||||
'courseshortname' => '',
|
||||
'cmid' => false,
|
||||
'destination' => '',
|
||||
'help' => false,
|
||||
), array('h' => 'help'));
|
||||
@@ -43,14 +42,13 @@ if ($unrecognized) {
|
||||
cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
|
||||
}
|
||||
|
||||
if ($options['help'] || !($options['courseid'] || $options['courseshortname'] || $options['cmid'])) {
|
||||
if ($options['help'] || !($options['courseid'] || $options['courseshortname'])) {
|
||||
$help = <<<EOL
|
||||
Perform backup of the given course or course module.
|
||||
Perform backup of the given course.
|
||||
|
||||
Options:
|
||||
--courseid=INTEGER Course ID for backup.
|
||||
--courseshortname=STRING Course shortname for backup. This option is ignored if courseid is set.
|
||||
--cmid=INTEGER Course module ID for backup. This option is ignored if courseid or courseshortname is set.
|
||||
--courseshortname=STRING Course shortname for backup.
|
||||
--destination=STRING Path where to store backup file. If not set the backup
|
||||
will be stored within the course backup file area.
|
||||
-h, --help Print out this help.
|
||||
@@ -78,26 +76,16 @@ if (!empty($dir)) {
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the course or course module exists.
|
||||
// Check that the course exists.
|
||||
if ($options['courseid']) {
|
||||
$course = $DB->get_record('course', array('id' => $options['courseid']), '*', MUST_EXIST);
|
||||
} else if ($options['courseshortname']) {
|
||||
$course = $DB->get_record('course', array('shortname' => $options['courseshortname']), '*', MUST_EXIST);
|
||||
} else if ($options['cmid']) {
|
||||
$cm = $DB->get_record('course_modules', ['id' => $options['cmid']], '*', MUST_EXIST);
|
||||
}
|
||||
|
||||
cli_heading('Performing backup...');
|
||||
if (!empty($course)) {
|
||||
$bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
|
||||
backup::INTERACTIVE_YES, backup::MODE_GENERAL, $admin->id);
|
||||
} else if (!empty($cm)) {
|
||||
$bc = new backup_controller(backup::TYPE_1ACTIVITY, $cm->id, backup::FORMAT_MOODLE,
|
||||
backup::INTERACTIVE_YES, backup::MODE_GENERAL, $admin->id);
|
||||
} else {
|
||||
throw new \moodle_exception('invalidoption');
|
||||
}
|
||||
|
||||
$bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
|
||||
backup::INTERACTIVE_YES, backup::MODE_GENERAL, $admin->id);
|
||||
// Set the default filename.
|
||||
$format = $bc->get_format();
|
||||
$type = $bc->get_type();
|
||||
@@ -125,11 +113,7 @@ if (!empty($dir)) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!empty($course)) {
|
||||
mtrace("Backup completed, the new file is listed in the backup area of the given course");
|
||||
} else {
|
||||
mtrace("Backup completed, the new file is listed in the backup area of the given module");
|
||||
}
|
||||
mtrace("Backup completed, the new file is listed in the backup area of the given course");
|
||||
}
|
||||
$bc->destroy();
|
||||
exit(0);
|
||||
@@ -39,6 +39,8 @@ if ($unrecognized) {
|
||||
cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
|
||||
}
|
||||
|
||||
// TODO: MDL-71421 - Remove the openssl alternative once sodium becomes a requirement in Moodle 4.2.
|
||||
|
||||
if ($options['help']) {
|
||||
echo "Generate secure key
|
||||
|
||||
@@ -52,7 +54,9 @@ may be manually installed on multiple servers.
|
||||
|
||||
Options:
|
||||
-h, --help Print out this help
|
||||
--method <method> Generate key for specified encryption method instead of default (sodium)
|
||||
--method <method> Generate key for specified encryption method instead of default.
|
||||
* sodium
|
||||
* openssl-aes-256-ctr
|
||||
|
||||
Example:
|
||||
php admin/cli/generate_key.php
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* This script allows to import a course from CLI.
|
||||
*
|
||||
* @package core
|
||||
* @subpackage cli
|
||||
* @author Tomo Tsuyuki <tomotsuyuki@catalyst-au.net>
|
||||
* @copyright 2023 Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
define('CLI_SCRIPT', 1);
|
||||
|
||||
require(__DIR__ . '/../../config.php');
|
||||
require_once($CFG->libdir . '/clilib.php');
|
||||
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
|
||||
require_once($CFG->dirroot . "/backup/util/includes/restore_includes.php");
|
||||
|
||||
list($options, $unrecognized) = cli_get_params([
|
||||
'srccourseid' => '',
|
||||
'srccmid' => '',
|
||||
'dstcourseid' => '',
|
||||
'showdebugging' => false,
|
||||
'help' => false,
|
||||
], [
|
||||
's' => 'showdebugging',
|
||||
'h' => 'help',
|
||||
]);
|
||||
|
||||
if ($unrecognized) {
|
||||
$unrecognized = implode("\n ", $unrecognized);
|
||||
cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
|
||||
}
|
||||
|
||||
if ($options['help'] ||
|
||||
!(($options['srccourseid'] && $options['dstcourseid'])
|
||||
|| ($options['srccmid'] && $options['dstcourseid']))) {
|
||||
$help = <<<EOL
|
||||
Import course or course module into provided course.
|
||||
|
||||
Options:
|
||||
--srccourseid=INT Source course ID to backup.
|
||||
--srccmid=INT Source course module ID to backup.
|
||||
--dstcourseid=INT Destination course ID to restore.
|
||||
-s, --showdebugging Show developer level debugging information
|
||||
-h, --help Print out this help.
|
||||
|
||||
Example1: (Import from a course into another course)
|
||||
\$sudo -u www-data /usr/bin/php admin/cli/import.php --srccourseid=12 --dstcourseid=13\n
|
||||
|
||||
Example2: (Import from a course module into a course)
|
||||
\$sudo -u www-data /usr/bin/php admin/cli/import.php --srccmid=21 --dstcourseid=11\n
|
||||
EOL;
|
||||
|
||||
echo $help;
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if ($options['showdebugging']) {
|
||||
set_debugging(DEBUG_DEVELOPER, true);
|
||||
}
|
||||
|
||||
if (!$admin = get_admin()) {
|
||||
throw new \moodle_exception('noadmins');
|
||||
}
|
||||
|
||||
if (!empty($options['srccourseid']) && !empty($options['dstcourseid'])) {
|
||||
// Import from course into course.
|
||||
if (!$srccourse = $DB->get_record('course', ['id' => $options['srccourseid']], 'id')) {
|
||||
throw new \moodle_exception('invalidcourseid');
|
||||
}
|
||||
if (!$dstcourse = $DB->get_record('course', ['id' => $options['dstcourseid']], 'id')) {
|
||||
throw new \moodle_exception('invalidcourseid');
|
||||
}
|
||||
|
||||
cli_heading(get_string('importfromcoursetocourse', 'backup', (object)$options));
|
||||
$bc = new backup_controller(backup::TYPE_1COURSE, $srccourse->id, backup::FORMAT_MOODLE,
|
||||
backup::INTERACTIVE_NO, backup::MODE_IMPORT, $admin->id);
|
||||
$backupid = $bc->get_backupid();
|
||||
$bc->execute_plan();
|
||||
$bc->destroy();
|
||||
cli_heading(get_string('backupthenrestore', 'backup'));
|
||||
|
||||
$rc = new restore_controller($backupid, $dstcourse->id, backup::INTERACTIVE_NO,
|
||||
backup::MODE_SAMESITE, $admin->id, backup::TARGET_EXISTING_ADDING);
|
||||
$rc->execute_precheck();
|
||||
$rc->execute_plan();
|
||||
$rc->destroy();
|
||||
cli_heading(get_string('restoredcourseid', 'backup', $dstcourse->id));
|
||||
} else if (!empty($options['srccmid']) && !empty($options['dstcourseid'])) {
|
||||
// Import from course module into course.
|
||||
if (!$cm = $DB->get_record('course_modules', ['id' => $options['srccmid']], 'id')) {
|
||||
throw new \moodle_exception('invalidcoursemoduleid', 'error', '', $options['srccmid']);
|
||||
}
|
||||
if (!$course = $DB->get_record('course', ['id' => $options['dstcourseid']], 'id')) {
|
||||
throw new \moodle_exception('invalidcourseid');
|
||||
}
|
||||
|
||||
cli_heading(get_string('importfromccmidtocourse', 'backup', (object)$options));
|
||||
$bc = new backup_controller(backup::TYPE_1ACTIVITY, $cm->id, backup::FORMAT_MOODLE,
|
||||
backup::INTERACTIVE_NO, backup::MODE_IMPORT, $admin->id);
|
||||
$backupid = $bc->get_backupid();
|
||||
$bc->execute_plan();
|
||||
$bc->destroy();
|
||||
cli_heading(get_string('backupthenrestore', 'backup'));
|
||||
|
||||
$rc = new restore_controller($backupid, $course->id, backup::INTERACTIVE_NO,
|
||||
backup::MODE_SAMESITE, $admin->id, backup::TARGET_EXISTING_ADDING);
|
||||
$rc->execute_precheck();
|
||||
$rc->execute_plan();
|
||||
$rc->destroy();
|
||||
cli_heading(get_string('restoredcourseid', 'backup', $course->id));
|
||||
} else {
|
||||
throw new \moodle_exception('invalidoption');
|
||||
}
|
||||
|
||||
exit(0);
|
||||
+11
-7
@@ -172,16 +172,11 @@ $CFG->debugdeveloper = true;
|
||||
$parts = explode('/', str_replace('\\', '/', dirname(__DIR__)));
|
||||
$CFG->admin = array_pop($parts);
|
||||
|
||||
// Point pear include path to moodles lib/pear so that includes and requires will search there for files before anywhere else
|
||||
// the problem is that we need specific version of quickforms and hacked excel files :-(.
|
||||
//point pear include path to moodles lib/pear so that includes and requires will search there for files before anywhere else
|
||||
//the problem is that we need specific version of quickforms and hacked excel files :-(
|
||||
ini_set('include_path', $CFG->libdir.'/pear' . PATH_SEPARATOR . ini_get('include_path'));
|
||||
|
||||
// The core_component class can be used in any scripts, it does not need anything else.
|
||||
require_once($CFG->libdir.'/classes/component.php');
|
||||
|
||||
// Register our classloader.
|
||||
\core\component::register_autoloader();
|
||||
|
||||
require_once($CFG->libdir.'/classes/text.php');
|
||||
require_once($CFG->libdir.'/classes/string_manager.php');
|
||||
require_once($CFG->libdir.'/classes/string_manager_install.php');
|
||||
@@ -195,6 +190,15 @@ require_once($CFG->libdir.'/moodlelib.php');
|
||||
require_once($CFG->libdir.'/deprecatedlib.php');
|
||||
require_once($CFG->libdir.'/adminlib.php');
|
||||
require_once($CFG->libdir.'/componentlib.class.php');
|
||||
require_once($CFG->dirroot.'/cache/lib.php');
|
||||
|
||||
// Register our classloader, in theory somebody might want to replace it to load other hacked core classes.
|
||||
// Required because the database checks below lead to session interaction which is going to lead us to requiring autoloaded classes.
|
||||
if (defined('COMPONENT_CLASSLOADER')) {
|
||||
spl_autoload_register(COMPONENT_CLASSLOADER);
|
||||
} else {
|
||||
spl_autoload_register('core_component::classloader');
|
||||
}
|
||||
|
||||
require($CFG->dirroot.'/version.php');
|
||||
$CFG->target_release = $release;
|
||||
|
||||
@@ -57,7 +57,6 @@ Options:
|
||||
--fullname=STRING Name of the site
|
||||
--shortname=STRING Name of the site
|
||||
--summary=STRING The summary to be displayed on the front page
|
||||
--supportemail=STRING Email address for support and help.
|
||||
-h, --help Print out this help
|
||||
|
||||
Example:
|
||||
@@ -99,7 +98,6 @@ list($options, $unrecognized) = cli_get_params(
|
||||
'fullname' => '',
|
||||
'shortname' => '',
|
||||
'summary' => '',
|
||||
'supportemail' => '',
|
||||
'agree-license' => false,
|
||||
'help' => false
|
||||
),
|
||||
@@ -138,15 +136,6 @@ if (!empty($options['adminemail']) && !validate_email($options['adminemail'])) {
|
||||
cli_error(get_string('cliincorrectvalueerror', 'admin', $a));
|
||||
}
|
||||
|
||||
// Validate that the supportemail provided was an e-mail address.
|
||||
if (!empty($options['supportemail']) && !validate_email($options['supportemail'])) {
|
||||
$a = (object) [
|
||||
'option' => 'supportemail',
|
||||
'value' => $options['supportemail']
|
||||
];
|
||||
cli_error(get_string('cliincorrectvalueerror', 'admin', $a));
|
||||
}
|
||||
|
||||
$options['lang'] = clean_param($options['lang'], PARAM_SAFEDIR);
|
||||
if (!file_exists($CFG->dirroot.'/install/lang/'.$options['lang'])) {
|
||||
$options['lang'] = 'en';
|
||||
|
||||
@@ -50,6 +50,6 @@ Example:
|
||||
exit(0);
|
||||
}
|
||||
|
||||
\core\session\manager::destroy_all();
|
||||
\core\session\manager::kill_all_sessions();
|
||||
|
||||
exit(0);
|
||||
|
||||
@@ -69,7 +69,7 @@ if ($options['enablelater']) {
|
||||
}
|
||||
|
||||
$time = time() + ($options['enablelater']*60);
|
||||
set_config('maintenance_later', $time, null, true);
|
||||
set_config('maintenance_later', $time);
|
||||
|
||||
echo get_string('clistatusenabledlater', 'admin', userdate($time))."\n";
|
||||
return 0;
|
||||
@@ -77,32 +77,23 @@ if ($options['enablelater']) {
|
||||
} else if ($options['enable']) {
|
||||
if (file_exists("$CFG->dataroot/climaintenance.html")) {
|
||||
// The maintenance is already enabled, nothing to do.
|
||||
exit(0);
|
||||
}
|
||||
enable_cli_maintenance_mode();
|
||||
set_config('maintenance_enabled', 'cli mode', null, true);
|
||||
|
||||
if (isset($CFG->maintenance_later)) {
|
||||
unset_config('maintenance_later', null, true);
|
||||
} else {
|
||||
enable_cli_maintenance_mode();
|
||||
}
|
||||
set_config('maintenance_enabled', 0);
|
||||
unset_config('maintenance_later');
|
||||
echo get_string('sitemaintenanceoncli', 'admin')."\n";
|
||||
exit(0);
|
||||
|
||||
} else if ($options['enableold']) {
|
||||
set_config('maintenance_enabled', 1, null, true);
|
||||
if (isset($CFG->maintenance_later)) {
|
||||
unset_config('maintenance_later', null, true);
|
||||
}
|
||||
set_config('maintenance_enabled', 1);
|
||||
unset_config('maintenance_later');
|
||||
echo get_string('sitemaintenanceon', 'admin')."\n";
|
||||
exit(0);
|
||||
|
||||
} else if ($options['disable']) {
|
||||
if ($CFG->maintenance_enabled !== '0') {
|
||||
set_config('maintenance_enabled', 0, null, true);
|
||||
}
|
||||
if (isset($CFG->maintenance_later)) {
|
||||
unset_config('maintenance_later', null, true);
|
||||
}
|
||||
set_config('maintenance_enabled', 0);
|
||||
unset_config('maintenance_later');
|
||||
if (file_exists("$CFG->dataroot/climaintenance.html")) {
|
||||
unlink("$CFG->dataroot/climaintenance.html");
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
*/
|
||||
|
||||
define('CLI_SCRIPT', true);
|
||||
define('IGNORE_COMPONENT_CACHE', true);
|
||||
|
||||
require(__DIR__.'/../../config.php');
|
||||
require_once($CFG->libdir.'/clilib.php');
|
||||
@@ -32,7 +31,6 @@ require_once($CFG->libdir.'/clilib.php');
|
||||
$longoptions = [
|
||||
'help' => false,
|
||||
'muc' => false,
|
||||
'courses' => false,
|
||||
'theme' => false,
|
||||
'lang' => false,
|
||||
'js' => false,
|
||||
@@ -57,8 +55,6 @@ all caches will be purged.
|
||||
Options:
|
||||
-h, --help Print out this help
|
||||
--muc Purge all MUC caches (includes lang cache)
|
||||
--courses Purge all course caches (or only those specified by a comma-separated list).
|
||||
e.g. --courses=4,67,145
|
||||
--theme Purge theme cache
|
||||
--lang Purge language string cache
|
||||
--js Purge JavaScript cache
|
||||
|
||||
@@ -32,13 +32,11 @@ require_once($CFG->dirroot . "/backup/util/includes/restore_includes.php");
|
||||
list($options, $unrecognized) = cli_get_params([
|
||||
'file' => '',
|
||||
'categoryid' => '',
|
||||
'courseid' => '',
|
||||
'showdebugging' => false,
|
||||
'help' => false,
|
||||
], [
|
||||
'f' => 'file',
|
||||
'c' => 'categoryid',
|
||||
'C' => 'courseid',
|
||||
's' => 'showdebugging',
|
||||
'h' => 'help',
|
||||
]);
|
||||
@@ -48,21 +46,18 @@ if ($unrecognized) {
|
||||
cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
|
||||
}
|
||||
|
||||
if ($options['help'] || !($options['file']) || !($options['categoryid'] || $options['courseid'])) {
|
||||
if ($options['help'] || !($options['file']) || !($options['categoryid'])) {
|
||||
$help = <<<EOL
|
||||
Restore backup into provided category or course.
|
||||
If courseid is set, course module/s will be added into the course.
|
||||
Restore backup into provided category.
|
||||
|
||||
Options:
|
||||
-f, --file=STRING Path to the backup file.
|
||||
-c, --categoryid=INT ID of the course category to restore to. This option is ignored when restoring an activity and courseid is set.
|
||||
-C, --courseid=INT ID of the course to restore to. This option is ignored when restoring a course and the categoryid is set.
|
||||
-s, --showdebugging Show developer level debugging information
|
||||
-h, --help Print out this help.
|
||||
-f, --file=STRING Path to the backup file.
|
||||
-c, --categoryid=INT ID of the category to restore too.
|
||||
-s, --showdebugging Show developer level debugging information
|
||||
-h, --help Print out this help.
|
||||
|
||||
Example:
|
||||
\$sudo -u www-data /usr/bin/php admin/cli/restore_backup.php --file=/path/to/backup/coursebackup.mbz --categoryid=1\n
|
||||
\$sudo -u www-data /usr/bin/php admin/cli/restore_backup.php --file=/path/to/backup/activitybackup.mbz --courseid=1\n
|
||||
\$sudo -u www-data /usr/bin/php admin/cli/restore_backup.php --file=/path/to/backup/file.mbz --categoryid=1\n
|
||||
EOL;
|
||||
|
||||
echo $help;
|
||||
@@ -81,75 +76,29 @@ if (!file_exists($options['file'])) {
|
||||
throw new \moodle_exception('filenotfound');
|
||||
}
|
||||
|
||||
if ($options['categoryid']) {
|
||||
if (!$category = $DB->get_record('course_categories', ['id' => $options['categoryid']], 'id')) {
|
||||
throw new \moodle_exception('invalidcategoryid');
|
||||
}
|
||||
if (!$category = $DB->get_record('course_categories', ['id' => $options['categoryid']], 'id')) {
|
||||
throw new \moodle_exception('invalidcategoryid');
|
||||
}
|
||||
|
||||
if ($options['courseid']) {
|
||||
if (!$course = $DB->get_record('course', ['id' => $options['courseid']], 'id')) {
|
||||
throw new \moodle_exception('invalidcourseid');
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($category) && empty($course)) {
|
||||
throw new \moodle_exception('invalidoption');
|
||||
}
|
||||
|
||||
$backupdir = restore_controller::get_tempdir_name(SITEID, $USER->id);
|
||||
$path = make_backup_temp_directory($backupdir);
|
||||
$backupdir = "restore_" . uniqid();
|
||||
$path = $CFG->tempdir . DIRECTORY_SEPARATOR . "backup" . DIRECTORY_SEPARATOR . $backupdir;
|
||||
|
||||
cli_heading(get_string('extractingbackupfileto', 'backup', $path));
|
||||
$fp = get_file_packer('application/vnd.moodle.backup');
|
||||
$fp->extract_to_pathname($options['file'], $path);
|
||||
|
||||
cli_heading(get_string('preprocessingbackupfile'));
|
||||
|
||||
try {
|
||||
// Create a temporary restore controller to determine the restore type.
|
||||
$tmprc = new restore_controller($backupdir, SITEID, backup::INTERACTIVE_NO,
|
||||
backup::MODE_GENERAL, $admin->id, backup::TARGET_EXISTING_ADDING);
|
||||
// Restore the backup into a new course if:
|
||||
// - It is a course backup and the category is set.
|
||||
// - It is an activity backup and the course is not set.
|
||||
$restoreasnewcourse = ($tmprc->get_type() === backup::TYPE_1COURSE && !empty($category)) ||
|
||||
($tmprc->get_type() !== backup::TYPE_1COURSE && empty($course));
|
||||
// Make sure to clean up the temporary restore controller.
|
||||
$tmprc->destroy();
|
||||
list($fullname, $shortname) = restore_dbops::calculate_course_names(0, get_string('restoringcourse', 'backup'),
|
||||
get_string('restoringcourseshortname', 'backup'));
|
||||
|
||||
if ($restoreasnewcourse) {
|
||||
list($fullname, $shortname) = restore_dbops::calculate_course_names(0, get_string('restoringcourse', 'backup'),
|
||||
get_string('restoringcourseshortname', 'backup'));
|
||||
$courseid = restore_dbops::create_new_course($fullname, $shortname, $category->id);
|
||||
$rc = new restore_controller($backupdir, $courseid, backup::INTERACTIVE_NO,
|
||||
backup::MODE_GENERAL, $admin->id, backup::TARGET_NEW_COURSE);
|
||||
} else {
|
||||
$courseid = $course->id;
|
||||
$rc = new restore_controller($backupdir, $courseid, backup::INTERACTIVE_NO,
|
||||
backup::MODE_GENERAL, $admin->id, backup::TARGET_EXISTING_ADDING);
|
||||
}
|
||||
$courseid = restore_dbops::create_new_course($fullname, $shortname, $category->id);
|
||||
|
||||
$rc = new restore_controller($backupdir, $courseid, backup::INTERACTIVE_NO,
|
||||
backup::MODE_GENERAL, $admin->id, backup::TARGET_NEW_COURSE);
|
||||
$rc->execute_precheck();
|
||||
$rc->execute_plan();
|
||||
$rc->destroy();
|
||||
|
||||
// Rename the course's full and short names with the backup file's original names if the backup file is an activity backup
|
||||
// that is restored to a new course.
|
||||
if ($restoreasnewcourse && $rc->get_type() !== backup::TYPE_1COURSE) {
|
||||
$course = get_course($courseid);
|
||||
$backupinfo = $rc->get_info();
|
||||
$tmpfullname = $backupinfo->original_course_fullname ?? get_string('restoretonewcourse', 'backup');
|
||||
$tmpshortname = $backupinfo->original_course_shortname ?? get_string('newcourse');
|
||||
list($fullname, $shortname) = restore_dbops::calculate_course_names(
|
||||
courseid: 0,
|
||||
fullname: $tmpfullname,
|
||||
shortname: $tmpshortname,
|
||||
);
|
||||
$course->fullname = $fullname;
|
||||
$course->shortname = $shortname;
|
||||
$course->visible = 1;
|
||||
$DB->update_record('course', $course);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
cli_heading(get_string('cleaningtempdata'));
|
||||
fulldelete($path);
|
||||
|
||||
@@ -36,8 +36,6 @@ list($options, $unrecognized) = cli_get_params(
|
||||
'showsql' => false,
|
||||
'showdebugging' => false,
|
||||
'force' => false,
|
||||
'disable' => false,
|
||||
'enable' => false,
|
||||
], [
|
||||
'h' => 'help',
|
||||
'f' => 'force',
|
||||
@@ -49,15 +47,11 @@ if ($unrecognized) {
|
||||
cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
|
||||
}
|
||||
|
||||
$commands = ['list', 'execute', 'disable', 'enable'];
|
||||
$hascommand = count(array_filter($commands, fn($command) => $options[$command])) > 0;
|
||||
if ($options['help'] || !$hascommand) {
|
||||
if ($options['help'] or (!$options['list'] and !$options['execute'])) {
|
||||
$help =
|
||||
"Scheduled cron tasks.
|
||||
|
||||
Options:
|
||||
--disable=\\some\\task Disable scheduled task
|
||||
--enable=\\some\\task Enable scheduled task
|
||||
--execute=\\some\\task Execute scheduled task manually
|
||||
--list List all scheduled tasks
|
||||
--showsql Show sql queries before they are executed
|
||||
@@ -125,43 +119,17 @@ if ($options['list']) {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if (moodle_needs_upgrading()) {
|
||||
mtrace("Moodle upgrade pending, cannot manage tasks.");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ($disable = $options['disable']) {
|
||||
if (!$task = \core\task\manager::get_scheduled_task($disable)) {
|
||||
mtrace("Task '$disable' not found");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
$task->disable();
|
||||
mtrace("Disabled '$disable'");
|
||||
} catch (Exception $e) {
|
||||
mtrace("$e->getMessage()");
|
||||
exit(1);
|
||||
}
|
||||
} else if ($enable = $options['enable']) {
|
||||
if (!$task = \core\task\manager::get_scheduled_task($enable)) {
|
||||
mtrace("Task '$enable' not found");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
$task->enable();
|
||||
mtrace("Enabled '$enable'");
|
||||
} catch (Exception $e) {
|
||||
mtrace("$e->getMessage()");
|
||||
exit(1);
|
||||
}
|
||||
} else if ($execute = $options['execute']) {
|
||||
if ($execute = $options['execute']) {
|
||||
if (!$task = \core\task\manager::get_scheduled_task($execute)) {
|
||||
mtrace("Task '$execute' not found");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (moodle_needs_upgrading()) {
|
||||
mtrace("Moodle upgrade pending, cannot execute tasks.");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!get_config('core', 'cron_enabled') && !$options['force']) {
|
||||
mtrace('Cron is disabled. Use --force to override.');
|
||||
exit(1);
|
||||
@@ -189,7 +157,11 @@ if ($disable = $options['disable']) {
|
||||
}
|
||||
|
||||
$task->set_lock($lock);
|
||||
$cronlock->release();
|
||||
if (!$task->is_blocking()) {
|
||||
$cronlock->release();
|
||||
} else {
|
||||
$task->set_cron_lock($cronlock);
|
||||
}
|
||||
|
||||
\core\cron::run_inner_scheduled_task($task);
|
||||
}
|
||||
|
||||
@@ -158,13 +158,13 @@ if ($options['plugins']) {
|
||||
if (is_null($plugin)) {
|
||||
cli_writeln('Unknown plugin: ' . $component);
|
||||
} else {
|
||||
$pluginstring = $component . "\t" . $plugin->displayname;
|
||||
$pluginstring = $plugin->component . "\t" . $plugin->displayname;
|
||||
|
||||
if ($pluginman->can_uninstall_plugin($component)) {
|
||||
if ($pluginman->can_uninstall_plugin($plugin->component)) {
|
||||
if ($options['run']) {
|
||||
cli_writeln('Uninstalling: ' . $pluginstring);
|
||||
$progress = new progress_trace_buffer(new text_progress_trace(), true);
|
||||
$pluginman->uninstall_plugin($component, $progress);
|
||||
$pluginman->uninstall_plugin($plugin->component, $progress);
|
||||
$progress->finished();
|
||||
cli_write($progress->get_buffer());
|
||||
} else {
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Communication and its plugins settings.
|
||||
*
|
||||
* @package core
|
||||
* @subpackage communication
|
||||
* @copyright 2023 Huong Nguyen <huongnv13@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
require_once('../config.php');
|
||||
require_once($CFG->libdir . '/adminlib.php');
|
||||
|
||||
$action = required_param('action', PARAM_ALPHANUMEXT);
|
||||
$name = required_param('name', PARAM_PLUGIN);
|
||||
|
||||
$syscontext = context_system::instance();
|
||||
$PAGE->set_url('/admin/communication.php');
|
||||
$PAGE->set_context($syscontext);
|
||||
|
||||
require_admin();
|
||||
require_sesskey();
|
||||
|
||||
$return = new moodle_url('/admin/settings.php', ['section' => 'managecommunicationproviders']);
|
||||
|
||||
$plugins = core_plugin_manager::instance()->get_plugins_of_type('communication');
|
||||
$sortorder = array_flip(array_keys($plugins));
|
||||
|
||||
if (!isset($plugins[$name])) {
|
||||
throw new moodle_exception('communicationprovidernotfound', 'core_communication', $return, $name);
|
||||
}
|
||||
|
||||
$plugintypename = $plugins[$name]->type . '_' . $plugins[$name]->name;
|
||||
|
||||
switch ($action) {
|
||||
case 'disable':
|
||||
if ($plugins[$name]->is_enabled()) {
|
||||
$class = core_plugin_manager::resolve_plugininfo_class('communication');
|
||||
$class::enable_plugin($name, false);
|
||||
}
|
||||
break;
|
||||
case 'enable':
|
||||
if (!$plugins[$name]->is_enabled()) {
|
||||
$class = core_plugin_manager::resolve_plugininfo_class('communication');
|
||||
$class::enable_plugin($name, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
redirect($return);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,41 +0,0 @@
|
||||
<?php
|
||||
// This file is part of Moodle - https://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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Hooks overview page.
|
||||
*
|
||||
* @package core
|
||||
* @author Petr Skoda
|
||||
* @copyright 2022 Open LMS
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
require_once(__DIR__ . '/../config.php');
|
||||
require_once($CFG->libdir . '/adminlib.php');
|
||||
require_once($CFG->libdir . '/tablelib.php');
|
||||
|
||||
admin_externalpage_setup('hooksoverview');
|
||||
require_capability('moodle/site:config', \core\context\system::instance());
|
||||
|
||||
$hookmanager = \core\di::get(\core\hook\manager::class);
|
||||
|
||||
echo $OUTPUT->header();
|
||||
echo $OUTPUT->heading(get_string('hooksoverview', 'core_admin'));
|
||||
|
||||
$table = new \core_admin\table\hook_list_table();
|
||||
$table->out();
|
||||
|
||||
echo $OUTPUT->footer();
|
||||
+13
-19
@@ -40,6 +40,13 @@ if (!function_exists('iconv')) {
|
||||
die();
|
||||
}
|
||||
|
||||
// Make sure php5-json is available.
|
||||
if (!function_exists('json_encode') || !function_exists('json_decode')) {
|
||||
// This also shouldn't happen.
|
||||
echo 'Moodle requires the json PHP extension. Please install or enable the json extension.';
|
||||
die();
|
||||
}
|
||||
|
||||
// Make sure xml extension is available.
|
||||
if (!extension_loaded('xml')) {
|
||||
echo 'Moodle requires the xml PHP extension. Please install or enable the xml extension.';
|
||||
@@ -217,7 +224,7 @@ if (!core_tables_exist()) {
|
||||
$strlicense = get_string('license');
|
||||
|
||||
$PAGE->navbar->add($strlicense);
|
||||
$PAGE->set_title($strinstallation . moodle_page::TITLE_SEPARATOR . 'Moodle ' . $CFG->target_release, false);
|
||||
$PAGE->set_title($strinstallation.' - Moodle '.$CFG->target_release);
|
||||
$PAGE->set_heading($strinstallation);
|
||||
$PAGE->set_cacheable(false);
|
||||
|
||||
@@ -260,7 +267,7 @@ if (!core_tables_exist()) {
|
||||
upgrade_init_javascript();
|
||||
|
||||
$PAGE->navbar->add($strdatabasesetup);
|
||||
$PAGE->set_title($strinstallation . moodle_page::TITLE_SEPARATOR . $CFG->target_release, false);
|
||||
$PAGE->set_title($strinstallation.' - Moodle '.$CFG->target_release);
|
||||
$PAGE->set_heading($strinstallation);
|
||||
$PAGE->set_cacheable(false);
|
||||
|
||||
@@ -811,7 +818,7 @@ $context = context_system::instance();
|
||||
|
||||
if (!has_capability('moodle/site:config', $context)) {
|
||||
// Do not throw exception display an empty page with administration menu if visible for current user.
|
||||
$PAGE->set_title(get_string('home'));
|
||||
$PAGE->set_title($SITE->fullname);
|
||||
$PAGE->set_heading($SITE->fullname);
|
||||
echo $OUTPUT->header();
|
||||
echo $OUTPUT->footer();
|
||||
@@ -916,14 +923,8 @@ if (empty($CFG->disabledevlibdirscheck) && (is_dir($CFG->dirroot.'/vendor') || i
|
||||
// Check if the site is being foced onto ssl.
|
||||
$overridetossl = !empty($CFG->overridetossl);
|
||||
|
||||
if (defined('BEHAT_SITE_RUNNING') && BEHAT_SITE_RUNNING) {
|
||||
// We need to add this in order to be able to verify the showcampaigncontent setting behaviour during behat tests.
|
||||
$showcampaigncontent = get_config('core', 'showcampaigncontent');
|
||||
$showcampaigncontent = $showcampaigncontent === 'true';
|
||||
} else {
|
||||
// Check if moodle campaign content setting is enabled or not.
|
||||
$showcampaigncontent = !isset($CFG->showcampaigncontent) || $CFG->showcampaigncontent;
|
||||
}
|
||||
// Check if moodle campaign content setting is enabled or not.
|
||||
$showcampaigncontent = !isset($CFG->showcampaigncontent) || $CFG->showcampaigncontent;
|
||||
|
||||
// Encourage admins to enable the user feedback feature if it is not enabled already.
|
||||
$showfeedbackencouragement = empty($CFG->enableuserfeedback);
|
||||
@@ -937,13 +938,6 @@ $result = new environment_results('custom_checks');
|
||||
$result = check_xmlrpc_usage($result);
|
||||
$xmlrpcwarning = !is_null($result) ? get_string($result->getFeedbackStr(), 'admin') : '';
|
||||
|
||||
// Check whether MoodleNet features are enabled and warn about removal.
|
||||
$moodlenetwarning = '';
|
||||
$moodlenetenabled = get_config('tool_moodlenet', 'enablemoodlenet');
|
||||
if ($moodlenetenabled) {
|
||||
$moodlenetwarning = get_string('moodlenetremovalwarning', 'admin');
|
||||
}
|
||||
|
||||
admin_externalpage_setup('adminnotifications');
|
||||
|
||||
$output = $PAGE->get_renderer('core', 'admin');
|
||||
@@ -953,4 +947,4 @@ echo $output->admin_notifications_page($maturity, $insecuredataroot, $errorsdisp
|
||||
$registered, $cachewarnings, $eventshandlers, $themedesignermode, $devlibdir,
|
||||
$mobileconfigured, $overridetossl, $invalidforgottenpasswordurl, $croninfrequent,
|
||||
$showcampaigncontent, $showfeedbackencouragement, $servicesandsupportcontent,
|
||||
$xmlrpcwarning, $moodlenetwarning);
|
||||
$xmlrpcwarning);
|
||||
|
||||
+8
-4
@@ -96,16 +96,20 @@ function core_admin_pluginfile($course, $cm, $context, $filearea, $args, $forced
|
||||
send_file_not_found();
|
||||
}
|
||||
|
||||
// Check whether width/height are specified, and we can resize the image (some types such as ICO cannot be resized).
|
||||
if (($maxwidth === 0 && $maxheight === 0) ||
|
||||
!$filedata = $file->resize_image($maxwidth, $maxheight)) {
|
||||
|
||||
// No need for resizing, but if the file should be cached we save it so we can serve it fast next time.
|
||||
if (empty($maxwidth) && empty($maxheight)) {
|
||||
if ($lifetime) {
|
||||
file_safe_save_content($file->get_content(), $candidate);
|
||||
}
|
||||
send_stored_file($file, $lifetime, 0, false, $options);
|
||||
}
|
||||
|
||||
// Proceed with the resizing.
|
||||
$filedata = $file->resize_image($maxwidth, $maxheight);
|
||||
if (!$filedata) {
|
||||
send_file_not_found();
|
||||
}
|
||||
|
||||
// If we don't want to cached the file, serve now and quit.
|
||||
if (!$lifetime) {
|
||||
send_content_uncached($filedata, $filename);
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
* Displays the list of found local plugins, their version (if found) and
|
||||
* a link to delete the local plugin.
|
||||
*
|
||||
* @see https://moodledev.io/docs/apis/plugintypes/local
|
||||
* @see http://docs.moodle.org/dev/Local_customisation
|
||||
* @package admin
|
||||
* @copyright 2010 David Mudrak <david.mudrak@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
* Displays the list of found plagiarism plugins, their version (if found) and
|
||||
* a link to uninstall the plagiarism plugin.
|
||||
*
|
||||
* @see https://moodledev.io/docs/apis/subsystems/plagiarism
|
||||
* @see http://docs.moodle.org/dev/Plagiarism_API
|
||||
* @package admin
|
||||
* @copyright 2012 Dan Marsden <dan@danmarsden.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
|
||||
+7
-4
@@ -29,6 +29,8 @@ require_once($CFG->libdir . '/adminlib.php');
|
||||
require_once($CFG->libdir . '/filelib.php');
|
||||
|
||||
$fetchupdates = optional_param('fetchupdates', false, PARAM_BOOL); // Check for available plugins updates.
|
||||
$updatesonly = optional_param('updatesonly', false, PARAM_BOOL); // Show updateable plugins only.
|
||||
$contribonly = optional_param('contribonly', false, PARAM_BOOL); // Show additional plugins only.
|
||||
$uninstall = optional_param('uninstall', '', PARAM_COMPONENT); // Uninstall the plugin.
|
||||
$delete = optional_param('delete', '', PARAM_COMPONENT); // Delete the plugin folder after it is uninstalled.
|
||||
$confirmed = optional_param('confirm', false, PARAM_BOOL); // Confirm the uninstall/delete action.
|
||||
@@ -45,7 +47,8 @@ require_admin();
|
||||
$syscontext = context_system::instance();
|
||||
|
||||
// URL params we want to maintain on redirects.
|
||||
$pageurl = new moodle_url('/admin/plugins.php');
|
||||
$pageparams = array('updatesonly' => $updatesonly, 'contribonly' => $contribonly);
|
||||
$pageurl = new moodle_url('/admin/plugins.php', $pageparams);
|
||||
|
||||
$pluginman = core_plugin_manager::instance();
|
||||
|
||||
@@ -54,7 +57,7 @@ $PAGE->set_primary_active_tab('siteadminnode');
|
||||
if ($uninstall) {
|
||||
|
||||
if (!$confirmed) {
|
||||
admin_externalpage_setup('pluginsoverview');
|
||||
admin_externalpage_setup('pluginsoverview', '', $pageparams);
|
||||
} else {
|
||||
$PAGE->set_url($pageurl);
|
||||
$PAGE->set_context($syscontext);
|
||||
@@ -198,7 +201,7 @@ if ($installupdate and $installupdateversion) {
|
||||
}
|
||||
}
|
||||
|
||||
admin_externalpage_setup('pluginsoverview');
|
||||
admin_externalpage_setup('pluginsoverview', '', $pageparams);
|
||||
|
||||
/** @var core_admin_renderer $output */
|
||||
$output = $PAGE->get_renderer('core', 'admin');
|
||||
@@ -211,4 +214,4 @@ if ($fetchupdates) {
|
||||
redirect($PAGE->url);
|
||||
}
|
||||
|
||||
echo $output->plugin_management_page($pluginman, $checker);
|
||||
echo $output->plugin_management_page($pluginman, $checker, $pageparams);
|
||||
|
||||
@@ -301,12 +301,14 @@ class helper {
|
||||
// Set Activity chooser tabs to the default value ("Starred, Recommended, All, Activities, Resources").
|
||||
static::add_item($presetid, 'activitychoosertabmode', '3');
|
||||
|
||||
// Modules: Enable database, external tool (lti), IMS content package (imscp), lesson, SCORM, wiki, workshop.
|
||||
// Modules: Enable chat, database, external tool (lti), IMS content package (imscp), lesson, SCORM, survey, wiki, workshop.
|
||||
static::add_plugin($presetid, 'mod', 'chat', true);
|
||||
static::add_plugin($presetid, 'mod', 'data', true);
|
||||
static::add_plugin($presetid, 'mod', 'lti', true);
|
||||
static::add_plugin($presetid, 'mod', 'imscp', true);
|
||||
static::add_plugin($presetid, 'mod', 'lesson', true);
|
||||
static::add_plugin($presetid, 'mod', 'scorm', true);
|
||||
static::add_plugin($presetid, 'mod', 'survey', true);
|
||||
static::add_plugin($presetid, 'mod', 'wiki', true);
|
||||
static::add_plugin($presetid, 'mod', 'workshop', true);
|
||||
|
||||
|
||||
+29
-4
@@ -26,8 +26,34 @@ namespace core_adminpresets\local\setting;
|
||||
*/
|
||||
class adminpresets_admin_setting_configmultiselect extends adminpresets_setting {
|
||||
|
||||
/** @var \admin_setting_configmultiselect $settingdata */
|
||||
protected $settingdata;
|
||||
/**
|
||||
* Ensure that the $value values are setting choices.
|
||||
*
|
||||
* @param mixed $value Setting value
|
||||
* @return mixed Returns false if wrong param value
|
||||
*/
|
||||
protected function set_value($value) {
|
||||
if ($value) {
|
||||
$options = explode(',', $value);
|
||||
foreach ($options as $option) {
|
||||
|
||||
foreach ($this->settingdata->choices as $key => $choice) {
|
||||
|
||||
if ($key == $option) {
|
||||
$this->value = $option;
|
||||
$this->set_visiblevalue();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$value = implode(',', $options);
|
||||
}
|
||||
$this->value = $value;
|
||||
$this->set_visiblevalue();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function set_visiblevalue() {
|
||||
$values = explode(',', $this->value);
|
||||
@@ -35,8 +61,7 @@ class adminpresets_admin_setting_configmultiselect extends adminpresets_setting
|
||||
|
||||
foreach ($values as $value) {
|
||||
|
||||
// Ensure that each value exists as a setting choice.
|
||||
if (array_key_exists($value, $this->settingdata->choices)) {
|
||||
if (!empty($this->settingdata->choices[$value])) {
|
||||
$visiblevalues[] = $this->settingdata->choices[$value];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,9 +26,6 @@ namespace core_adminpresets\local\setting;
|
||||
*/
|
||||
class adminpresets_admin_setting_configselect extends adminpresets_setting {
|
||||
|
||||
/** @var \admin_setting_configselect $settingdata */
|
||||
protected $settingdata;
|
||||
|
||||
/**
|
||||
* Sets the setting value cleaning it.
|
||||
*
|
||||
|
||||
@@ -26,9 +26,6 @@ namespace core_adminpresets\local\setting;
|
||||
*/
|
||||
class adminpresets_admin_setting_configtime extends adminpresets_setting {
|
||||
|
||||
/** @var \admin_setting_configtime $settingdata */
|
||||
protected $settingdata;
|
||||
|
||||
/**
|
||||
* To check that the value is one of the options
|
||||
*
|
||||
|
||||
@@ -19,8 +19,6 @@ namespace core_adminpresets\local\setting;
|
||||
/**
|
||||
* Reimplementation to allow human friendly view of the selected regexps.
|
||||
*
|
||||
* @deprecated Moodle 4.3 MDL-78468 - No longer used since the devicedetectregex was removed.
|
||||
* @todo Final deprecation on Moodle 4.7 MDL-79052
|
||||
* @package core_adminpresets
|
||||
* @copyright 2021 Pimenko <support@pimenko.com><pimenko.com>
|
||||
* @author Jordan Kesraoui | Sylvain Revenu | Pimenko based on David Monllaó <david.monllao@urv.cat> code
|
||||
@@ -28,16 +26,7 @@ namespace core_adminpresets\local\setting;
|
||||
*/
|
||||
class adminpresets_admin_setting_devicedetectregex extends adminpresets_admin_setting_configtext {
|
||||
|
||||
/**
|
||||
* @deprecated Moodle 4.3 MDL-78468 - No longer used since the devicedetectregex was removed.
|
||||
* @todo Final deprecation on Moodle 4.7 MDL-79052
|
||||
*/
|
||||
public function set_visiblevalue() {
|
||||
debugging(
|
||||
__FUNCTION__ . '() is deprecated.' .
|
||||
'All functions associated with devicedetectregex theme setting are being removed.',
|
||||
DEBUG_DEVELOPER
|
||||
);
|
||||
$values = json_decode($this->get_value());
|
||||
|
||||
if (!$values) {
|
||||
|
||||
@@ -28,9 +28,6 @@ namespace core_adminpresets\local\setting;
|
||||
*/
|
||||
class adminpresets_admin_setting_special_backupdays extends adminpresets_setting {
|
||||
|
||||
/** @var \admin_setting_special_backupdays $settingdata */
|
||||
protected $settingdata;
|
||||
|
||||
protected function set_value($value) {
|
||||
$this->value = clean_param($value, PARAM_SEQUENCE);
|
||||
$this->set_visiblevalue();
|
||||
|
||||
-3
@@ -26,9 +26,6 @@ namespace core_adminpresets\local\setting;
|
||||
*/
|
||||
class adminpresets_admin_setting_users_with_capability extends adminpresets_admin_setting_configmultiselect {
|
||||
|
||||
/** @var \admin_setting_configmultiselect $settingdata */
|
||||
protected $settingdata;
|
||||
|
||||
protected function set_behaviors() {
|
||||
$this->behaviors['loadchoices'] = &$this->settingdata;
|
||||
}
|
||||
|
||||
@@ -761,8 +761,8 @@ class manager {
|
||||
public function delete_preset(int $presetid): void {
|
||||
global $DB;
|
||||
|
||||
// Check the preset exists (cannot delete the pre-installed core "Starter" and "Full" presets).
|
||||
$preset = $DB->get_record('adminpresets', ['id' => $presetid, 'iscore' => self::NONCORE_PRESET]);
|
||||
// Check the preset exists.
|
||||
$preset = $DB->get_record('adminpresets', ['id' => $presetid]);
|
||||
if (!$preset) {
|
||||
throw new moodle_exception('errordeleting', 'core_adminpresets');
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ class provider implements
|
||||
* @param collection $collection A list of information about this component
|
||||
* @return collection The collection object filled out with information about this component.
|
||||
*/
|
||||
public static function get_metadata(collection $collection): collection {
|
||||
public static function get_metadata(collection $collection) : collection {
|
||||
// These tables are really data about site configuration and not user data.
|
||||
|
||||
// The adminpresets includes information about which user performed a configuration change using the admin_presets
|
||||
@@ -74,7 +74,7 @@ class provider implements
|
||||
* @param int $userid The user to search.
|
||||
* @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
|
||||
*/
|
||||
public static function get_contexts_for_userid(int $userid): contextlist {
|
||||
public static function get_contexts_for_userid(int $userid) : contextlist {
|
||||
return new contextlist();
|
||||
}
|
||||
|
||||
|
||||
@@ -227,6 +227,7 @@ class core_adminpresets_generator extends \component_generator_base {
|
||||
public function access_protected($object, string $property) {
|
||||
$reflection = new ReflectionClass($object);
|
||||
$property = $reflection->getProperty($property);
|
||||
$property->setAccessible(true);
|
||||
return $property->getValue($object);
|
||||
}
|
||||
|
||||
@@ -243,6 +244,7 @@ class core_adminpresets_generator extends \component_generator_base {
|
||||
|
||||
// Set method accessibility.
|
||||
$method = new ReflectionMethod(manager::class, 'get_setting');
|
||||
$method->setAccessible(true);
|
||||
|
||||
// Get the proper adminpresets_setting instance.
|
||||
$settingpage = $adminroot->locate($category);
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace core_adminpresets;
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @coversDefaultClass core_adminpresets_generator
|
||||
*/
|
||||
final class generator_test extends \advanced_testcase {
|
||||
class generator_test extends \advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test the behaviour of create_preset() method.
|
||||
@@ -180,7 +180,7 @@ final class generator_test extends \advanced_testcase {
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function create_preset_provider(): array {
|
||||
public function create_preset_provider(): array {
|
||||
return [
|
||||
'Default values' => [
|
||||
],
|
||||
|
||||
@@ -23,9 +23,9 @@ namespace core_adminpresets;
|
||||
* @category test
|
||||
* @copyright 2021 Sara Arjona (sara@moodle.com)
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @coversDefaultClass \core_adminpresets\helper
|
||||
* @coversDefaultClass helper
|
||||
*/
|
||||
final class helper_test extends \advanced_testcase {
|
||||
class helper_test extends \advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test the behaviour of create_preset() method.
|
||||
@@ -84,7 +84,7 @@ final class helper_test extends \advanced_testcase {
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function create_preset_provider(): array {
|
||||
public function create_preset_provider(): array {
|
||||
return [
|
||||
'Default values' => [
|
||||
],
|
||||
@@ -177,7 +177,7 @@ final class helper_test extends \advanced_testcase {
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function add_item_provider(): array {
|
||||
public function add_item_provider(): array {
|
||||
return [
|
||||
'Setting without plugin' => [
|
||||
'name' => 'settingname',
|
||||
@@ -239,7 +239,7 @@ final class helper_test extends \advanced_testcase {
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function add_plugin_provider(): array {
|
||||
public function add_plugin_provider(): array {
|
||||
return [
|
||||
'Plugin: enabled (using int)' => [
|
||||
'type' => 'plugintype',
|
||||
@@ -321,7 +321,7 @@ final class helper_test extends \advanced_testcase {
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function change_default_preset_provider(): array {
|
||||
public function change_default_preset_provider(): array {
|
||||
return [
|
||||
'Starter preset' => [
|
||||
'preset' => 'starter',
|
||||
@@ -344,7 +344,7 @@ final class helper_test extends \advanced_testcase {
|
||||
],
|
||||
'plugins' => [
|
||||
'assign' => 1,
|
||||
'book' => 1,
|
||||
'chat' => 1,
|
||||
'data' => 1,
|
||||
'lesson' => 1,
|
||||
],
|
||||
@@ -364,7 +364,7 @@ final class helper_test extends \advanced_testcase {
|
||||
'preset' => 'unexisting',
|
||||
],
|
||||
'Valid XML file' => [
|
||||
'preset' => self::get_fixture_path(__NAMESPACE__, 'import_settings_plugins.xml'),
|
||||
'preset' => __DIR__ . '/fixtures/import_settings_plugins.xml',
|
||||
'settings' => [
|
||||
'allowemojipicker' => 1,
|
||||
'enableportfolios' => 1,
|
||||
@@ -377,7 +377,7 @@ final class helper_test extends \advanced_testcase {
|
||||
],
|
||||
],
|
||||
'Invalid XML file' => [
|
||||
'preset' => self::get_fixture_path(__NAMESPACE__, 'invalid_xml_file.xml'),
|
||||
'preset' => __DIR__ . '/fixtures/invalid_xml_file.xml',
|
||||
],
|
||||
'Unexisting XML file' => [
|
||||
'preset' => __DIR__ . '/fixtures/unexisting.xml',
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user