Compare commits
277 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| db07c09afc | |||
| 70c8bfa038 | |||
| ccca8adbc1 | |||
| 7e01d53de3 | |||
| 3317a05ec9 | |||
| b0dddbb02a | |||
| 322dae8b9c | |||
| f6be7e2a3e | |||
| aca5d1d5c6 | |||
| 07367f22f1 | |||
| 0c47aa6a92 | |||
| e23f603c41 | |||
| 9af9711c0a | |||
| 30fadc3686 | |||
| 756090ed79 | |||
| 2630591338 | |||
| e00e4487a4 | |||
| 64f1fd7d7b | |||
| 09b06bdb28 | |||
| 9e1723f4b8 | |||
| 3b4e2ae357 | |||
| d35b9dc7bb | |||
| 500cec5757 | |||
| 891e9994b4 | |||
| 83b3ee6212 | |||
| 253c16b64d | |||
| af96d4eebe | |||
| 4e6de24f31 | |||
| 7d1563333b | |||
| f7988538b2 | |||
| 6ba71820b1 | |||
| 61c5863b1a | |||
| 0091b1a153 | |||
| b12afc89e4 | |||
| 844aef124c | |||
| 476a8fcb72 | |||
| fe22fe1c83 | |||
| cb9527cd4f | |||
| e71fc4ff3b | |||
| 89fae608a6 | |||
| 34bcb7e70e | |||
| 608f7d6aa0 | |||
| c43d07d0cf | |||
| ad46a97f53 | |||
| da4e626ffa | |||
| cb4904ea25 | |||
| 0efd3066a2 | |||
| 002b1aed6a | |||
| c73c1bafc1 | |||
| e56c732209 | |||
| cff22cd5ab | |||
| 2ec3b297d7 | |||
| 1e480e2851 | |||
| a22d64816d | |||
| f540fab1dc | |||
| 1291869f90 | |||
| 7a420b6a34 | |||
| ff963d9feb | |||
| ad693b830a | |||
| c2b0463539 | |||
| f0445d5cac | |||
| 2debc731ef | |||
| 56aaf7a998 | |||
| 86cb89c5fc | |||
| 9a75b24e1c | |||
| b19ce46723 | |||
| 370843c967 | |||
| da15787ed0 | |||
| 801a2b9497 | |||
| 7a5bdf76b6 | |||
| 63f7c6cc6a | |||
| b35b012e9e | |||
| 0204fbb810 | |||
| 5cf7efb761 | |||
| 2016d4c6dd | |||
| aae18f59aa | |||
| 4d23fe8a58 | |||
| fcfcdd6508 | |||
| 45808a99b4 | |||
| e4acde27c9 | |||
| 5182b60391 | |||
| 23ab7d4973 | |||
| af8f13bac5 | |||
| e5b2b5e0ba | |||
| 9a6b07f0de | |||
| 11e53bf4b0 | |||
| 176caab765 | |||
| bd03886512 | |||
| 6f6d2d9a0f | |||
| e6af0ea94e | |||
| 86b45212e8 | |||
| b2a485e244 | |||
| 80f521eed0 | |||
| f14fdf23e3 | |||
| 25724ebfbb | |||
| c73623506d | |||
| 7450f0e33f | |||
| 55163a6ba9 | |||
| 4e04339cbd | |||
| bf3562d13e | |||
| aca3ad2008 | |||
| f34e050e9b | |||
| c2eb42fc7b | |||
| 625e015ff8 | |||
| 9d74cebd74 | |||
| 64ad970e5e | |||
| f0fb54d907 | |||
| 5da8226f72 | |||
| 2fcd7f93b9 | |||
| 9b30118456 | |||
| dd87304fcf | |||
| b08fbf99db | |||
| b3f708d4d1 | |||
| 4572a114a9 | |||
| c889381be2 | |||
| 1e07a5eef6 | |||
| 2c40c8554b | |||
| 955f4f3bb7 | |||
| 1dca48371d | |||
| c0b9a52430 | |||
| 462b8f9861 | |||
| 242ab5d5aa | |||
| bf8a28b220 | |||
| a76a1e3f5c | |||
| 78b3369ca0 | |||
| 2f0513b91c | |||
| b25405fd56 | |||
| 5565f47e9b | |||
| 2848c7d6be | |||
| 4e151ddf30 | |||
| 4b9de301c3 | |||
| dbffeb334b | |||
| 165709ff7d | |||
| 0a75d57b13 | |||
| 3182c71093 | |||
| 56fb55683a | |||
| 78229db6a1 | |||
| d04044bb0a | |||
| c1b4ccc4c6 | |||
| 5db3c95e29 | |||
| bcae2164ac | |||
| 207eed1190 | |||
| c948d9e40e | |||
| 9ccdee7687 | |||
| 1a3e68a956 | |||
| ffe67fc6df | |||
| 8a60437bea | |||
| c62143a5df | |||
| 691f2c8950 | |||
| 6ae2c78e9f | |||
| 7a4ea4dd65 | |||
| 217a7af933 | |||
| df176b292a | |||
| fa5a564a17 | |||
| dd1d91486d | |||
| f9ab045794 | |||
| 32bf836fec | |||
| e205d5accc | |||
| 9febdf9b0f | |||
| ce8695ff1f | |||
| 3e80d6ee79 | |||
| 513bafbcfd | |||
| 7fc818ebb8 | |||
| 0219831af4 | |||
| 477aa7c750 | |||
| 47e844d0f4 | |||
| c44c1b5345 | |||
| 28e9e58577 | |||
| 6a628bcd3a | |||
| 10e44e40d9 | |||
| a625583b11 | |||
| 810ab99d7d | |||
| 82a76a1b69 | |||
| ddca055e0d | |||
| b0a11d12ca | |||
| 996564f803 | |||
| 2512e6b09e | |||
| d5d6b0f7f9 | |||
| 52b4f3bb1d | |||
| 1c2f5dc1df | |||
| 75d95bc4b8 | |||
| a6c17af4c7 | |||
| 7ac2d7f598 | |||
| c4533967d1 | |||
| 5bd4a81e09 | |||
| 72f0a11f0d | |||
| 7b873db958 | |||
| b83cd14d25 | |||
| 4b0ca64617 | |||
| 7741ae3094 | |||
| aa1dc46bc8 | |||
| 27dd2e751f | |||
| f88dbfcafc | |||
| aaf45097b7 | |||
| adb0352ef4 | |||
| da4486b202 | |||
| 778470818a | |||
| 4625eeb998 | |||
| 4e7d555c18 | |||
| 8b7f262aca | |||
| d33dd13fd5 | |||
| a548bccf18 | |||
| 9977c07a69 | |||
| eedb5fde7a | |||
| eabc15f983 | |||
| 18ac43934a | |||
| 35914200b5 | |||
| 48c064505e | |||
| 417fbe1534 | |||
| c70ceb5f7b | |||
| c4735904e5 | |||
| 0792e7bdd9 | |||
| eaf09ebbc4 | |||
| d620ee8cdf | |||
| f7124a4a95 | |||
| 34f1f22b00 | |||
| a95ac513bf | |||
| a829242d47 | |||
| 2479bc2042 | |||
| 67376511ac | |||
| b65b4892ae | |||
| be159037d4 | |||
| eccadb67b7 | |||
| 94d8b7f040 | |||
| c6bf1efac6 | |||
| 50c7842d73 | |||
| 48597285cd | |||
| e13fe01c7b | |||
| 1542a9f9e1 | |||
| 310b932482 | |||
| edf923e38e | |||
| 95a5cb8038 | |||
| 771786ad4d | |||
| 5f6b32c4cb | |||
| 8ee732f391 | |||
| 21e6eea20e | |||
| 1dbc78253c | |||
| c9d48f6f25 | |||
| 894ee17f53 | |||
| cbdfc83b69 | |||
| 6c484f9fc8 | |||
| 6de35b2623 | |||
| f0c1a3789d | |||
| 8f0f474882 | |||
| 452cf5c72d | |||
| dd88b540c2 | |||
| 6a04c121af | |||
| 83eb2a624c | |||
| 1d6bee488d | |||
| 14574fdb56 | |||
| aa213563e7 | |||
| 1d01c74390 | |||
| 0e70a88d03 | |||
| 49b666b670 | |||
| 4c1beb921f | |||
| dcb43cf3b0 | |||
| aa71fcfb7f | |||
| 53557c3be1 | |||
| 16f7008c68 | |||
| 291a5449eb | |||
| 42d52002ee | |||
| c9b7b67fad | |||
| 8a2929c1a9 | |||
| 8f53f30e02 | |||
| 9ea7607613 | |||
| 26a607e8a3 | |||
| 4378d1dadc | |||
| 582eb88404 | |||
| ec0e1d74a0 | |||
| 82eacb2e48 | |||
| 8b976e77ce | |||
| cab8184fcb | |||
| 7b18faed92 | |||
| 4adb2a7695 | |||
| 410b333e47 | |||
| a8908c1a50 | |||
| b0fec7d82b |
@@ -4,6 +4,7 @@ on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- main
|
||||
- master
|
||||
- MOODLE_[0-9]+_STABLE
|
||||
tags-ignore:
|
||||
- v[0-9]+.[0-9]+.[0-9]+*
|
||||
@@ -122,6 +123,3 @@ jobs:
|
||||
dbtype: ${{ matrix.db }}
|
||||
phpunit_options: ${{ secrets.phpunit_options }}
|
||||
run: vendor/bin/phpunit $phpunit_options ${{ inputs.phpunit_extra_options }}
|
||||
|
||||
- name: Git status
|
||||
run: git status
|
||||
|
||||
@@ -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 }}
|
||||
@@ -128,7 +128,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
|
||||
|
||||
|
||||
+1
-4
@@ -26,9 +26,7 @@
|
||||
/.patches/
|
||||
/.idea/
|
||||
.phpstorm.*
|
||||
!/.phpstorm.meta.php/
|
||||
/.phpstorm.meta.php/*
|
||||
!/.phpstorm.meta.php/di.php
|
||||
!.phpstorm.meta.php
|
||||
/nbproject/
|
||||
CVS
|
||||
.DS_Store
|
||||
@@ -58,4 +56,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;
|
||||
|
||||
@@ -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',
|
||||
});
|
||||
@@ -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();
|
||||
-1292
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);
|
||||
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,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -105,7 +105,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 +183,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 +211,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 +224,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(
|
||||
|
||||
@@ -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'),
|
||||
)));
|
||||
|
||||
|
||||
@@ -159,11 +159,11 @@ class users extends system_report {
|
||||
->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']);
|
||||
['class' => 'badge badge-secondary ml-1']);
|
||||
}
|
||||
if (!$row->confirmed) {
|
||||
$fullname .= ' ' . \html_writer::tag('span', get_string('confirmationpending', 'admin'),
|
||||
['class' => 'badge badge-danger ms-1']);
|
||||
['class' => 'badge badge-danger ml-1']);
|
||||
}
|
||||
return $fullname;
|
||||
});
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
+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;
|
||||
|
||||
@@ -4508,394 +4508,4 @@
|
||||
</CUSTOM_CHECK>
|
||||
</CUSTOM_CHECKS>
|
||||
</MOODLE>
|
||||
<MOODLE version="4.5" requires="4.1.2">
|
||||
<UNICODE level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unicoderequired" />
|
||||
</FEEDBACK>
|
||||
</UNICODE>
|
||||
<DATABASE level="required">
|
||||
<VENDOR name="mariadb" version="10.6.7" />
|
||||
<VENDOR name="mysql" version="8.0" />
|
||||
<VENDOR name="postgres" version="13" />
|
||||
<VENDOR name="mssql" version="14.0" />
|
||||
<VENDOR name="oracle" version="19" />
|
||||
</DATABASE>
|
||||
<PHP version="8.1.0" level="required">
|
||||
</PHP>
|
||||
<PCREUNICODE level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="pcreunicodewarning" />
|
||||
</FEEDBACK>
|
||||
</PCREUNICODE>
|
||||
<PHP_EXTENSIONS>
|
||||
<PHP_EXTENSION name="iconv" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="iconvrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="mbstring" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="mbstringrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="curl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="curlrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="openssl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="opensslrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="tokenizer" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="tokenizerrecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="soap" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="soaprecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="ctype" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="ctyperequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="zip" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="ziprequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="zlib" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="gd" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="gdrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="simplexml" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="simplexmlrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="spl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="splrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="pcre" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="dom" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="xml" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="xmlreader" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="intl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="intlrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="json" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="hash" level="required"/>
|
||||
<PHP_EXTENSION name="fileinfo" level="required"/>
|
||||
<PHP_EXTENSION name="sodium" level="required"/>
|
||||
<PHP_EXTENSION name="exif" level="optional"/>
|
||||
</PHP_EXTENSIONS>
|
||||
<PHP_SETTINGS>
|
||||
<PHP_SETTING name="memory_limit" value="96M" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="settingmemorylimit" />
|
||||
</FEEDBACK>
|
||||
</PHP_SETTING>
|
||||
<PHP_SETTING name="file_uploads" value="1" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="settingfileuploads" />
|
||||
</FEEDBACK>
|
||||
</PHP_SETTING>
|
||||
<PHP_SETTING name="opcache.enable" value="1" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="opcacherecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_SETTING>
|
||||
</PHP_SETTINGS>
|
||||
<CUSTOM_CHECKS>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_storage_engine" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddbstorageengine" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="question/engine/upgrade/upgradelib.php" function="quiz_attempts_upgraded" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="quizattemptsupgradedmessage" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_slasharguments" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="slashargumentswarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_tables_row_format" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="unsupporteddbtablerowformat" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_unoconv_version" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="unoconvwarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_libcurl_version" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="libcurlwarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_file_format" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddbfileformat" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_file_per_table" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddbfilepertable" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_large_prefix" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddblargeprefix" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_is_https" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="ishttpswarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_incomplete_unicode_support" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="incompleteunicodesupport" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_sixtyfour_bits" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="sixtyfourbitsrequired" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_max_input_vars" level="optional">
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_admin_dir_usage" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="iscustomadminwarnings" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_xmlrpc_usage" level="optional">
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mod_assignment" level="required">
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_db_prefix_length" level="required">
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_oracle_usage" level="optional">
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_async_backup" level="recommended">
|
||||
</CUSTOM_CHECK>
|
||||
</CUSTOM_CHECKS>
|
||||
</MOODLE>
|
||||
<MOODLE version="5.0" requires="4.2.3">
|
||||
<UNICODE level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unicoderequired" />
|
||||
</FEEDBACK>
|
||||
</UNICODE>
|
||||
<DATABASE level="required">
|
||||
<VENDOR name="mariadb" version="10.11.0" />
|
||||
<VENDOR name="mysql" version="8.4" />
|
||||
<VENDOR name="postgres" version="14" />
|
||||
<VENDOR name="mssql" version="14.0" />
|
||||
<VENDOR name="oracle" version="19" />
|
||||
</DATABASE>
|
||||
<PHP version="8.2.0" level="required">
|
||||
</PHP>
|
||||
<PCREUNICODE level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="pcreunicodewarning" />
|
||||
</FEEDBACK>
|
||||
</PCREUNICODE>
|
||||
<PHP_EXTENSIONS>
|
||||
<PHP_EXTENSION name="iconv" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="iconvrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="mbstring" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="mbstringrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="curl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="curlrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="openssl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="opensslrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="tokenizer" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="tokenizerrecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="soap" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="soaprecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="ctype" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="ctyperequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="zip" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="ziprequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="zlib" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="gd" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="gdrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="simplexml" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="simplexmlrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="spl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="splrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="pcre" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="dom" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="xml" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="xmlreader" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="intl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="intlrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="json" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="hash" level="required"/>
|
||||
<PHP_EXTENSION name="fileinfo" level="required"/>
|
||||
<PHP_EXTENSION name="sodium" level="required"/>
|
||||
<PHP_EXTENSION name="exif" level="optional"/>
|
||||
</PHP_EXTENSIONS>
|
||||
<PHP_SETTINGS>
|
||||
<PHP_SETTING name="memory_limit" value="96M" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="settingmemorylimit" />
|
||||
</FEEDBACK>
|
||||
</PHP_SETTING>
|
||||
<PHP_SETTING name="file_uploads" value="1" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="settingfileuploads" />
|
||||
</FEEDBACK>
|
||||
</PHP_SETTING>
|
||||
<PHP_SETTING name="opcache.enable" value="1" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="opcacherecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_SETTING>
|
||||
</PHP_SETTINGS>
|
||||
<CUSTOM_CHECKS>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_storage_engine" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddbstorageengine" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="question/engine/upgrade/upgradelib.php" function="quiz_attempts_upgraded" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="quizattemptsupgradedmessage" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_slasharguments" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="slashargumentswarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_tables_row_format" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="unsupporteddbtablerowformat" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_unoconv_version" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="unoconvwarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_libcurl_version" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="libcurlwarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_file_format" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddbfileformat" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_file_per_table" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddbfilepertable" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_large_prefix" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddblargeprefix" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_is_https" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="ishttpswarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_incomplete_unicode_support" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="incompleteunicodesupport" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_sixtyfour_bits" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="sixtyfourbitsrequired" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_max_input_vars" level="optional">
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_admin_dir_usage" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="iscustomadminwarnings" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_xmlrpc_usage" level="optional">
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mod_assignment" level="required">
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_db_prefix_length" level="required">
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_oracle_usage" level="optional">
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_async_backup" level="recommended">
|
||||
</CUSTOM_CHECK>
|
||||
</CUSTOM_CHECKS>
|
||||
</MOODLE>
|
||||
</COMPATIBILITY_MATRIX>
|
||||
|
||||
@@ -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.';
|
||||
|
||||
+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);
|
||||
|
||||
@@ -180,7 +180,7 @@ 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,7 +23,7 @@ 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
|
||||
*/
|
||||
class helper_test extends \advanced_testcase {
|
||||
|
||||
@@ -84,7 +84,7 @@ 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 @@ 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 @@ 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 @@ 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',
|
||||
@@ -364,7 +364,7 @@ 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 @@ 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',
|
||||
|
||||
@@ -72,7 +72,7 @@ class adminpresets_admin_setting_bloglevel_test extends \advanced_testcase {
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function save_value_provider(): array {
|
||||
public function save_value_provider(): array {
|
||||
return [
|
||||
'Save the bloglevel and set blog_menu block visibility to true' => [
|
||||
'setttingvalue' => BLOG_USER_LEVEL,
|
||||
|
||||
@@ -66,7 +66,7 @@ class adminpresets_admin_setting_sitesettext_test extends \advanced_testcase {
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function save_value_provider(): array {
|
||||
public function save_value_provider(): array {
|
||||
return [
|
||||
'Fullname: different value' => [
|
||||
'settingname' => 'fullname',
|
||||
|
||||
@@ -77,7 +77,7 @@ class adminpresets_setting_test extends \advanced_testcase {
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function save_value_provider(): array {
|
||||
public function save_value_provider(): array {
|
||||
return [
|
||||
'Core setting with the same value is not saved' => [
|
||||
'category' => 'optionalsubsystems',
|
||||
@@ -167,7 +167,7 @@ class adminpresets_setting_test extends \advanced_testcase {
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function save_attributes_values_provider(): array {
|
||||
public function save_attributes_values_provider(): array {
|
||||
return [
|
||||
'Plugin setting with the same value is not saved' => [
|
||||
'category' => 'modsettinglesson',
|
||||
|
||||
@@ -35,7 +35,6 @@ class manager_test extends \advanced_testcase {
|
||||
public static function setUpBeforeClass(): void {
|
||||
global $CFG;
|
||||
require_once($CFG->libdir.'/adminlib.php');
|
||||
parent::setUpBeforeClass();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -368,7 +367,7 @@ class manager_test extends \advanced_testcase {
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function export_preset_provider(): array {
|
||||
public function export_preset_provider(): array {
|
||||
return [
|
||||
'Export settings and plugins, excluding sensible' => [
|
||||
'includesensible' => false,
|
||||
@@ -424,7 +423,7 @@ class manager_test extends \advanced_testcase {
|
||||
* @param string|null $expectedpresetname Expected preset name.
|
||||
*/
|
||||
public function test_import_preset(string $filecontents, bool $expectedpreset, bool $expectedsettings = false,
|
||||
bool $expectedplugins = false, bool $expecteddebugging = false, ?string $expectedexception = null,
|
||||
bool $expectedplugins = false, bool $expecteddebugging = false, string $expectedexception = null,
|
||||
string $expectedpresetname = 'Imported preset'): void {
|
||||
global $DB;
|
||||
|
||||
@@ -531,32 +530,26 @@ class manager_test extends \advanced_testcase {
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function import_preset_provider(): array {
|
||||
public function import_preset_provider(): array {
|
||||
return [
|
||||
'Import settings from an empty file' => [
|
||||
'filecontents' => '',
|
||||
'expectedpreset' => false,
|
||||
],
|
||||
'Import settings and plugins from a valid XML file' => [
|
||||
'filecontents' => file_get_contents(
|
||||
filename: self::get_fixture_path(__NAMESPACE__, 'import_settings_plugins.xml')
|
||||
),
|
||||
'filecontents' => file_get_contents(__DIR__ . '/fixtures/import_settings_plugins.xml'),
|
||||
'expectedpreset' => true,
|
||||
'expectedsettings' => true,
|
||||
'expectedplugins' => true,
|
||||
],
|
||||
'Import only settings from a valid XML file' => [
|
||||
'filecontents' => file_get_contents(
|
||||
filename: self::get_fixture_path(__NAMESPACE__, 'import_settings.xml')
|
||||
),
|
||||
'filecontents' => file_get_contents(__DIR__ . '/fixtures/import_settings.xml'),
|
||||
'expectedpreset' => true,
|
||||
'expectedsettings' => true,
|
||||
'expectedplugins' => false,
|
||||
],
|
||||
'Import settings and plugins from a valid XML file with Starter name, which will be marked as non-core' => [
|
||||
'filecontents' => file_get_contents(
|
||||
filename: self::get_fixture_path(__NAMESPACE__, 'import_starter_name.xml')
|
||||
),
|
||||
'filecontents' => file_get_contents(__DIR__ . '/fixtures/import_starter_name.xml'),
|
||||
'expectedpreset' => true,
|
||||
'expectedsettings' => true,
|
||||
'expectedplugins' => true,
|
||||
@@ -565,9 +558,7 @@ class manager_test extends \advanced_testcase {
|
||||
'expectedpresetname' => 'Starter',
|
||||
],
|
||||
'Import settings from an invalid XML file' => [
|
||||
'filecontents' => file_get_contents(
|
||||
filename: self::get_fixture_path(__NAMESPACE__, 'invalid_xml_file.xml')
|
||||
),
|
||||
'filecontents' => file_get_contents(__DIR__ . '/fixtures/invalid_xml_file.xml'),
|
||||
'expectedpreset' => false,
|
||||
'expectedsettings' => false,
|
||||
'expectedplugins' => false,
|
||||
@@ -575,26 +566,20 @@ class manager_test extends \advanced_testcase {
|
||||
'expectedexception' => \Exception::class,
|
||||
],
|
||||
'Import unexisting settings category' => [
|
||||
'filecontents' => file_get_contents(
|
||||
filename: self::get_fixture_path(__NAMESPACE__, 'unexisting_category.xml')
|
||||
),
|
||||
'filecontents' => file_get_contents(__DIR__ . '/fixtures/unexisting_category.xml'),
|
||||
'expectedpreset' => false,
|
||||
'expectedsettings' => false,
|
||||
'expectedplugins' => false,
|
||||
],
|
||||
'Import unexisting setting' => [
|
||||
'filecontents' => file_get_contents(
|
||||
filename: self::get_fixture_path(__NAMESPACE__, 'unexisting_setting.xml')
|
||||
),
|
||||
'filecontents' => file_get_contents(__DIR__ . '/fixtures/unexisting_setting.xml'),
|
||||
'expectedpreset' => false,
|
||||
'expectedsettings' => false,
|
||||
'expectedplugins' => false,
|
||||
'expecteddebugging' => true,
|
||||
],
|
||||
'Import valid settings with one unexisting setting too' => [
|
||||
'filecontents' => file_get_contents(
|
||||
filename: self::get_fixture_path(__NAMESPACE__, 'import_settings_with_unexisting_setting.xml'),
|
||||
),
|
||||
'filecontents' => file_get_contents(__DIR__ . '/fixtures/import_settings_with_unexisting_setting.xml'),
|
||||
'expectedpreset' => true,
|
||||
'expectedsettings' => false,
|
||||
'expectedplugins' => false,
|
||||
@@ -603,6 +588,7 @@ class manager_test extends \advanced_testcase {
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test the behaviour of delete_preset() method when the preset id doesn't exist.
|
||||
*
|
||||
|
||||
@@ -36,7 +36,7 @@ class privacy_provider_test extends provider_testcase {
|
||||
* Test for provider::get_metadata().
|
||||
* @covers ::get_metadata
|
||||
*/
|
||||
public function test_get_metadata(): void {
|
||||
public function test_get_metadata() {
|
||||
$collection = new collection('core_adminpresets');
|
||||
$newcollection = provider::get_metadata($collection);
|
||||
$itemcollection = $newcollection->get_collection();
|
||||
@@ -49,7 +49,7 @@ class privacy_provider_test extends provider_testcase {
|
||||
* Test for provider::get_contexts_for_userid() doesn't return any context.
|
||||
* @covers ::get_contexts_for_userid
|
||||
*/
|
||||
public function test_get_contexts_for_userid(): void {
|
||||
public function test_get_contexts_for_userid() {
|
||||
global $USER;
|
||||
|
||||
$this->resetAfterTest();
|
||||
@@ -67,7 +67,7 @@ class privacy_provider_test extends provider_testcase {
|
||||
* Test for provider::get_users_in_context() doesn't return any user.
|
||||
* @covers ::get_users_in_context
|
||||
*/
|
||||
public function test_get_users_in_context(): void {
|
||||
public function test_get_users_in_context() {
|
||||
global $USER;
|
||||
|
||||
$this->resetAfterTest();
|
||||
@@ -88,7 +88,7 @@ class privacy_provider_test extends provider_testcase {
|
||||
* Test for provider::export_user_data().
|
||||
* @covers ::export_user_data
|
||||
*/
|
||||
public function test_export_user_data(): void {
|
||||
public function test_export_user_data() {
|
||||
global $USER;
|
||||
|
||||
$this->resetAfterTest();
|
||||
@@ -121,7 +121,7 @@ class privacy_provider_test extends provider_testcase {
|
||||
* Test for provider::delete_data_for_all_users_in_context().
|
||||
* @covers ::delete_data_for_all_users_in_context
|
||||
*/
|
||||
public function test_delete_data_for_all_users_in_context(): void {
|
||||
public function test_delete_data_for_all_users_in_context() {
|
||||
global $DB, $USER;
|
||||
|
||||
$this->resetAfterTest();
|
||||
@@ -146,7 +146,7 @@ class privacy_provider_test extends provider_testcase {
|
||||
* Test for provider::delete_data_for_user().
|
||||
* @covers ::delete_data_for_user
|
||||
*/
|
||||
public function test_delete_data_for_user(): void {
|
||||
public function test_delete_data_for_user() {
|
||||
global $DB, $USER;
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
@@ -125,7 +125,4 @@ if (\core\hub\registration::is_registered()) {
|
||||
echo html_writer::div(html_writer::link(new moodle_url($returnurl), get_string('skipregistration', 'hub')),
|
||||
'skipregistration mt-2');
|
||||
}
|
||||
|
||||
$PAGE->requires->js_call_amd('core_admin/expand_hash', 'init');
|
||||
|
||||
echo $OUTPUT->footer();
|
||||
|
||||
+75
-80
@@ -295,7 +295,6 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
|
||||
$output .= $this->header();
|
||||
$output .= $this->output->heading(get_string('notifications', 'admin'));
|
||||
$output .= $this->upgrade_news_message();
|
||||
$output .= $this->maturity_info($maturity);
|
||||
$output .= empty($CFG->disableupdatenotifications) ? $this->available_updates($availableupdates, $availableupdatesfetch) : '';
|
||||
$output .= $this->insecure_dataroot_warning($insecuredataroot);
|
||||
@@ -755,22 +754,6 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
return $this->box($copyrighttext, 'copyright');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a transient notification for important upgrades messages for
|
||||
* specific releases.
|
||||
*
|
||||
* @return string HTML to output.
|
||||
*/
|
||||
protected function upgrade_news_message() {
|
||||
return $this->notification(
|
||||
get_string('importantupdates_content', 'admin'),
|
||||
'info',
|
||||
false,
|
||||
get_string('importantupdates_title', 'admin'),
|
||||
'i/circleinfo, core'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a warning about installing development code if necesary.
|
||||
* @param int $maturity
|
||||
@@ -860,14 +843,14 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
$registerbutton = $this->single_button(new moodle_url('/admin/registration/index.php'),
|
||||
get_string('register', 'admin'));
|
||||
$str = 'registrationwarning';
|
||||
$type = 'error alert alert-danger';
|
||||
} else {
|
||||
$registerbutton = '';
|
||||
$str = 'registrationwarningcontactadmin';
|
||||
$type = 'info';
|
||||
}
|
||||
|
||||
return $this->warning( get_string($str, 'admin') . ' ' . $registerbutton , $type);
|
||||
return $this->warning( get_string($str, 'admin')
|
||||
. ' ' . $this->help_icon('registration', 'admin') . $registerbutton ,
|
||||
'error alert alert-danger');
|
||||
}
|
||||
|
||||
return '';
|
||||
@@ -891,8 +874,8 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
protected function mobile_configuration_warning($mobileconfigured) {
|
||||
$output = '';
|
||||
if (!$mobileconfigured) {
|
||||
$settingslink = new moodle_url('/admin/search.php', ['query' => 'enablemobilewebservice']);
|
||||
$configurebutton = $this->single_button($settingslink, get_string('enablemobilewebservice', 'admin'), 'get');
|
||||
$settingslink = new moodle_url('/admin/settings.php', ['section' => 'mobilesettings']);
|
||||
$configurebutton = $this->single_button($settingslink, get_string('enablemobilewebservice', 'admin'));
|
||||
$output .= $this->warning(get_string('mobilenotconfiguredwarning', 'admin') . ' ' . $configurebutton);
|
||||
}
|
||||
|
||||
@@ -1021,7 +1004,7 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
function upgrade_reload($url) {
|
||||
return html_writer::empty_tag('br') .
|
||||
html_writer::tag('div',
|
||||
html_writer::link($url, $this->pix_icon('i/reload', '', '', ['class' => 'icon']) .
|
||||
html_writer::link($url, $this->pix_icon('i/reload', '', '', array('class' => 'icon icon-pre')) .
|
||||
get_string('reload'), array('title' => get_string('reload'))),
|
||||
array('class' => 'continuebutton')) . html_writer::empty_tag('br');
|
||||
}
|
||||
@@ -1258,7 +1241,7 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
new moodle_url($this->page->url, array('installupdatex' => 1)),
|
||||
get_string('updateavailableinstallall', 'core_admin', count($installableupdates)),
|
||||
'post',
|
||||
array('class' => 'singlebutton updateavailableinstallall me-1')
|
||||
array('class' => 'singlebutton updateavailableinstallall mr-1')
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1267,7 +1250,7 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
new moodle_url($this->page->url, array('abortinstallx' => 1, 'confirmplugincheck' => 0)),
|
||||
get_string('cancelinstallall', 'core_plugin', count($installabortable)),
|
||||
'post',
|
||||
array('class' => 'singlebutton cancelinstallall me-1')
|
||||
array('class' => 'singlebutton cancelinstallall mr-1')
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1276,17 +1259,17 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
new moodle_url($this->page->url, array('abortupgradex' => 1)),
|
||||
get_string('cancelupgradeall', 'core_plugin', count($upgradeabortable)),
|
||||
'post',
|
||||
array('class' => 'singlebutton cancelupgradeall me-1')
|
||||
array('class' => 'singlebutton cancelupgradeall mr-1')
|
||||
);
|
||||
}
|
||||
|
||||
$out .= html_writer::div(html_writer::link(new moodle_url($this->page->url, array('showallplugins' => 0)),
|
||||
get_string('plugincheckattention', 'core_plugin')).' '.html_writer::span($sumattention, 'badge bg-light text-dark'),
|
||||
'btn btn-link me-1');
|
||||
'btn btn-link mr-1');
|
||||
|
||||
$out .= html_writer::div(html_writer::link(new moodle_url($this->page->url, array('showallplugins' => 1)),
|
||||
get_string('plugincheckall', 'core_plugin')).' '.html_writer::span($sumtotal, 'badge bg-light text-dark'),
|
||||
'btn btn-link me-1');
|
||||
'btn btn-link mr-1');
|
||||
|
||||
$out .= $this->output->container_end(); // End of .actions container.
|
||||
$out .= $this->output->container_end(); // End of #plugins-check-info container.
|
||||
@@ -1305,7 +1288,7 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
* @param null|moodle_url $cancel URL for the cancel link, defaults to the current page
|
||||
* @return string HTML
|
||||
*/
|
||||
public function plugins_management_confirm_buttons(?moodle_url $continue=null, ?moodle_url $cancel=null) {
|
||||
public function plugins_management_confirm_buttons(moodle_url $continue=null, moodle_url $cancel=null) {
|
||||
|
||||
$out = html_writer::start_div('plugins-management-confirm-buttons');
|
||||
|
||||
@@ -1393,13 +1376,13 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
new moodle_url($this->page->url, array('installdepx' => 1)),
|
||||
get_string('dependencyinstallmissing', 'core_plugin', count($installable)),
|
||||
'post',
|
||||
array('class' => 'singlebutton dependencyinstallmissing d-inline-block me-1')
|
||||
array('class' => 'singlebutton dependencyinstallmissing d-inline-block mr-1')
|
||||
);
|
||||
}
|
||||
|
||||
$out .= html_writer::div(html_writer::link(new moodle_url('/admin/tool/installaddon/'),
|
||||
get_string('dependencyuploadmissing', 'core_plugin'), array('class' => 'btn btn-link')),
|
||||
'dependencyuploadmissing d-inline-block me-1');
|
||||
'dependencyuploadmissing d-inline-block mr-1');
|
||||
|
||||
$out .= $this->output->container_end(); // End of .plugins-check-dependencies-actions container.
|
||||
|
||||
@@ -1465,29 +1448,29 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
$info .= html_writer::div(
|
||||
html_writer::link('https://moodle.org/plugins/view.php?plugin='.$plugin->component,
|
||||
get_string('misdepinfoplugin', 'core_plugin')),
|
||||
'misdepinfoplugin d-inline-block me-3 mb-1'
|
||||
'misdepinfoplugin d-inline-block mr-3 mb-1'
|
||||
);
|
||||
|
||||
$info .= html_writer::div(
|
||||
html_writer::link('https://moodle.org/plugins/pluginversion.php?id='.$plugin->version->id,
|
||||
get_string('misdepinfoversion', 'core_plugin')),
|
||||
'misdepinfoversion d-inline-block me-3 mb-1'
|
||||
'misdepinfoversion d-inline-block mr-3 mb-1'
|
||||
);
|
||||
|
||||
$info .= html_writer::div(html_writer::link($plugin->version->downloadurl, get_string('download')),
|
||||
'misdepdownload d-inline-block me-3 mb-1');
|
||||
'misdepdownload d-inline-block mr-3 mb-1');
|
||||
|
||||
if ($pluginman->is_remote_plugin_installable($plugin->component, $plugin->version->version, $reason)) {
|
||||
$info .= $this->output->single_button(
|
||||
new moodle_url($this->page->url, array('installdep' => $plugin->component)),
|
||||
get_string('dependencyinstall', 'core_plugin'),
|
||||
'post',
|
||||
array('class' => 'singlebutton dependencyinstall me-3 mb-1')
|
||||
array('class' => 'singlebutton dependencyinstall mr-3 mb-1')
|
||||
);
|
||||
} else {
|
||||
$reasonhelp = $this->info_remote_plugin_not_installable($reason);
|
||||
if ($reasonhelp) {
|
||||
$info .= html_writer::div($reasonhelp, 'reasonhelp dependencyinstall d-inline-block me-3 mb-1');
|
||||
$info .= html_writer::div($reasonhelp, 'reasonhelp dependencyinstall d-inline-block mr-3 mb-1');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1673,9 +1656,8 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
public function plugins_overview_panel(core_plugin_manager $pluginman, array $options = array()) {
|
||||
|
||||
$plugininfo = $pluginman->get_plugins();
|
||||
$this->page->requires->js_call_amd('core_admin/plugins_overview', 'init');
|
||||
|
||||
$numtotal = $numextension = $numupdatable = $numinstallable = $nummissing = $numnew = 0;
|
||||
$numtotal = $numextension = $numupdatable = $numinstallable = 0;
|
||||
|
||||
foreach ($plugininfo as $type => $plugins) {
|
||||
foreach ($plugins as $name => $plugin) {
|
||||
@@ -1689,12 +1671,8 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
}
|
||||
}
|
||||
if ($plugin->get_status() === core_plugin_manager::PLUGIN_STATUS_MISSING) {
|
||||
$nummissing++;
|
||||
continue;
|
||||
}
|
||||
if ($plugin->get_status() === core_plugin_manager::PLUGIN_STATUS_NEW) {
|
||||
$numnew++;
|
||||
}
|
||||
$numtotal++;
|
||||
if (!$plugin->is_standard()) {
|
||||
$numextension++;
|
||||
@@ -1703,50 +1681,36 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
}
|
||||
|
||||
$infoall = html_writer::link(
|
||||
$this->page->url,
|
||||
new moodle_url($this->page->url, array('contribonly' => 0, 'updatesonly' => 0)),
|
||||
get_string('overviewall', 'core_plugin'),
|
||||
['title' => get_string('filterall', 'core_plugin'), 'data-filterby' => 'all', 'class' => 'active']
|
||||
array('title' => get_string('filterall', 'core_plugin'))
|
||||
).' '.html_writer::span($numtotal, 'badge number number-all');
|
||||
|
||||
$infoext = html_writer::link(
|
||||
new moodle_url($this->page->url, [], 'additional'),
|
||||
new moodle_url($this->page->url, array('contribonly' => 1, 'updatesonly' => 0)),
|
||||
get_string('overviewext', 'core_plugin'),
|
||||
['title' => get_string('filtercontribonly', 'core_plugin'), 'data-filterby' => 'additional']
|
||||
array('title' => get_string('filtercontribonly', 'core_plugin'))
|
||||
).' '.html_writer::span($numextension, 'badge number number-additional');
|
||||
|
||||
if ($numupdatable) {
|
||||
$infoupdatable = html_writer::link(
|
||||
new moodle_url($this->page->url, [], 'updatable'),
|
||||
new moodle_url($this->page->url, array('contribonly' => 0, 'updatesonly' => 1)),
|
||||
get_string('overviewupdatable', 'core_plugin'),
|
||||
['title' => get_string('filterupdatesonly', 'core_plugin'), 'data-filterby' => 'updatable']
|
||||
array('title' => get_string('filterupdatesonly', 'core_plugin'))
|
||||
).' '.html_writer::span($numupdatable, 'badge bg-info text-white number number-updatable');
|
||||
} else {
|
||||
// No updates, or the notifications disabled.
|
||||
$infoupdatable = '';
|
||||
}
|
||||
|
||||
if ($numnew) {
|
||||
$infonew = html_writer::link(
|
||||
new moodle_url($this->page->url, [], 'newplugin'),
|
||||
get_string('status_new', 'plugin'),
|
||||
['title' => get_string('filternewpluginsonly', 'core_plugin'), 'data-filterby' => 'newplugin']
|
||||
).' '.html_writer::span($numnew, 'badge bg-success text-white number number-newplugin');
|
||||
} else {
|
||||
$infonew = '';
|
||||
}
|
||||
$out = html_writer::start_div('', array('id' => 'plugins-overview-panel'));
|
||||
|
||||
if ($nummissing) {
|
||||
$infomissing = html_writer::link(
|
||||
new moodle_url($this->page->url, [], 'missing'),
|
||||
get_string('status_missing', 'plugin'),
|
||||
['title' => get_string('filtermissingonly', 'core_plugin'), 'data-filterby' => 'missing']
|
||||
).' '.html_writer::span($nummissing, 'badge bg-danger text-white number number-missing');
|
||||
} else {
|
||||
$infomissing = '';
|
||||
if (!empty($options['updatesonly'])) {
|
||||
$out .= $this->output->heading(get_string('overviewupdatable', 'core_plugin'), 3);
|
||||
} else if (!empty($options['contribonly'])) {
|
||||
$out .= $this->output->heading(get_string('overviewext', 'core_plugin'), 3);
|
||||
}
|
||||
|
||||
$out = html_writer::start_div('', ['id' => 'plugins-overview-panel']);
|
||||
|
||||
if ($numinstallable) {
|
||||
$out .= $this->output->single_button(
|
||||
new moodle_url($this->page->url, array('installupdatex' => 1)),
|
||||
@@ -1757,10 +1721,8 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
}
|
||||
|
||||
$out .= html_writer::div($infoall, 'info info-all').
|
||||
html_writer::div($infoext, 'info info-additional').
|
||||
html_writer::div($infoupdatable, 'info info-updatable').
|
||||
html_writer::div($infonew, 'info info-newplugin').
|
||||
html_writer::div($infomissing, 'info info-missing');
|
||||
html_writer::div($infoext, 'info info-ext').
|
||||
html_writer::div($infoupdatable, 'info info-updatable');
|
||||
|
||||
$out .= html_writer::end_div(); // End of #plugins-overview-panel block.
|
||||
|
||||
@@ -1780,6 +1742,38 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
|
||||
$plugininfo = $pluginman->get_plugins();
|
||||
|
||||
// Filter the list of plugins according the options.
|
||||
if (!empty($options['updatesonly'])) {
|
||||
$updateable = array();
|
||||
foreach ($plugininfo as $plugintype => $pluginnames) {
|
||||
foreach ($pluginnames as $pluginname => $pluginfo) {
|
||||
$pluginavailableupdates = $pluginfo->available_updates();
|
||||
if (!empty($pluginavailableupdates)) {
|
||||
foreach ($pluginavailableupdates as $pluginavailableupdate) {
|
||||
$updateable[$plugintype][$pluginname] = $pluginfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$plugininfo = $updateable;
|
||||
}
|
||||
|
||||
if (!empty($options['contribonly'])) {
|
||||
$contribs = array();
|
||||
foreach ($plugininfo as $plugintype => $pluginnames) {
|
||||
foreach ($pluginnames as $pluginname => $pluginfo) {
|
||||
if (!$pluginfo->is_standard()) {
|
||||
$contribs[$plugintype][$pluginname] = $pluginfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
$plugininfo = $contribs;
|
||||
}
|
||||
|
||||
if (empty($plugininfo)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$table = new html_table();
|
||||
$table->id = 'plugins-control-panel';
|
||||
$table->head = array(
|
||||
@@ -1877,15 +1871,13 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
$row->attributes['class'] .= ' standard';
|
||||
$source = '';
|
||||
} else {
|
||||
$row->attributes['class'] .= ' additional';
|
||||
$source = html_writer::div(get_string('sourceext', 'core_plugin'), 'source badge mr-1 bg-info text-white');
|
||||
$row->attributes['class'] .= ' extension';
|
||||
$source = html_writer::div(get_string('sourceext', 'core_plugin'), 'source badge bg-info text-white');
|
||||
}
|
||||
|
||||
if ($status === core_plugin_manager::PLUGIN_STATUS_MISSING) {
|
||||
$row->attributes['class'] .= ' missing';
|
||||
$msg = html_writer::div(get_string('status_missing', 'core_plugin'), 'statusmsg badge bg-danger text-white');
|
||||
} else if ($status === core_plugin_manager::PLUGIN_STATUS_NEW) {
|
||||
$row->attributes['class'] .= ' newplugin';
|
||||
$msg = html_writer::div(get_string('status_new', 'core_plugin'), 'statusmsg badge bg-success text-white');
|
||||
} else {
|
||||
$msg = '';
|
||||
@@ -1901,7 +1893,6 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
|
||||
$updateinfo = '';
|
||||
if (is_array($plugin->available_updates())) {
|
||||
$row->attributes['class'] .= ' updatable';
|
||||
foreach ($plugin->available_updates() as $availableupdate) {
|
||||
$updateinfo .= $this->plugin_available_update_info($pluginman, $availableupdate);
|
||||
}
|
||||
@@ -2215,10 +2206,14 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
* @return string
|
||||
*/
|
||||
public function moodleorg_registration_message() {
|
||||
$a = new stdClass();
|
||||
$a->moreinformation = '#id_sitestats'; // More information anchor.
|
||||
$a->moodleapp = HUB_MOODLEORGHUBURL . '/solutions/moodle-app/';
|
||||
$out = format_text(get_string('registerwithmoodleorginfo', 'core_hub', $a), FORMAT_MARKDOWN);
|
||||
|
||||
$out = format_text(get_string('registerwithmoodleorginfo', 'core_hub'), FORMAT_MARKDOWN);
|
||||
|
||||
$out .= html_writer::link(
|
||||
MOODLE_PRODUCTURL.'/solutions/moodle-app/',
|
||||
$this->output->pix_icon('i/info', '').' '.get_string('registerwithmoodleorginfoapp', 'core_hub'),
|
||||
['class' => 'btn btn-link', 'role' => 'opener', 'target' => '_href']
|
||||
);
|
||||
|
||||
$out .= html_writer::link(
|
||||
HUB_MOODLEORGHUBURL,
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
# core_role (subsystem) Upgrade notes
|
||||
|
||||
## 4.5
|
||||
|
||||
### Added
|
||||
|
||||
- All session management has been moved to the `\core\session\manager` class.
|
||||
This removes the dependancy to use the `sessions` table.
|
||||
|
||||
Session management plugins (like Redis) should now inherit
|
||||
the base `\core\session\handler` class, which implements
|
||||
`SessionHandlerInterface`, and override methods as required.
|
||||
|
||||
The following methods in `\core\session\manager` have been deprecated:
|
||||
| Old method name | New method name |
|
||||
| --- | --- |
|
||||
| `kill_all_sessions` | `destroy_all` |
|
||||
| `kill_session` | `destroy` |
|
||||
| `kill_sessions_for_auth_plugin` | `destroy_by_auth_plugin` |
|
||||
| `kill_user_sessions` | `destroy_user_sessions` |
|
||||
|
||||
For more information see [MDL-66151](https://tracker.moodle.org/browse/MDL-66151)
|
||||
@@ -655,7 +655,7 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
|
||||
echo "</label>\n";
|
||||
}
|
||||
if ($helpicon) {
|
||||
echo '<span class="float-sm-end text-nowrap">'.$helpicon.'</span>';
|
||||
echo '<span class="float-sm-right text-nowrap">'.$helpicon.'</span>';
|
||||
}
|
||||
echo '</div>';
|
||||
if (isset($this->errors[$name])) {
|
||||
|
||||
@@ -18,12 +18,13 @@ declare(strict_types=1);
|
||||
|
||||
namespace core_role\reportbuilder\local\entities;
|
||||
|
||||
use core\{context, context_helper};
|
||||
use core\lang_string;
|
||||
use context;
|
||||
use context_helper;
|
||||
use lang_string;
|
||||
use stdClass;
|
||||
use core_reportbuilder\local\entities\base;
|
||||
use core_reportbuilder\local\filters\select;
|
||||
use core_reportbuilder\local\report\{column, filter};
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Role entity
|
||||
@@ -95,6 +96,7 @@ class role extends base {
|
||||
$this->get_entity_name()
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->set_type(column::TYPE_TEXT)
|
||||
->add_fields("{$rolealias}.name, {$rolealias}.shortname, {$rolealias}.id, {$contextalias}.id AS contextid")
|
||||
->add_fields(context_helper::get_preload_record_columns_sql($contextalias))
|
||||
// The sorting is on name, unless empty (determined by single space - thanks Oracle) then we use shortname.
|
||||
@@ -104,7 +106,7 @@ class role extends base {
|
||||
ELSE {$rolealias}.name
|
||||
END",
|
||||
])
|
||||
->add_callback(static function(?string $name, stdClass $role): string {
|
||||
->set_callback(static function($name, stdClass $role): string {
|
||||
if ($name === null) {
|
||||
return '';
|
||||
}
|
||||
@@ -122,6 +124,7 @@ class role extends base {
|
||||
$this->get_entity_name()
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->set_type(column::TYPE_TEXT)
|
||||
->add_fields("{$rolealias}.name, {$rolealias}.shortname")
|
||||
// The sorting is on name, unless empty (determined by single space - thanks Oracle) then we use shortname.
|
||||
->set_is_sortable(true, [
|
||||
@@ -130,9 +133,12 @@ class role extends base {
|
||||
ELSE {$rolealias}.name
|
||||
END",
|
||||
])
|
||||
->add_callback(fn(?string $name, stdClass $role) => match ($name) {
|
||||
null => '',
|
||||
default => role_get_name($role, null, ROLENAME_ORIGINAL),
|
||||
->set_callback(static function($name, stdClass $role): string {
|
||||
if ($name === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return role_get_name($role, null, ROLENAME_ORIGINAL);
|
||||
});
|
||||
|
||||
// Short name column.
|
||||
@@ -142,22 +148,8 @@ class role extends base {
|
||||
$this->get_entity_name()
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->add_field("{$rolealias}.shortname")
|
||||
->set_is_sortable(true);
|
||||
|
||||
// Archetype column.
|
||||
$columns[] = (new column(
|
||||
'archetype',
|
||||
new lang_string('archetype', 'core_role'),
|
||||
$this->get_entity_name(),
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->add_field("{$rolealias}.archetype")
|
||||
->add_callback(fn(?string $archetype) => match ($archetype) {
|
||||
null => '',
|
||||
'' => get_string('none'),
|
||||
default => get_string("archetype{$archetype}", 'core_role'),
|
||||
})
|
||||
->set_type(column::TYPE_TEXT)
|
||||
->add_fields("{$rolealias}.shortname")
|
||||
->set_is_sortable(true);
|
||||
|
||||
// Description column.
|
||||
@@ -174,9 +166,12 @@ class role extends base {
|
||||
->set_type(column::TYPE_LONGTEXT)
|
||||
->add_field($descriptionfieldsql, 'description')
|
||||
->add_field("{$rolealias}.shortname")
|
||||
->add_callback(fn(?string $description, stdClass $role) => match ($description) {
|
||||
null => '',
|
||||
default => role_get_description($role),
|
||||
->set_callback(static function($description, stdClass $role): string {
|
||||
if ($description === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return role_get_description($role);
|
||||
});
|
||||
|
||||
return $columns;
|
||||
@@ -203,22 +198,6 @@ class role extends base {
|
||||
return role_get_names(null, ROLENAME_ORIGINAL, true);
|
||||
});
|
||||
|
||||
// Archetype filter.
|
||||
$filters[] = (new filter(
|
||||
select::class,
|
||||
'archetype',
|
||||
new lang_string('archetype', 'core_role'),
|
||||
$this->get_entity_name(),
|
||||
"{$rolealias}.archetype",
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->set_options_callback(static function(): array {
|
||||
return array_map(
|
||||
fn(string $archetype) => get_string("archetype{$archetype}", 'core_role'),
|
||||
get_role_archetypes(),
|
||||
);
|
||||
});
|
||||
|
||||
return $filters;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@ class role_assignment extends base {
|
||||
$this->get_entity_name()
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->set_type(column::TYPE_TEXT)
|
||||
->add_field("{$raalias}.component")
|
||||
->set_is_sortable(true);
|
||||
|
||||
@@ -112,6 +113,7 @@ class role_assignment extends base {
|
||||
$this->get_entity_name()
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->set_type(column::TYPE_INTEGER)
|
||||
->add_field("{$raalias}.itemid")
|
||||
->set_is_sortable(true);
|
||||
|
||||
|
||||
@@ -165,7 +165,7 @@ if (!empty($capabilities)) {
|
||||
$attrs = array('type'=>'submit', 'name'=>'savechanges', 'value'=>get_string('savechanges'), 'class'=>'btn btn-primary');
|
||||
echo html_writer::empty_tag('input', $attrs);
|
||||
$attrs = array('type' => 'submit', 'name' => 'cancel', 'value' => get_string('cancel'),
|
||||
'class' => 'btn btn-secondary ms-1');
|
||||
'class' => 'btn btn-secondary ml-1');
|
||||
echo html_writer::empty_tag('input', $attrs);
|
||||
echo html_writer::end_tag('div');
|
||||
echo html_writer::end_tag('div');
|
||||
|
||||
@@ -27,7 +27,7 @@ use core_role_preset;
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class preset_test extends \advanced_testcase {
|
||||
public function test_xml(): void {
|
||||
public function test_xml() {
|
||||
global $DB;
|
||||
|
||||
$roles = $DB->get_records('role');
|
||||
@@ -81,10 +81,10 @@ class preset_test extends \advanced_testcase {
|
||||
* Tests covered method.
|
||||
* @covers \core_role_preset::parse_preset
|
||||
*/
|
||||
public function test_mixed_levels(): void {
|
||||
public function test_mixed_levels() {
|
||||
// The problem here is that we cannot guarantee plugin contexts
|
||||
// have unique short names, so we have to also support level numbers.
|
||||
$xml = file_get_contents(self::get_fixture_path(__NAMESPACE__, 'mixed_levels.xml'));
|
||||
$xml = file_get_contents(__DIR__ . '/fixtures/mixed_levels.xml');
|
||||
$this->assertTrue(\core_role_preset::is_valid_preset($xml));
|
||||
|
||||
$preset = \core_role_preset::parse_preset($xml);
|
||||
|
||||
@@ -45,7 +45,7 @@ class provider_test extends provider_testcase {
|
||||
* Test to check export_user_preferences.
|
||||
* returns user preferences data.
|
||||
*/
|
||||
public function test_export_user_preferences(): void {
|
||||
public function test_export_user_preferences() {
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
@@ -64,7 +64,7 @@ class provider_test extends provider_testcase {
|
||||
/**
|
||||
* Check all contexts are returned if there is any user data for this user.
|
||||
*/
|
||||
public function test_get_contexts_for_userid(): void {
|
||||
public function test_get_contexts_for_userid() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
@@ -114,7 +114,7 @@ class provider_test extends provider_testcase {
|
||||
/**
|
||||
* Test that user data is exported correctly.
|
||||
*/
|
||||
public function test_export_user_data(): void {
|
||||
public function test_export_user_data() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
@@ -243,7 +243,7 @@ class provider_test extends provider_testcase {
|
||||
/**
|
||||
* Test for provider::delete_data_for_all_users_in_context().
|
||||
*/
|
||||
public function test_delete_data_for_all_users_in_context(): void {
|
||||
public function test_delete_data_for_all_users_in_context() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
@@ -329,7 +329,7 @@ class provider_test extends provider_testcase {
|
||||
/**
|
||||
* Test for provider::delete_data_for_user().
|
||||
*/
|
||||
public function test_delete_data_for_user(): void {
|
||||
public function test_delete_data_for_user() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
@@ -387,7 +387,7 @@ class provider_test extends provider_testcase {
|
||||
/**
|
||||
* Export for a user with a key against a script where no instance is specified.
|
||||
*/
|
||||
public function test_export_user_role_to_cohort(): void {
|
||||
public function test_export_user_role_to_cohort() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
@@ -443,7 +443,7 @@ class provider_test extends provider_testcase {
|
||||
/**
|
||||
* Test for provider::delete_user_role_to_cohort().
|
||||
*/
|
||||
public function test_delete_user_role_to_cohort(): void {
|
||||
public function test_delete_user_role_to_cohort() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
@@ -478,7 +478,7 @@ class provider_test extends provider_testcase {
|
||||
/**
|
||||
* Test that only users within a course context are fetched.
|
||||
*/
|
||||
public function test_get_users_in_context(): void {
|
||||
public function test_get_users_in_context() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
@@ -599,7 +599,7 @@ class provider_test extends provider_testcase {
|
||||
/**
|
||||
* Test that data for users in approved userlist is deleted.
|
||||
*/
|
||||
public function test_delete_data_for_users(): void {
|
||||
public function test_delete_data_for_users() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
@@ -32,7 +32,7 @@ require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php");
|
||||
* Unit tests for roles datasource
|
||||
*
|
||||
* @package core_role
|
||||
* @covers \core_role\reportbuilder\datasource\roles
|
||||
* @covers \core_role\reportbuilder\datasource\roles;
|
||||
* @copyright 2024 Paul Holden <paulh@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
@@ -104,7 +104,6 @@ final class roles_test extends core_reportbuilder_testcase {
|
||||
// Role.
|
||||
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'role:name']);
|
||||
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'role:shortname']);
|
||||
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'role:archetype']);
|
||||
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'role:description']);
|
||||
|
||||
// Role assignment.
|
||||
@@ -115,20 +114,11 @@ final class roles_test extends core_reportbuilder_testcase {
|
||||
$content = $this->get_custom_report_content($report->get('id'));
|
||||
$this->assertCount(1, $content);
|
||||
|
||||
[
|
||||
$rolename,
|
||||
$roleshortname,
|
||||
$rolearchetype,
|
||||
$roledescription,
|
||||
$timemodified,
|
||||
$component,
|
||||
$itemid,
|
||||
] = array_values($content[0]);
|
||||
[$rolename, $roleshortname, $roledescription, $timemodified, $component, $itemid] = array_values($content[0]);
|
||||
|
||||
// Role.
|
||||
$this->assertEquals('Moocher (Manager)', $rolename);
|
||||
$this->assertEquals('manager', $roleshortname);
|
||||
$this->assertEquals('ARCHETYPE: Manager', $rolearchetype);
|
||||
$this->assertEquals('Managers can access courses and modify them, but usually do not participate in them.',
|
||||
$roledescription);
|
||||
|
||||
@@ -156,14 +146,6 @@ final class roles_test extends core_reportbuilder_testcase {
|
||||
'role:name_operator' => select::EQUAL_TO,
|
||||
'role:name_value' => -1,
|
||||
], false],
|
||||
'Filter role archetype' => ['role:archetype', [
|
||||
'role:archetype_operator' => select::EQUAL_TO,
|
||||
'role:archetype_value' => 'student',
|
||||
], true],
|
||||
'Filter role archetype (no match)' => ['role:archetype', [
|
||||
'role:archetype_operator' => select::EQUAL_TO,
|
||||
'role:archetype_value' => 'teacher',
|
||||
], false],
|
||||
|
||||
// Role assignment.
|
||||
'Filter role assignment time modified' => ['role_assignment:timemodified', [
|
||||
|
||||
@@ -1,69 +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/>.
|
||||
|
||||
/**
|
||||
* Adds settings links to admin tree.
|
||||
*
|
||||
* AI gets top billing in general because it's the future.
|
||||
*
|
||||
* @package core_admin
|
||||
* @copyright 2024 Matt Porritt <matt.porritt@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
if ($hassiteconfig) {
|
||||
// Add settings page for AI provider settings.
|
||||
$providers = new admin_settingpage('aiprovider', new lang_string('aiproviders', 'ai'));
|
||||
$providers->add(new admin_setting_heading('availableproviders',
|
||||
get_string('availableproviders', 'core_ai'),
|
||||
get_string('availableproviders_desc', 'core_ai')));
|
||||
$providers->add(new \core_admin\admin\admin_setting_plugin_manager(
|
||||
'aiprovider',
|
||||
\core_ai\table\aiprovider_management_table::class,
|
||||
'manageaiproviders',
|
||||
new lang_string('manageaiproviders', 'core_ai'),
|
||||
));
|
||||
$ADMIN->add('ai', $providers);
|
||||
|
||||
// Load settings for all AI providers.
|
||||
$plugins = core_plugin_manager::instance()->get_plugins_of_type('aiprovider');
|
||||
foreach ($plugins as $plugin) {
|
||||
/** @var \core\plugininfo\aiprovider $plugin */
|
||||
$plugin->load_settings($ADMIN, 'ai', $hassiteconfig);
|
||||
}
|
||||
|
||||
// Add settings page for AI placement settings.
|
||||
$placements = new admin_settingpage('aiplacement', new lang_string('aiplacements', 'ai'));
|
||||
$placements->add(new admin_setting_heading('availableplacements',
|
||||
get_string('availableplacements', 'core_ai'),
|
||||
get_string('availableplacements_desc', 'core_ai')));
|
||||
$placements->add(new \core_admin\admin\admin_setting_plugin_manager(
|
||||
'aiplacement',
|
||||
\core_ai\table\aiplacement_management_table::class,
|
||||
'manageaiplacements',
|
||||
new lang_string('manageaiplacements', 'core_ai'),
|
||||
));
|
||||
$ADMIN->add('ai', $placements);
|
||||
|
||||
// Load settings for all placements.
|
||||
$plugins = core_plugin_manager::instance()->get_plugins_of_type('aiplacement');
|
||||
foreach ($plugins as $plugin) {
|
||||
/** @var \core\plugininfo\aiprovider $plugin */
|
||||
$plugin->load_settings($ADMIN, 'ai', $hassiteconfig);
|
||||
}
|
||||
}
|
||||
@@ -152,12 +152,6 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) { // sp
|
||||
}
|
||||
$choices[HOMEPAGE_MYCOURSES] = new lang_string('mycourses', 'admin');
|
||||
$choices[HOMEPAGE_USER] = new lang_string('userpreference', 'admin');
|
||||
|
||||
// Allow hook callbacks to extend options.
|
||||
$hook = new \core_user\hook\extend_default_homepage();
|
||||
\core\di::get(\core\hook\manager::class)->dispatch($hook);
|
||||
$choices += $hook->get_options();
|
||||
|
||||
$temp->add(new admin_setting_configselect('defaulthomepage', new lang_string('defaulthomepage', 'admin'),
|
||||
new lang_string('configdefaulthomepage', 'admin'), get_default_home_page(), $choices));
|
||||
if (!isset($CFG->enabledashboard) || $CFG->enabledashboard) {
|
||||
|
||||
@@ -87,7 +87,7 @@ if (($hassiteconfig || has_any_capability(array(
|
||||
$ADMIN->add('badges',
|
||||
new admin_externalpage('newbadge',
|
||||
new lang_string('newbadge', 'badges'),
|
||||
new moodle_url('/badges/edit.php', ['action' => 'new']),
|
||||
new moodle_url('/badges/newbadge.php', array('type' => BADGE_TYPE_SITE)),
|
||||
array('moodle/badges:createbadge'), empty($CFG->enablebadges)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -420,12 +420,6 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) {
|
||||
new lang_string('generalgroups', 'backup'), new lang_string('configgeneralgroups', 'backup'),
|
||||
array('value' => 1, 'locked' => 0)));
|
||||
$temp->add(new admin_setting_configcheckbox_with_lock('backup/backup_general_competencies', new lang_string('generalcompetencies','backup'), new lang_string('configgeneralcompetencies','backup'), array('value'=>1, 'locked'=>0)));
|
||||
$temp->add(new admin_setting_configcheckbox_with_lock(
|
||||
'backup/backup_general_customfield',
|
||||
new lang_string('generalcustomfield', 'backup'),
|
||||
new lang_string('configgeneralcustomfield', 'backup'),
|
||||
['value' => 1, 'locked' => 0],
|
||||
));
|
||||
$temp->add(new admin_setting_configcheckbox_with_lock('backup/backup_general_contentbankcontent',
|
||||
new lang_string('generalcontentbankcontent', 'backup'),
|
||||
new lang_string('configgeneralcontentbankcontent', 'backup'),
|
||||
@@ -437,6 +431,7 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) {
|
||||
['value' => 1, 'locked' => 0])
|
||||
);
|
||||
|
||||
|
||||
$temp->add(new admin_setting_configcheckbox_with_lock('backup/backup_general_legacyfiles',
|
||||
new lang_string('generallegacyfiles', 'backup'),
|
||||
new lang_string('configlegacyfiles', 'backup'), array('value' => 1, 'locked' => 0)));
|
||||
@@ -465,12 +460,6 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) {
|
||||
new lang_string('generalgroups', 'backup'), new lang_string('configgeneralgroups', 'backup'),
|
||||
array('value' => 1, 'locked' => 0)));
|
||||
$temp->add(new admin_setting_configcheckbox_with_lock('backup/backup_import_competencies', new lang_string('generalcompetencies','backup'), new lang_string('configgeneralcompetencies','backup'), array('value'=>1, 'locked'=>0)));
|
||||
$temp->add(new admin_setting_configcheckbox_with_lock(
|
||||
'backup/backup_import_customfield',
|
||||
new lang_string('generalcustomfield', 'backup'),
|
||||
new lang_string('configgeneralcustomfield', 'backup'),
|
||||
['value' => 1, 'locked' => 0],
|
||||
));
|
||||
$temp->add(new admin_setting_configcheckbox_with_lock(
|
||||
'backup/backup_import_contentbankcontent',
|
||||
new lang_string('generalcontentbankcontent', 'backup'),
|
||||
@@ -600,12 +589,6 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) {
|
||||
$temp->add(new admin_setting_configcheckbox('backup/backup_auto_groups', new lang_string('generalgroups', 'backup'),
|
||||
new lang_string('configgeneralgroups', 'backup'), 1));
|
||||
$temp->add(new admin_setting_configcheckbox('backup/backup_auto_competencies', new lang_string('generalcompetencies','backup'), new lang_string('configgeneralcompetencies','backup'), 1));
|
||||
$temp->add(new admin_setting_configcheckbox(
|
||||
'backup/backup_auto_customfield',
|
||||
new lang_string('generalcustomfield', 'backup'),
|
||||
new lang_string('configgeneralcustomfield', 'backup'),
|
||||
1,
|
||||
));
|
||||
$temp->add(new admin_setting_configcheckbox(
|
||||
'backup/backup_auto_contentbankcontent',
|
||||
new lang_string('generalcontentbankcontent', 'backup'),
|
||||
@@ -685,12 +668,6 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) {
|
||||
$temp->add(new admin_setting_configcheckbox_with_lock('restore/restore_general_competencies',
|
||||
new lang_string('generalcompetencies', 'backup'),
|
||||
new lang_string('configrestorecompetencies', 'backup'), array('value' => 1, 'locked' => 0)));
|
||||
$temp->add(new admin_setting_configcheckbox_with_lock(
|
||||
'restore/restore_general_customfield',
|
||||
new lang_string('generalcustomfield', 'backup'),
|
||||
new lang_string('configrestorecustomfield', 'backup'),
|
||||
['value' => 1, 'locked' => 0],
|
||||
));
|
||||
$temp->add(new admin_setting_configcheckbox_with_lock('restore/restore_general_contentbankcontent',
|
||||
new lang_string('generalcontentbankcontent', 'backup'),
|
||||
new lang_string('configrestorecontentbankcontent', 'backup'), array('value' => 1, 'locked' => 0)));
|
||||
|
||||
@@ -109,16 +109,7 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
|
||||
// Add the 'profiling' page to admin block.
|
||||
$ADMIN->add('development', $temp);
|
||||
|
||||
$ADMIN->add(
|
||||
parentname: 'development',
|
||||
something: new admin_externalpage(
|
||||
name: 'swaggerui',
|
||||
visiblename: new lang_string('swaggerui', 'admin'),
|
||||
url: "$CFG->wwwroot/admin/swaggerui.php",
|
||||
),
|
||||
);
|
||||
|
||||
// Web service test clients DO NOT COMMIT : THE EXTERNAL WEB PAGE IS NOT AN ADMIN PAGE !!!!!
|
||||
// Web service test clients DO NOT COMMIT : THE EXTERNAL WEB PAGE IS NOT AN ADMIN PAGE !!!!!
|
||||
$ADMIN->add('development', new admin_externalpage('testclient', new lang_string('testclient', 'webservice'), "$CFG->wwwroot/$CFG->admin/webservice/testclient.php"));
|
||||
|
||||
|
||||
|
||||
@@ -1,44 +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/>.
|
||||
|
||||
/**
|
||||
* Configure the settings for file redaction service.
|
||||
*
|
||||
* @package core_admin
|
||||
* @copyright Meirza <meirza.arson@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
if ($hassiteconfig) {
|
||||
if (!$ADMIN->locate('file_redactor')) {
|
||||
$ADMIN->add('server', new admin_category('file_redactor', get_string('redactor', 'core_files')));
|
||||
}
|
||||
|
||||
$manager = \core\di::get(\core_files\redactor\manager::class);
|
||||
|
||||
// Get settings from each service.
|
||||
foreach ($manager->get_service_classnames() as $servicename => $service) {
|
||||
$servicesettings = new admin_settingpage(
|
||||
$servicename,
|
||||
new lang_string("redactor:{$servicename}", 'core_files'),
|
||||
);
|
||||
$service::add_settings($servicesettings);
|
||||
|
||||
$ADMIN->add('file_redactor', $servicesettings);
|
||||
}
|
||||
}
|
||||
@@ -39,37 +39,18 @@ if ($hassiteconfig) {
|
||||
$temp->add(new admin_setting_configtext('defaultcity', new lang_string('defaultcity', 'core_admin'),
|
||||
new lang_string('defaultcity_help', 'core_admin'), ''));
|
||||
|
||||
$temp->add(new admin_setting_countrycodes('allcountrycodes', new lang_string('allcountrycodes', 'core_admin'),
|
||||
new lang_string('configallcountrycodes', 'core_admin')));
|
||||
|
||||
$temp->add(new admin_setting_heading('iplookup', new lang_string('iplookup', 'core_admin'),
|
||||
new lang_string('iplookupinfo', 'core_admin')));
|
||||
|
||||
$temp->add(new admin_setting_configfile('geoip2file', new lang_string('geoipfile', 'core_admin'),
|
||||
new lang_string('configgeoipfile', 'core_admin', $CFG->dataroot . '/geoip/'),
|
||||
$CFG->dataroot . '/geoip/GeoIP-City.mmdb'));
|
||||
|
||||
$temp->add(new admin_setting_configselect('geoipdbedition',
|
||||
new lang_string('geoipdbedition', 'core_admin'),
|
||||
new lang_string('geoipdbedition_desc', 'core_admin'), 'GeoLite2-City',
|
||||
['GeoLite2-City' => 'GeoLite2-City', 'GeoIP2-City' => 'GeoIP2-City']));
|
||||
|
||||
$temp->add(new admin_setting_configtext('geoipmaxmindaccid',
|
||||
new lang_string('geoipmaxmindaccid', 'core_admin'),
|
||||
new lang_string('geoipmaxmindaccid_desc', 'core_admin'),
|
||||
'',
|
||||
PARAM_TEXT,
|
||||
));
|
||||
|
||||
$temp->add(new admin_setting_configtext('geoipmaxmindlicensekey',
|
||||
new lang_string('geoipmaxmindlicensekey', 'core_admin'),
|
||||
new lang_string('geoipmaxmindlicensekey_desc', 'core_admin'),
|
||||
'',
|
||||
PARAM_TEXT,
|
||||
));
|
||||
$CFG->dataroot . '/geoip/GeoLite2-City.mmdb'));
|
||||
|
||||
$temp->add(new admin_setting_configtext('googlemapkey3', new lang_string('googlemapkey3', 'core_admin'),
|
||||
new lang_string('googlemapkey3_help', 'core_admin'), '', PARAM_RAW, 60));
|
||||
|
||||
$temp->add(new admin_setting_countrycodes('allcountrycodes', new lang_string('allcountrycodes', 'core_admin'),
|
||||
new lang_string('configallcountrycodes', 'core_admin')));
|
||||
}
|
||||
|
||||
$ADMIN->add('location', $temp);
|
||||
|
||||
@@ -102,8 +102,6 @@ if ($hassiteconfig) {
|
||||
new lang_string('limitconcurrentlogins_desc', 'core_auth'), 0, $options));
|
||||
$temp->add(new admin_setting_configtext('alternateloginurl', new lang_string('alternateloginurl', 'auth'),
|
||||
new lang_string('alternatelogin', 'auth', htmlspecialchars(get_login_url(), ENT_COMPAT)), ''));
|
||||
$temp->add(new admin_setting_configcheckbox('showloginform', new lang_string('showloginform', 'core_auth'),
|
||||
new lang_string('showloginform_desc', 'core_auth'), 1));
|
||||
$temp->add(new admin_setting_configtext('forgottenpasswordurl', new lang_string('forgottenpasswordurl', 'auth'),
|
||||
new lang_string('forgottenpassword', 'auth'), '', PARAM_URL));
|
||||
$temp->add(new admin_setting_confightmleditor('auth_instructions', new lang_string('instructions', 'auth'),
|
||||
@@ -287,7 +285,7 @@ if ($hassiteconfig) {
|
||||
$items[] = new admin_setting_configcheckbox('filtermatchonepertext', new lang_string('filtermatchonepertext', 'admin'), new lang_string('configfiltermatchonepertext', 'admin'), 0);
|
||||
$items[] = new admin_setting_configcheckbox('filternavigationwithsystemcontext',
|
||||
new lang_string('filternavigationwithsystemcontext', 'admin'),
|
||||
new lang_string('configfilternavigationwithsystemcontext', 'admin'), 1);
|
||||
new lang_string('configfilternavigationwithsystemcontext', 'admin'), 0);
|
||||
foreach ($items as $item) {
|
||||
$item->set_updatedcallback('reset_text_filters_cache');
|
||||
$temp->add($item);
|
||||
@@ -544,18 +542,6 @@ if ($hassiteconfig || has_capability('moodle/question:config', $systemcontext))
|
||||
$settings->add(new admin_setting_configselect('question_preview/history',
|
||||
get_string('responsehistory', 'question'), '', 0, $hiddenofvisible));
|
||||
|
||||
// Question editing settings.
|
||||
$settings = new admin_settingpage('qediting',
|
||||
get_string('questionediting', 'question'),
|
||||
'moodle/question:config');
|
||||
$ADMIN->add('qtypesettings', $settings);
|
||||
|
||||
$settings->add(new admin_setting_heading('qediting_options',
|
||||
'', get_string('questionediting_desc', 'question')));
|
||||
|
||||
$settings->add(new admin_setting_configcheckbox('questiondefaultssave',
|
||||
get_string('questiondefaultssave', 'question'), get_string('questiondefaultssave_desc', 'question'), 1));
|
||||
|
||||
// Settings for particular question types.
|
||||
$plugins = core_plugin_manager::instance()->get_plugins_of_type('qtype');
|
||||
core_collator::asort_objects_by_property($plugins, 'displayname');
|
||||
@@ -836,49 +822,12 @@ if ($hassiteconfig && core_communication\api::is_available()) {
|
||||
}
|
||||
}
|
||||
|
||||
// SMS plugins.
|
||||
if ($hassiteconfig) {
|
||||
$ADMIN->add(
|
||||
'modules',
|
||||
new admin_category(
|
||||
'sms',
|
||||
new lang_string('sms', 'core_sms'),
|
||||
),
|
||||
);
|
||||
$ADMIN->add(
|
||||
'sms',
|
||||
new admin_externalpage(
|
||||
'smsgateway',
|
||||
new lang_string('manage_sms_gateways', 'core_sms'),
|
||||
$CFG->wwwroot . '/sms/sms_gateways.php',
|
||||
),
|
||||
);
|
||||
foreach (core_component::get_plugin_list('smsgateway') as $plugin => $path) {
|
||||
$settingspath = $path . '/settings.php';
|
||||
if (file_exists($settingspath)) {
|
||||
$settings = new admin_settingpage(
|
||||
'smsgateway_' . $plugin . '_settings',
|
||||
new lang_string('pluginname', 'smsgateway_' . $plugin),
|
||||
'moodle/site:config',
|
||||
);
|
||||
include($settingspath);
|
||||
$ADMIN->add('smsgateway', $settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Content bank content types.
|
||||
if ($hassiteconfig) {
|
||||
$ADMIN->add('modules', new admin_category('contentbanksettings', new lang_string('contentbank')));
|
||||
$temp = new admin_settingpage('managecontentbanktypes', new lang_string('managecontentbanktypes'));
|
||||
$temp->add(new admin_setting_managecontentbankcontenttypes());
|
||||
$ADMIN->add('contentbanksettings', $temp);
|
||||
$ADMIN->add('contentbanksettings',
|
||||
new admin_externalpage('contentbank', new lang_string('contentbankcustomfields', 'contentbank'),
|
||||
$CFG->wwwroot . '/contentbank/customfield.php',
|
||||
'moodle/contentbank:configurecustomfields'
|
||||
)
|
||||
);
|
||||
$plugins = core_plugin_manager::instance()->get_plugins_of_type('contenttype');
|
||||
foreach ($plugins as $plugin) {
|
||||
/** @var \core\plugininfo\contentbank $plugin */
|
||||
|
||||
@@ -126,7 +126,7 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
|
||||
$temp->add($adminsetting);
|
||||
$temp->add(new admin_setting_configcheckbox('passwordchangelogout',
|
||||
new lang_string('passwordchangelogout', 'admin'),
|
||||
new lang_string('passwordchangelogout_desc', 'admin'), 1));
|
||||
new lang_string('passwordchangelogout_desc', 'admin'), 0));
|
||||
|
||||
$temp->add(new admin_setting_configcheckbox('passwordchangetokendeletion',
|
||||
new lang_string('passwordchangetokendeletion', 'admin'),
|
||||
@@ -152,9 +152,7 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
|
||||
$sensiblesettingsdefault .= 'server_password@@search_solr, ssl_keypassword@@search_solr, ';
|
||||
$sensiblesettingsdefault .= 'alternateserver_password@@search_solr, alternatessl_keypassword@@search_solr, ';
|
||||
$sensiblesettingsdefault .= 'test_password@@cachestore_redis, password@@mlbackend_python, ';
|
||||
$sensiblesettingsdefault .= 'badges_badgesalt@@none, calendar_exportsalt@@none, ';
|
||||
$sensiblesettingsdefault .= 'bigbluebuttonbn_shared_secret@@none, apikey@@tiny_premium, ';
|
||||
$sensiblesettingsdefault .= 'matrixaccesstoken@@communication_matrix, api_secret@@factor_sms';
|
||||
$sensiblesettingsdefault .= 'badges_badgesalt@@none, calendar_exportsalt@@none';
|
||||
$temp->add(new admin_setting_configtextarea('adminpresets/sensiblesettings',
|
||||
get_string('sensiblesettings', 'core_adminpresets'),
|
||||
get_string('sensiblesettingstext', 'core_adminpresets'),
|
||||
|
||||
@@ -25,7 +25,6 @@ if ($hassiteconfig) {
|
||||
$ADMIN->add('root', new admin_category('users', new lang_string('users','admin')));
|
||||
$ADMIN->add('root', new admin_category('courses', new lang_string('courses','admin')));
|
||||
$ADMIN->add('root', new admin_category('grades', new lang_string('grades')));
|
||||
$ADMIN->add('root', new admin_category('ai', new lang_string('ai', 'ai')));
|
||||
$ADMIN->add('root', new admin_category('analytics', new lang_string('analytics', 'analytics')));
|
||||
$ADMIN->add('root', new admin_category('competencies', new lang_string('competencies', 'core_competency')));
|
||||
$ADMIN->add('root', new admin_category('badges', new lang_string('badges'), empty($CFG->enablebadges)));
|
||||
|
||||
@@ -1,87 +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/>.
|
||||
|
||||
/**
|
||||
* Swagger UI for Moodle
|
||||
*
|
||||
* @package core_admin
|
||||
* @copyright Andrew Lyons <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
require('../config.php');
|
||||
require_once($CFG->libdir . '/adminlib.php');
|
||||
|
||||
$swaggerversion = '5.17.14';
|
||||
|
||||
$PAGE->set_url('/admin/swaggerui.php');
|
||||
|
||||
admin_externalpage_setup('swaggerui');
|
||||
|
||||
$PAGE->requires->css(new moodle_url("https://unpkg.com/swagger-ui-dist@{$swaggerversion}/swagger-ui.css"));
|
||||
|
||||
echo $OUTPUT->header();
|
||||
|
||||
// These have to be manually added for now because they must be made cross-origin. The `js` method does not yet support this.
|
||||
echo html_writer::tag(
|
||||
tagname: 'script',
|
||||
contents: '',
|
||||
attributes: [
|
||||
'src' => new moodle_url("https://unpkg.com/swagger-ui-dist@{$swaggerversion}/swagger-ui-bundle.js"),
|
||||
'crossorigin' => 'crossorigin',
|
||||
],
|
||||
);
|
||||
echo html_writer::tag(
|
||||
tagname: 'script',
|
||||
contents: '',
|
||||
attributes: [
|
||||
'src' => new moodle_url("https://unpkg.com/swagger-ui-plugin-hierarchical-tags"),
|
||||
'crossorigin' => 'crossorigin',
|
||||
],
|
||||
);
|
||||
|
||||
$openapipath = moodle_url::routed_path('/api/rest/v2/openapi.json')->out();
|
||||
$swaggerinit = <<<JS
|
||||
window.ui = SwaggerUIBundle({
|
||||
url: "{$openapipath}",
|
||||
dom_id: '#swagger-ui',
|
||||
|
||||
// Enable the "Try it out" button by default.
|
||||
tryItOutEnabled: true,
|
||||
|
||||
// Show snippets different OS options.
|
||||
requestSnippetsEnabled: true,
|
||||
|
||||
deepLinking: true,
|
||||
|
||||
plugins: [
|
||||
HierarchicalTagsPlugin,
|
||||
],
|
||||
|
||||
hierarchicalTagSeparator: /[_]/
|
||||
});
|
||||
JS;
|
||||
|
||||
$PAGE->requires->js_init_code(
|
||||
jscode: $swaggerinit,
|
||||
ondomready: true,
|
||||
);
|
||||
|
||||
echo html_writer::div('', '', [
|
||||
'id' => 'swagger-ui',
|
||||
]);
|
||||
|
||||
echo $OUTPUT->footer();
|
||||
+1
-34
@@ -24,7 +24,6 @@
|
||||
|
||||
require_once(__DIR__ . '/../config.php');
|
||||
require_once("{$CFG->libdir}/adminlib.php");
|
||||
require_once("tool/task/lib.php");
|
||||
|
||||
use core_admin\reportbuilder\local\systemreports\task_logs;
|
||||
use core_reportbuilder\system_report_factory;
|
||||
@@ -50,41 +49,9 @@ if (null !== $logid) {
|
||||
if ($download) {
|
||||
$filename = str_replace('\\', '_', $log->classname) . "-{$log->id}.log";
|
||||
header("Content-Disposition: attachment; filename=\"{$filename}\"");
|
||||
readstring_accel($log->output, 'text/plain');
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$class = new $log->classname;
|
||||
$title = $class->get_name();
|
||||
} catch (Exception $e) {
|
||||
$title = $log->classname;
|
||||
}
|
||||
$title .= " ($log->id)";
|
||||
|
||||
$PAGE->navbar->add($title, '');
|
||||
echo $OUTPUT->header();
|
||||
echo html_writer::start_tag('pre', ['class' => 'task-output', 'style' => 'min-height: 24lh']);
|
||||
|
||||
echo tool_task_mtrace_wrapper($log->output);
|
||||
echo html_writer::end_tag('pre');
|
||||
echo $OUTPUT->action_link(
|
||||
new moodle_url('/admin/tasklogs.php'),
|
||||
$strheading,
|
||||
null,
|
||||
null,
|
||||
new pix_icon('i/log', ''),
|
||||
);
|
||||
echo ' ';
|
||||
echo $OUTPUT->action_link(
|
||||
new moodle_url('/admin/tasklogs.php', ['logid' => $log->id, 'download' => true]),
|
||||
new lang_string('download'),
|
||||
null,
|
||||
null,
|
||||
new pix_icon('t/download', ''),
|
||||
);
|
||||
|
||||
echo $OUTPUT->footer();
|
||||
readstring_accel($log->output, 'text/plain');
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
Setting.
|
||||
}}
|
||||
<div class="form-item row" id="{{id}}">
|
||||
<div class="form-label col-sm-3 text-sm-end">
|
||||
<div class="form-label col-sm-3 text-sm-right">
|
||||
{{#customcontrol}}
|
||||
<p {{#labelfor}}id="{{labelfor}}_label"{{/labelfor}}>
|
||||
{{{title}}}
|
||||
|
||||
@@ -38,6 +38,6 @@
|
||||
}
|
||||
}}
|
||||
<div class="form-checkbox defaultsnext">
|
||||
{{^readonly}}<input type="hidden" name="{{name}}" value="{{no}}">{{/readonly}}
|
||||
<input type="hidden" name="{{name}}" value="{{no}}">
|
||||
<input {{#readonly}}disabled{{/readonly}} type="checkbox" name="{{name}}" value="{{value}}" id="{{id}}" {{#checked}}checked{{/checked}}>
|
||||
</div>
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
}
|
||||
}}
|
||||
<div class="form-multicheckbox">
|
||||
{{^readonly}}<input type="hidden" name="{{name}}[xxxxx]" value="1">{{/readonly}}
|
||||
<input type="hidden" name="{{name}}[xxxxx]" value="1">
|
||||
{{#hasoptions}}
|
||||
<ul>
|
||||
{{#options}}
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
Setting configmultiselect.
|
||||
}}
|
||||
<div class="form-select">
|
||||
{{^readonly}}<input type="hidden" name="{{name}}[xxxxx]" value="1">{{/readonly}}
|
||||
<input type="hidden" name="{{name}}[xxxxx]" value="1">
|
||||
<select {{#readonly}}disabled{{/readonly}} id="{{id}}" name="{{name}}[]" size="{{size}}" class="form-control" multiple>
|
||||
{{#options}}
|
||||
<option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
Setting configmultiselect with optgroup support.
|
||||
}}
|
||||
<div class="form-select">
|
||||
{{^readonly}}<input type="hidden" name="{{name}}[xxxxx]" value="1">{{/readonly}}
|
||||
<input type="hidden" name="{{name}}[xxxxx]" value="1">
|
||||
<select {{#readonly}}disabled{{/readonly}} id="{{id}}" name="{{name}}[]" size="{{size}}" class="form-control" multiple>
|
||||
{{#options}}
|
||||
<option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
|
||||
|
||||
@@ -1,59 +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/>.
|
||||
}}
|
||||
{{!
|
||||
@template core_admin/setting_configtoggle
|
||||
|
||||
Template for toggle element in admin settings.
|
||||
|
||||
Context variables required for this template:
|
||||
* id - element id
|
||||
* checked - bool, true if the toggle is enabled
|
||||
* name - form element name
|
||||
* value - form for name field
|
||||
* action - action to be performed when the toggle is clicked
|
||||
* plugin - plugin name
|
||||
* state - state of the toggle
|
||||
* title - title of the toggle tooltip
|
||||
* label - label of the toggle
|
||||
* labelclasses - classes for the label (sr-only for screen readers)
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"id": "reality-toggle-3",
|
||||
"checked": true,
|
||||
"dataattributes": [{
|
||||
"name": "id",
|
||||
"value": "toggle-reality",
|
||||
"action": "togglestate",
|
||||
"plugin": "tool_reality",
|
||||
"state": 1
|
||||
}],
|
||||
"title": "Title example",
|
||||
"label": "Enable/disable reality",
|
||||
"labelclasses": "sr-only"
|
||||
}
|
||||
}}
|
||||
<div id="container-{{id}}"
|
||||
data-toggle-method="{{dataattributes.toggle-method}}"
|
||||
data-action="{{dataattributes.action}}"
|
||||
data-plugin="{{dataattributes.plugin}}"
|
||||
data-state="{{dataattributes.state}}"
|
||||
data-value="{{dataattributes.value}}"
|
||||
>
|
||||
{{> core/toggle }}
|
||||
</div>
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
Setting description.
|
||||
}}
|
||||
<div class="form-item row">
|
||||
<div class="form-label col-sm-3 text-sm-end">
|
||||
<div class="form-label col-sm-3 text-sm-right">
|
||||
<label>
|
||||
{{{title}}}
|
||||
</label>
|
||||
|
||||
@@ -1,33 +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/>.
|
||||
}}
|
||||
{{!
|
||||
@template core_admin/setting_savebutton
|
||||
|
||||
Admin save button settings template.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
}
|
||||
}}
|
||||
{{!
|
||||
Settings.
|
||||
}}
|
||||
<div class="row">
|
||||
<div class="offset-sm-3 col-sm-3">
|
||||
<button type="submit" class="btn btn-primary">{{#str}}savechanges, admin{{/str}}</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,39 +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/>.
|
||||
}}
|
||||
{{!
|
||||
@template core_admin/table/namedesc
|
||||
|
||||
Admin setting template to display an name and description
|
||||
in a single table cell.
|
||||
|
||||
Context variables required for this template:
|
||||
* name - Name of the table setting
|
||||
* description - Description of the setting
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"name": "Fancy Setting",
|
||||
"description": "Text that describes this setting well"
|
||||
}
|
||||
}}
|
||||
{{!
|
||||
Name and description.
|
||||
}}
|
||||
<div>
|
||||
<div><strong>{{name}}</strong></div>
|
||||
<div>{{description}}</div>
|
||||
</div>
|
||||
@@ -58,7 +58,7 @@
|
||||
<a
|
||||
href="{{reporturl}}"
|
||||
id="theme-usage-report-{{choose}}"
|
||||
class="btn btn-link p-0 ms-2"
|
||||
class="btn btn-link p-0 ml-2"
|
||||
title="{{#str}}themeusagereportname, admin, {{name}}{{/str}}">
|
||||
<i class="icon fa fa-area-chart m-0" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{#str}}themeusagereportname, admin, {{name}}{{/str}}</span>
|
||||
@@ -68,7 +68,7 @@
|
||||
<a
|
||||
href="{{settingsurl}}"
|
||||
id="theme-settings-{{choose}}"
|
||||
class="btn btn-link p-0 ms-2"
|
||||
class="btn btn-link p-0 ml-2"
|
||||
title="{{#str}}themeeditsettingsname, admin, {{name}}{{/str}}">
|
||||
<i class="icon fa fa-cog m-0" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{#str}}themeeditsettingsname, admin, {{name}}{{/str}}</span>
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<div class="lead">{{tokenname}}</div>
|
||||
<div class="d-flex justify-content-start align-middle">
|
||||
<div class="lead text-break pt-1" id="copytoclipboardtoken">{{token}}</div>
|
||||
<button class="btn btn-primary ms-2" data-action="copytoclipboard" data-clipboard-target="#copytoclipboardtoken" data-clipboard-success-message="{{#str}}tokencopied, webservice{{/str}}">
|
||||
<button class="btn btn-primary ml-2" data-action="copytoclipboard" data-clipboard-target="#copytoclipboardtoken" data-clipboard-success-message="{{#str}}tokencopied, webservice{{/str}}">
|
||||
{{#pix}}t/copy, core {{/pix}}{{#str}}copytoclipboard{{/str}}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -90,8 +90,6 @@ class behat_admin extends behat_base {
|
||||
* @Given /^the following config values are set as admin:$/
|
||||
* @param TableNode $table
|
||||
*/
|
||||
#[\core\attribute\example('And the following config values are set as admin:
|
||||
| sendcoursewelcomemessage | 0 | enrol_manual |')]
|
||||
public function the_following_config_values_are_set_as_admin(TableNode $table) {
|
||||
|
||||
if (!$data = $table->getRowsHash()) {
|
||||
|
||||
@@ -18,7 +18,7 @@ Feature: An administrator can browse user accounts
|
||||
When I navigate to "Users > Accounts > Browse list of users" in site administration
|
||||
# Name field always present, email field is default for showidentity.
|
||||
Then the following should exist in the "reportbuilder-table" table:
|
||||
| First name | Email address |
|
||||
| First name / Last name | Email address |
|
||||
| User One | one@example.com |
|
||||
| User Two | two@example.com |
|
||||
# Should not see other identity fields or non-default name fields.
|
||||
@@ -34,7 +34,7 @@ Feature: An administrator can browse user accounts
|
||||
| alternativefullnameformat | firstnamephonetic lastname |
|
||||
When I navigate to "Users > Accounts > Browse list of users" in site administration
|
||||
Then the following should exist in the "reportbuilder-table" table:
|
||||
| First name - phonetic | Email address |
|
||||
| First name - phonetic / Last name | Email address |
|
||||
| Yewzer One | one@example.com |
|
||||
| Yoozare Two | two@example.com |
|
||||
|
||||
@@ -43,7 +43,7 @@ Feature: An administrator can browse user accounts
|
||||
| showuseridentity | department,profile_field_frog |
|
||||
When I navigate to "Users > Accounts > Browse list of users" in site administration
|
||||
Then the following should exist in the "reportbuilder-table" table:
|
||||
| First name | Favourite frog | Department |
|
||||
| First name / Last name | Favourite frog | Department |
|
||||
| User One | Kermit | Attack |
|
||||
| User Two | Tree | Defence |
|
||||
And I should not see "Email address" in the "table" "css_element"
|
||||
@@ -103,7 +103,7 @@ Feature: An administrator can browse user accounts
|
||||
| user3 | User | Three | three@example.com | Glass |
|
||||
And I navigate to "Users > Accounts > Browse list of users" in site administration
|
||||
Then the following should exist in the "reportbuilder-table" table:
|
||||
| First name | Email address |
|
||||
| First name / Last name | Email address |
|
||||
| User One | one@example.com |
|
||||
| User Two | two@example.com |
|
||||
| User Three | three@example.com |
|
||||
@@ -137,7 +137,7 @@ Feature: An administrator can browse user accounts
|
||||
| user2 | C1 | student |
|
||||
And I navigate to "Users > Accounts > Browse list of users" in site administration
|
||||
Then the following should exist in the "reportbuilder-table" table:
|
||||
| First name | Email address |
|
||||
| First name / Last name | Email address |
|
||||
| User One | one@example.com |
|
||||
| User Two | two@example.com |
|
||||
And I click on "Filters" "button"
|
||||
@@ -166,7 +166,7 @@ Feature: An administrator can browse user accounts
|
||||
| user1 | coursecreator | system | |
|
||||
And I navigate to "Users > Accounts > Browse list of users" in site administration
|
||||
Then the following should exist in the "reportbuilder-table" table:
|
||||
| First name | Email address |
|
||||
| First name / Last name | Email address |
|
||||
| User One | one@example.com |
|
||||
| User Two | two@example.com |
|
||||
| User Three | three@example.com |
|
||||
|
||||
@@ -8,14 +8,26 @@ Feature: An administrator can manage Block plugins
|
||||
Scenario: An administrator can control the enabled state of block plugins using JavaScript
|
||||
Given I am logged in as "admin"
|
||||
And I navigate to "Plugins > Blocks > Manage blocks" in site administration
|
||||
When I toggle the "Disable Latest badges" admin switch "off"
|
||||
And I should see "Latest badges disabled."
|
||||
And I reload the page
|
||||
Then I should see "Enable Latest badges"
|
||||
And I toggle the "Enable Latest badges" admin switch "on"
|
||||
And I should see "Latest badges enabled."
|
||||
And I reload the page
|
||||
Then I should see "Disable Latest badges"
|
||||
When I click on "Disable Latest badges" "link"
|
||||
Then I should see "Latest badges disabled."
|
||||
And "Disable Latest badges" "link" should not exist
|
||||
But "Enable Latest badges" "link" should exist
|
||||
When I click on "Enable Latest badges" "link"
|
||||
Then I should see "Latest badges enabled."
|
||||
And "Enable Latest badges" "link" should not exist
|
||||
But "Disable Latest badges" "link" should exist
|
||||
|
||||
Scenario: An administrator can control the enabled state of block plugins without JavaScript
|
||||
Given I am logged in as "admin"
|
||||
And I navigate to "Plugins > Blocks > Manage blocks" in site administration
|
||||
When I click on "Disable Latest badges" "link"
|
||||
Then I should see "Latest badges disabled."
|
||||
And "Disable Latest badges" "link" should not exist
|
||||
But "Enable Latest badges" "link" should exist
|
||||
When I click on "Enable Latest badges" "link"
|
||||
Then I should see "Latest badges enabled."
|
||||
And "Enable Latest badges" "link" should not exist
|
||||
But "Disable Latest badges" "link" should exist
|
||||
|
||||
@javascript
|
||||
Scenario: An administrator can control the protected state of block plugins using JavaScript
|
||||
|
||||
@@ -6,14 +6,25 @@ Feature: An administrator can manage Media plugins
|
||||
|
||||
@javascript
|
||||
Scenario: An administrator can control the enabled state of media plugins using JavaScript
|
||||
The state change should be reflected in the UI and persist across page reloads
|
||||
Given I am logged in as "admin"
|
||||
And I navigate to "Plugins > Media players > Manage media players" in site administration
|
||||
When I toggle the "Disable YouTube" admin switch "off"
|
||||
And I should see "YouTube disabled."
|
||||
And I reload the page
|
||||
Then I should see "Enable YouTube"
|
||||
And I toggle the "Enable YouTube" admin switch "on"
|
||||
And I should see "YouTube enabled."
|
||||
And I reload the page
|
||||
Then I should see "Disable YouTube"
|
||||
When I click on "Disable YouTube" "link"
|
||||
Then I should see "YouTube disabled."
|
||||
And "Disable YouTube" "link" should not exist
|
||||
But "Enable YouTube" "link" should exist
|
||||
When I click on "Enable YouTube" "link"
|
||||
Then I should see "YouTube enabled."
|
||||
And "Enable YouTube" "link" should not exist
|
||||
But "Disable YouTube" "link" should exist
|
||||
|
||||
Scenario: An administrator can control the enabled state of media plugins without JavaScript
|
||||
Given I am logged in as "admin"
|
||||
And I navigate to "Plugins > Media players > Manage media players" in site administration
|
||||
When I click on "Disable YouTube" "link"
|
||||
Then I should see "YouTube disabled."
|
||||
And "Disable YouTube" "link" should not exist
|
||||
But "Enable YouTube" "link" should exist
|
||||
When I click on "Enable YouTube" "link"
|
||||
Then I should see "YouTube enabled."
|
||||
And "Enable YouTube" "link" should not exist
|
||||
But "Disable YouTube" "link" should exist
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
@core @core_admin
|
||||
Feature: Manage external services tokens
|
||||
In order to manage external service usage
|
||||
As an admin
|
||||
I need to be able to create, filter and delete tokens
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | password | firstname | lastname |
|
||||
| user1 | user1 | Firstname1 | Lastname1 |
|
||||
| user2 | user2 | Firstname2 | Lastname2 |
|
||||
| user3 | user3 | Firstname3 | Lastname3 |
|
||||
| user4 | user4 | Firstname4 | Lastname4 |
|
||||
And I change window size to "small"
|
||||
|
||||
@javascript
|
||||
Scenario: Add a token to user identified by name and then delete that token
|
||||
Given I log in as "admin"
|
||||
And I am on site homepage
|
||||
And I navigate to "Server > Web services > Manage tokens" in site administration
|
||||
And I press "Create token"
|
||||
And I set the field "Name" to "Webservice1"
|
||||
And I set the field "User" to "Firstname1 Lastname1"
|
||||
And I set the field "Service" to "Moodle mobile web service"
|
||||
And I set the field "IP restriction" to "127.0.0.1"
|
||||
When I press "Save changes"
|
||||
Then the following should exist in the "generaltable" table:
|
||||
| Name | First name | Service | IP restriction | Last access |
|
||||
| Webservice1 | Firstname1 Lastname1 | Moodle mobile web service | 127.0.0.1 | Never |
|
||||
|
||||
# Verify the message and the "Copy to clipboard" button.
|
||||
And I should see "Copy the token now. It won't be shown again once you leave this page."
|
||||
And "Copy to clipboard" "button" should exist
|
||||
|
||||
# New token can only read once.
|
||||
And I reload the page
|
||||
And I should not see "Copy the token now. It won't be shown again once you leave this page."
|
||||
And "Copy to clipboard" "button" should not exist
|
||||
|
||||
# Delete token.
|
||||
And I click on "Delete" "link" in the "Webservice1" "table_row"
|
||||
And I should see "Do you really want to delete this web service token for Firstname1 Lastname1 on the service Moodle mobile web service?"
|
||||
And I press "Delete"
|
||||
And "Webservice1" "table_row" should not exist
|
||||
|
||||
@javascript @skip_chrome_zerosize
|
||||
Scenario: Tokens can be filtered by name (case-insensitive), by user and by service
|
||||
Given the following "core_webservice > Service" exists:
|
||||
| name | Site information |
|
||||
| shortname | siteinfo |
|
||||
| enabled | 1 |
|
||||
And the following "core_webservice > Service function" exists:
|
||||
| service | siteinfo |
|
||||
| functions | core_webservice_get_site_info |
|
||||
And the following "core_webservice > Tokens" exist:
|
||||
| user | service | name |
|
||||
| user2 | siteinfo | WEBservice1 |
|
||||
| user3 | moodle_mobile_app | webservicE3 |
|
||||
| user4 | siteinfo | New service2 |
|
||||
When I log in as "admin"
|
||||
And I navigate to "Server > Web services > Manage tokens" in site administration
|
||||
|
||||
# All created tokens are shown by default.
|
||||
And "Firstname1 Lastname1" "table_row" should not exist
|
||||
And I should see "Site information" in the "Firstname2 Lastname2" "table_row"
|
||||
And I should see "Moodle mobile web service" in the "Firstname3 Lastname3" "table_row"
|
||||
And I should see "Site information" in the "Firstname4 Lastname4" "table_row"
|
||||
|
||||
# Filter tokens by by name (case-insensitive).
|
||||
And I click on "Tokens filter" "link"
|
||||
And I set the field "Name" to "webservice"
|
||||
And I press "Show only matching tokens"
|
||||
And I should see "Site information" in the "Firstname2 Lastname2" "table_row"
|
||||
And I should see "Moodle mobile web service" in the "Firstname3 Lastname3" "table_row"
|
||||
And "Firstname4 Lastname4" "table_row" should not exist
|
||||
|
||||
# Reset the filter.
|
||||
And I press "Show all tokens"
|
||||
And I should see "Site information" in the "Firstname2 Lastname2" "table_row"
|
||||
And I should see "Moodle mobile web service" in the "Firstname3 Lastname3" "table_row"
|
||||
And I should see "Site information" in the "Firstname4 Lastname4" "table_row"
|
||||
|
||||
# Filter tokens by user (note we can select the user by the identity field here).
|
||||
When I click on "Tokens filter" "link"
|
||||
And I set the field "User" to "user2@example.com"
|
||||
And I press "Show only matching tokens"
|
||||
Then "Firstname3 Lastname3" "table_row" should not exist
|
||||
And "Firstname4 Lastname4" "table_row" should not exist
|
||||
And I should see "Site information" in the "Firstname2 Lastname2" "table_row"
|
||||
|
||||
# Reset the filter.
|
||||
And I press "Show all tokens"
|
||||
And I should see "Site information" in the "Firstname2 Lastname2" "table_row"
|
||||
And I should see "Moodle mobile web service" in the "Firstname3 Lastname3" "table_row"
|
||||
And I should see "Site information" in the "Firstname4 Lastname4" "table_row"
|
||||
|
||||
# Filter tokens by service.
|
||||
And I click on "Tokens filter" "link"
|
||||
And I set the field "Service" to "Site information"
|
||||
And I press "Show only matching tokens"
|
||||
And I should see "Site information" in the "Firstname2 Lastname2" "table_row"
|
||||
And I should see "Site information" in the "Firstname4 Lastname4" "table_row"
|
||||
And "Firstname3 Lastname3" "table_row" should not exist
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
@core @core_admin @core_webservice @javascript
|
||||
@core @core_admin @javascript
|
||||
Feature: Verify the breadcrumbs in external webservice site administration pages
|
||||
Whenever I navigate to external webservice page in site administration
|
||||
As an admin
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
@core @core_admin @core_webservice @javascript
|
||||
@core @core_admin @javascript
|
||||
Feature: Verify the breadcrumbs in webservice tokens site administration pages
|
||||
Whenever I navigate to manage tokens page in site administration
|
||||
As an admin
|
||||
@@ -19,7 +19,7 @@ Feature: Verify the breadcrumbs in webservice tokens site administration pages
|
||||
And "Web services" "link" should exist in the ".breadcrumb" "css_element"
|
||||
And I set the field "User" to "John Doe"
|
||||
And I press "Save changes"
|
||||
When I press "Delete" action in the "John Doe" report row
|
||||
When I click on "Delete" "link"
|
||||
Then "Delete token" "text" should exist in the ".breadcrumb" "css_element"
|
||||
And "Manage tokens" "link" should exist in the ".breadcrumb" "css_element"
|
||||
And "Web services" "link" should exist in the ".breadcrumb" "css_element"
|
||||
@@ -48,3 +48,25 @@ Feature: View task logs report and use its filters
|
||||
| operator | shouldornotsee |
|
||||
| Less than | should not see |
|
||||
| Greater than | should see |
|
||||
|
||||
@javascript
|
||||
Scenario: Reset task log filters
|
||||
Given I log in as "admin"
|
||||
And I change window size to "large"
|
||||
And I navigate to "Server > Tasks > Task logs" in site administration
|
||||
When I click on "Filters" "button"
|
||||
And I set the following fields in the "Result" "core_reportbuilder > Filter" to these values:
|
||||
| Result operator | Is equal to |
|
||||
| Result value | Fail |
|
||||
And I click on "Apply" "button" in the "[data-region='report-filters']" "css_element"
|
||||
Then I should see "Filters applied"
|
||||
And I should see "Filters (1)" in the "#dropdownFiltersButton" "css_element"
|
||||
And I should see "Nothing to display"
|
||||
And I click on "Reset all" "button" in the "[data-region='report-filters']" "css_element"
|
||||
And I should see "Filters reset"
|
||||
And I should not see "Filters (1)" in the "#dropdownFiltersButton" "css_element"
|
||||
And I should see "Filters" in the "#dropdownFiltersButton" "css_element"
|
||||
And "[data-region='report-filters']" "css_element" should be visible
|
||||
And the following fields in the "Result" "core_reportbuilder > Filter" match these values:
|
||||
| Result operator | Is any value |
|
||||
And I should not see "Nothing to display"
|
||||
|
||||
+1
-1
@@ -85,7 +85,7 @@ class set_block_protection_test extends \externallib_advanced_testcase {
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function execute_provider(): array {
|
||||
public function execute_provider(): array {
|
||||
return [
|
||||
[
|
||||
'block_login',
|
||||
|
||||
+2
-2
@@ -72,7 +72,7 @@ class set_plugin_order_test extends \externallib_advanced_testcase {
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function execute_editor_provider(): array {
|
||||
public function execute_editor_provider(): array {
|
||||
$pluginmanager = \core_plugin_manager::instance();
|
||||
$allplugins = array_keys($pluginmanager->get_plugins_of_type('editor'));
|
||||
|
||||
@@ -142,7 +142,7 @@ class set_plugin_order_test extends \externallib_advanced_testcase {
|
||||
$this->assertIsArray(set_plugin_order::execute($plugin, 1));
|
||||
}
|
||||
|
||||
public static function execute_non_orderable_provider(): array {
|
||||
public function execute_non_orderable_provider(): array {
|
||||
return [
|
||||
// Activities do not support ordering.
|
||||
['mod_assign'],
|
||||
|
||||
+1
-1
@@ -66,7 +66,7 @@ class set_plugin_state_test extends \externallib_advanced_testcase {
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function execute_standard_provider(): array {
|
||||
public function execute_standard_provider(): array {
|
||||
$generatetestsfor = function (string $plugin): array {
|
||||
return [
|
||||
[
|
||||
|
||||
@@ -110,7 +110,7 @@ class task_logs_test extends core_reportbuilder_testcase {
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public static function datasource_filters_provider(): array {
|
||||
public function datasource_filters_provider(): array {
|
||||
return [
|
||||
'Filter name' => ['task_log:name', [
|
||||
'task_log:name_values' => [send_schedules::class],
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
# tool (plugin type) Upgrade notes
|
||||
|
||||
## 4.5
|
||||
|
||||
### Removed
|
||||
|
||||
- The Convert to InnoDB plugin (`tool_innodb`) has been completely removed.
|
||||
|
||||
For more information see [MDL-78776](https://tracker.moodle.org/browse/MDL-78776)
|
||||
@@ -1,3 +0,0 @@
|
||||
define("tool_admin_presets/admin_presets_list",["exports","core/event_dispatcher","core/notification","core/prefetch","core/str","core/pending","core_reportbuilder/local/events","core_reportbuilder/local/selectors","core/toast","tool_admin_presets/repository"],(function(_exports,_event_dispatcher,_notification,_prefetch,_str,_pending,reportEvents,reportSelectors,_toast,_repository){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}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_notification=_interopRequireDefault(_notification),_pending=_interopRequireDefault(_pending),reportEvents=_interopRequireWildcard(reportEvents),reportSelectors=_interopRequireWildcard(reportSelectors);_exports.init=()=>{(0,_prefetch.prefetchStrings)("core",["delete"]),(0,_prefetch.prefetchStrings)("tool_admin_presets",["deleteshow","deletepreset","eventpresetdeleted","deletepreviouslyapplied"]),document.addEventListener("click",(event=>{const presetDelete=event.target.closest('[data-action="admin-preset-delete"]');if(presetDelete){event.preventDefault();const triggerElement=presetDelete.closest(".dropdown").querySelector(".dropdown-toggle"),stringid=presetDelete.dataset.presetRollback?"deletepreviouslyapplied":"deletepreset";_notification.default.saveCancelPromise((0,_str.getString)("deleteshow","tool_admin_presets"),(0,_str.getString)(stringid,"tool_admin_presets",presetDelete.dataset.presetName),(0,_str.getString)("delete","core"),{triggerElement:triggerElement}).then((()=>{const pendingPromise=new _pending.default("tool/admin_presets:deletepreset"),reportElement=event.target.closest(reportSelectors.regions.report);return(0,_repository.deletePreset)(presetDelete.dataset.presetId).then((()=>(0,_toast.add)((0,_str.getString)("eventpresetdeleted","tool_admin_presets")))).then((()=>((0,_event_dispatcher.dispatchEvent)(reportEvents.tableReload,{preservePagination:!0},reportElement),pendingPromise.resolve()))).catch(_notification.default.exception)})).catch((()=>{}))}}))}}));
|
||||
|
||||
//# sourceMappingURL=admin_presets_list.min.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"admin_presets_list.min.js","sources":["../src/admin_presets_list.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 * Admin presets list management\n *\n * @module tool_admin_presets/admin_presets_list\n * @copyright 2024 David Carrillo <davidmc@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n\"use strict\";\n\nimport {dispatchEvent} from 'core/event_dispatcher';\nimport Notification from 'core/notification';\nimport {prefetchStrings} from 'core/prefetch';\nimport {getString} from 'core/str';\nimport Pending from 'core/pending';\nimport * as reportEvents from 'core_reportbuilder/local/events';\nimport * as reportSelectors from 'core_reportbuilder/local/selectors';\nimport {add as addToast} from 'core/toast';\nimport {deletePreset} from 'tool_admin_presets/repository';\n\n/**\n * Initialise module\n */\nexport const init = () => {\n prefetchStrings('core', [\n 'delete',\n ]);\n\n prefetchStrings('tool_admin_presets', [\n 'deleteshow',\n 'deletepreset',\n 'eventpresetdeleted',\n 'deletepreviouslyapplied'\n ]);\n\n document.addEventListener('click', event => {\n const presetDelete = event.target.closest('[data-action=\"admin-preset-delete\"]');\n if (presetDelete) {\n event.preventDefault();\n\n // Use triggerElement to return focus to the action menu toggle.\n const triggerElement = presetDelete.closest('.dropdown').querySelector('.dropdown-toggle');\n const stringid = presetDelete.dataset.presetRollback ? 'deletepreviouslyapplied' : 'deletepreset';\n\n /* eslint-disable promise/no-nesting */\n Notification.saveCancelPromise(\n getString('deleteshow', 'tool_admin_presets'),\n getString(stringid, 'tool_admin_presets', presetDelete.dataset.presetName),\n getString('delete', 'core'),\n {triggerElement}\n ).then(() => {\n const pendingPromise = new Pending('tool/admin_presets:deletepreset');\n const reportElement = event.target.closest(reportSelectors.regions.report);\n\n return deletePreset(presetDelete.dataset.presetId)\n .then(() => addToast(getString('eventpresetdeleted', 'tool_admin_presets')))\n .then(() => {\n dispatchEvent(reportEvents.tableReload, {preservePagination: true}, reportElement);\n return pendingPromise.resolve();\n })\n .catch(Notification.exception);\n }).catch(() => {\n return;\n });\n }\n });\n};\n"],"names":["document","addEventListener","event","presetDelete","target","closest","preventDefault","triggerElement","querySelector","stringid","dataset","presetRollback","saveCancelPromise","presetName","then","pendingPromise","Pending","reportElement","reportSelectors","regions","report","presetId","reportEvents","tableReload","preservePagination","resolve","catch","Notification","exception"],"mappings":"+qDAsCoB,mCACA,OAAQ,CACpB,yCAGY,qBAAsB,CAClC,aACA,eACA,qBACA,4BAGJA,SAASC,iBAAiB,SAASC,cACzBC,aAAeD,MAAME,OAAOC,QAAQ,0CACtCF,aAAc,CACdD,MAAMI,uBAGAC,eAAiBJ,aAAaE,QAAQ,aAAaG,cAAc,oBACjEC,SAAWN,aAAaO,QAAQC,eAAiB,0BAA4B,qCAGtEC,mBACT,kBAAU,aAAc,uBACxB,kBAAUH,SAAU,qBAAsBN,aAAaO,QAAQG,aAC/D,kBAAU,SAAU,QACpB,CAACN,eAAAA,iBACHO,MAAK,WACGC,eAAiB,IAAIC,iBAAQ,mCAC7BC,cAAgBf,MAAME,OAAOC,QAAQa,gBAAgBC,QAAQC,eAE5D,4BAAajB,aAAaO,QAAQW,UACpCP,MAAK,KAAM,eAAS,kBAAU,qBAAsB,yBACpDA,MAAK,yCACYQ,aAAaC,YAAa,CAACC,oBAAoB,GAAOP,eAC7DF,eAAeU,aAEzBC,MAAMC,sBAAaC,cACzBF,OAAM"}
|
||||
@@ -1,10 +0,0 @@
|
||||
define("tool_admin_presets/repository",["exports","core/ajax"],(function(_exports,_ajax){var obj;
|
||||
/**
|
||||
* Module to handle presets AJAX requests
|
||||
*
|
||||
* @module tool_admin_presets/repository
|
||||
* @copyright 2024 David Carrillo <davidmc@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.deletePreset=void 0,_ajax=(obj=_ajax)&&obj.__esModule?obj:{default:obj};_exports.deletePreset=presetId=>{const request={methodname:"tool_admin_presets_delete_preset",args:{id:presetId}};return _ajax.default.call([request])[0]}}));
|
||||
|
||||
//# sourceMappingURL=repository.min.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"repository.min.js","sources":["../src/repository.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 * Module to handle presets AJAX requests\n *\n * @module tool_admin_presets/repository\n * @copyright 2024 David Carrillo <davidmc@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\n\n/**\n * Delete given preset\n *\n * @param {Number} presetId\n * @return {Promise}\n */\nexport const deletePreset = presetId => {\n const request = {\n methodname: 'tool_admin_presets_delete_preset',\n args: {id: presetId}\n };\n\n return Ajax.call([request])[0];\n};\n"],"names":["presetId","request","methodname","args","id","Ajax","call"],"mappings":";;;;;;;oKA+B4BA,iBAClBC,QAAU,CACZC,WAAY,mCACZC,KAAM,CAACC,GAAIJ,kBAGRK,cAAKC,KAAK,CAACL,UAAU"}
|
||||
@@ -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/>.
|
||||
|
||||
/**
|
||||
* Admin presets list management
|
||||
*
|
||||
* @module tool_admin_presets/admin_presets_list
|
||||
* @copyright 2024 David Carrillo <davidmc@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
import {dispatchEvent} from 'core/event_dispatcher';
|
||||
import Notification from 'core/notification';
|
||||
import {prefetchStrings} from 'core/prefetch';
|
||||
import {getString} from 'core/str';
|
||||
import Pending from 'core/pending';
|
||||
import * as reportEvents from 'core_reportbuilder/local/events';
|
||||
import * as reportSelectors from 'core_reportbuilder/local/selectors';
|
||||
import {add as addToast} from 'core/toast';
|
||||
import {deletePreset} from 'tool_admin_presets/repository';
|
||||
|
||||
/**
|
||||
* Initialise module
|
||||
*/
|
||||
export const init = () => {
|
||||
prefetchStrings('core', [
|
||||
'delete',
|
||||
]);
|
||||
|
||||
prefetchStrings('tool_admin_presets', [
|
||||
'deleteshow',
|
||||
'deletepreset',
|
||||
'eventpresetdeleted',
|
||||
'deletepreviouslyapplied'
|
||||
]);
|
||||
|
||||
document.addEventListener('click', event => {
|
||||
const presetDelete = event.target.closest('[data-action="admin-preset-delete"]');
|
||||
if (presetDelete) {
|
||||
event.preventDefault();
|
||||
|
||||
// Use triggerElement to return focus to the action menu toggle.
|
||||
const triggerElement = presetDelete.closest('.dropdown').querySelector('.dropdown-toggle');
|
||||
const stringid = presetDelete.dataset.presetRollback ? 'deletepreviouslyapplied' : 'deletepreset';
|
||||
|
||||
/* eslint-disable promise/no-nesting */
|
||||
Notification.saveCancelPromise(
|
||||
getString('deleteshow', 'tool_admin_presets'),
|
||||
getString(stringid, 'tool_admin_presets', presetDelete.dataset.presetName),
|
||||
getString('delete', 'core'),
|
||||
{triggerElement}
|
||||
).then(() => {
|
||||
const pendingPromise = new Pending('tool/admin_presets:deletepreset');
|
||||
const reportElement = event.target.closest(reportSelectors.regions.report);
|
||||
|
||||
return deletePreset(presetDelete.dataset.presetId)
|
||||
.then(() => addToast(getString('eventpresetdeleted', 'tool_admin_presets')))
|
||||
.then(() => {
|
||||
dispatchEvent(reportEvents.tableReload, {preservePagination: true}, reportElement);
|
||||
return pendingPromise.resolve();
|
||||
})
|
||||
.catch(Notification.exception);
|
||||
}).catch(() => {
|
||||
return;
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -1,39 +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/>.
|
||||
|
||||
/**
|
||||
* Module to handle presets AJAX requests
|
||||
*
|
||||
* @module tool_admin_presets/repository
|
||||
* @copyright 2024 David Carrillo <davidmc@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
import Ajax from 'core/ajax';
|
||||
|
||||
/**
|
||||
* Delete given preset
|
||||
*
|
||||
* @param {Number} presetId
|
||||
* @return {Promise}
|
||||
*/
|
||||
export const deletePreset = presetId => {
|
||||
const request = {
|
||||
methodname: 'tool_admin_presets_delete_preset',
|
||||
args: {id: presetId}
|
||||
};
|
||||
|
||||
return Ajax.call([request])[0];
|
||||
};
|
||||
@@ -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 tool_admin_presets\external;
|
||||
|
||||
use core_adminpresets\manager;
|
||||
use core_external\external_api;
|
||||
use core_external\external_function_parameters;
|
||||
use core_external\external_value;
|
||||
|
||||
/**
|
||||
* External function tool_admin_presets_delete_preset
|
||||
*
|
||||
* @package tool_admin_presets
|
||||
* @copyright 2024 David Carrillo <davidmc@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class delete_preset extends external_api {
|
||||
|
||||
/**
|
||||
* Describes the parameters.
|
||||
*
|
||||
* @return external_function_parameters
|
||||
*/
|
||||
public static function execute_parameters(): external_function_parameters {
|
||||
return new external_function_parameters([
|
||||
'id' => new external_value(PARAM_INT),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* External function to delete custom presets.
|
||||
*
|
||||
* @param int $id
|
||||
*/
|
||||
public static function execute(int $id): void {
|
||||
// Parameter validation.
|
||||
[
|
||||
'id' => $id,
|
||||
] = self::validate_parameters(self::execute_parameters(), [
|
||||
'id' => $id,
|
||||
]);
|
||||
|
||||
// Validate context.
|
||||
$context = \context_system::instance();
|
||||
self::validate_context($context);
|
||||
|
||||
require_capability('moodle/site:config', $context);
|
||||
|
||||
(new manager())->delete_preset($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes the data returned from the external function.
|
||||
*/
|
||||
public static function execute_returns(): void {
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,9 @@
|
||||
namespace tool_admin_presets\local\action;
|
||||
|
||||
use context_system;
|
||||
use moodle_url;
|
||||
use core_adminpresets\manager;
|
||||
use tool_admin_presets\output\presets_list;
|
||||
use tool_admin_presets\output\export_import;
|
||||
|
||||
/**
|
||||
@@ -39,7 +41,7 @@ class base {
|
||||
'preview' => 'preset_previewed',
|
||||
'load' => 'preset_loaded',
|
||||
'rollback' => 'preset_reverted',
|
||||
'download_xml' => 'preset_downloaded',
|
||||
'download_xml' => 'preset_downloaded'
|
||||
];
|
||||
|
||||
/** @var string The main action (delete, export, import, load...). **/
|
||||
@@ -77,19 +79,14 @@ class base {
|
||||
* actions (preview, load, download, delete and rollback)
|
||||
*/
|
||||
public function show(): void {
|
||||
global $DB, $OUTPUT, $PAGE;
|
||||
global $DB, $OUTPUT;
|
||||
|
||||
$this->outputs = $OUTPUT->container_start('d-flex flex-wrap justify-content-end');
|
||||
$options = new export_import();
|
||||
$this->outputs .= $OUTPUT->render($options);
|
||||
$this->outputs .= $OUTPUT->container_end();
|
||||
$this->outputs = $OUTPUT->render($options);
|
||||
|
||||
$PAGE->requires->js_call_amd('tool_admin_presets/admin_presets_list', 'init');
|
||||
|
||||
$report = \core_reportbuilder\system_report_factory::create(
|
||||
\tool_admin_presets\reportbuilder\local\systemreports\admin_presets::class,
|
||||
$PAGE->context, '', '', 0, []);
|
||||
$this->outputs .= $report->output();
|
||||
$presets = $DB->get_records('adminpresets');
|
||||
$list = new presets_list($presets, true);
|
||||
$this->outputs .= $OUTPUT->render($list);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user