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 }}
|
||||
+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();
|
||||
@@ -1,7 +0,0 @@
|
||||
issueNumber: MDL-43938
|
||||
notes:
|
||||
core_badges:
|
||||
- message: >-
|
||||
The `badges/newbadge.php` page has been deprecated and merged with
|
||||
`badges/edit.php`. Please, use `badges/edit.php` instead.
|
||||
type: deprecated
|
||||
@@ -1,6 +0,0 @@
|
||||
issueNumber: MDL-48940
|
||||
notes:
|
||||
core:
|
||||
- message: >
|
||||
The previously deprecated function `search_generate_text_SQL` has been removed and can no longer be used.
|
||||
type: removed
|
||||
@@ -1,8 +0,0 @@
|
||||
issueNumber: MDL-52046
|
||||
notes:
|
||||
core_reportbuilder:
|
||||
- message: >-
|
||||
The return type of the `set_checkbox_toggleall` callback, defined by
|
||||
system reports, can now be null. Use if the checkbox should not be shown
|
||||
for the row.
|
||||
type: improved
|
||||
@@ -1,11 +0,0 @@
|
||||
issueNumber: MDL-54105
|
||||
notes:
|
||||
mod_assign:
|
||||
- message: |
|
||||
Added 2 new settings:
|
||||
- `mod_assign/defaultgradetype`
|
||||
- The value of this setting dictates which of the `GRADE_TYPE_X` constants is the default option when creating new instances of the assignment.
|
||||
- The default value is `GRADE_TYPE_VALUE` (Point)
|
||||
- `mod_assign/defaultgradescale`
|
||||
- The value of this setting dictates which of the existing scales is the default option when creating new instances of the assignment.
|
||||
type: improved
|
||||
@@ -1,8 +0,0 @@
|
||||
issueNumber: MDL-62640
|
||||
notes:
|
||||
core_admin:
|
||||
- message: >-
|
||||
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.
|
||||
type: removed
|
||||
@@ -1,9 +0,0 @@
|
||||
issueNumber: MDL-62640
|
||||
notes:
|
||||
core:
|
||||
- message: |
|
||||
The code that is associated with the slashargument option has been deprecated. Here is the detail:
|
||||
- The parameter $support of the \url::set_slashargument().
|
||||
- The parameter $includeslashargument of the \url::get_path().
|
||||
- The $CFG->yuislasharguments configuration.
|
||||
type: deprecated
|
||||
@@ -1,7 +0,0 @@
|
||||
issueNumber: MDL-62640
|
||||
notes:
|
||||
core:
|
||||
- message: |
|
||||
Due to the removal of the slashargument configuration, the server check page will no longer inspect the slash argument,
|
||||
therefore, the function check_slasharguments() is deprecated because it is no longer used.
|
||||
type: deprecated
|
||||
@@ -1,8 +0,0 @@
|
||||
issueNumber: MDL-64148
|
||||
notes:
|
||||
core:
|
||||
- message: >-
|
||||
The following method has been deprecated and should no longer be used:
|
||||
`reset_password_and_mail`. Please consider using `setnew_password_and_mail` as
|
||||
a replacement.
|
||||
type: deprecated
|
||||
@@ -1,6 +0,0 @@
|
||||
issueNumber: MDL-64148
|
||||
notes:
|
||||
core:
|
||||
- message: >-
|
||||
Add `\core_user::get_name_placeholders()` to return an array of user name fields.
|
||||
type: improved
|
||||
@@ -1,19 +0,0 @@
|
||||
issueNumber: MDL-66151
|
||||
notes:
|
||||
core_role:
|
||||
- message: |
|
||||
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` |
|
||||
type: improved
|
||||
@@ -1,11 +0,0 @@
|
||||
issueNumber: MDL-66903
|
||||
notes:
|
||||
core:
|
||||
- message: |
|
||||
The following classes have been renamed and now support autoloading.
|
||||
Existing classes are currently unaffected.
|
||||
|
||||
| Old class name | New class name |
|
||||
| --- | --- |
|
||||
| `\core_component` | `\core\component` |
|
||||
type: improved
|
||||
@@ -1,20 +0,0 @@
|
||||
issueNumber: MDL-66903
|
||||
notes:
|
||||
core:
|
||||
- message: >
|
||||
Added the ability for unit tests to autoload classes in the
|
||||
`\[component]\tests\`
|
||||
|
||||
namespace from the `[path/to/component]/tests/classes` directory.
|
||||
type: improved
|
||||
- message: >
|
||||
Added a helper to load fixtures from a components `tests/fixtures/`
|
||||
folder:
|
||||
|
||||
|
||||
```php
|
||||
|
||||
advanced_testcase::load_fixture(string $component, string $fixture): void;
|
||||
|
||||
```
|
||||
type: improved
|
||||
@@ -1,8 +0,0 @@
|
||||
issueNumber: MDL-67554
|
||||
notes:
|
||||
tool_oauth2:
|
||||
- message: >
|
||||
The `get_additional_login_parameters()` method now supports adding the
|
||||
language code to the authentication request so that the OAuth2 login
|
||||
page matches the language in Moodle.
|
||||
type: improved
|
||||
@@ -1,11 +0,0 @@
|
||||
issueNumber: MDL-69684
|
||||
notes:
|
||||
core:
|
||||
- message: >-
|
||||
Redis session cache has been improved to make a single call where two
|
||||
were used before.
|
||||
type: improved
|
||||
- message: >-
|
||||
The minimum Redis server version is now 2.6.12.
|
||||
The minimum PHP Redis extension version is now 2.2.4.
|
||||
type: changed
|
||||
@@ -1,5 +0,0 @@
|
||||
issueNumber: MDL-70854
|
||||
notes:
|
||||
core:
|
||||
- message: Added stored progress bars
|
||||
type: improved
|
||||
@@ -1,7 +0,0 @@
|
||||
issueNumber: MDL-70983
|
||||
notes:
|
||||
core_badges:
|
||||
- message: Final removal of `BADGE_BACKPACKAPIURL` and `BADGE_BACKPACKWEBURL`.
|
||||
type: removed
|
||||
- message: The `OPEN_BADGES_V1` constant is deprecated and should not be used anymore.
|
||||
type: deprecated
|
||||
@@ -1,14 +0,0 @@
|
||||
issueNumber: MDL-71326
|
||||
notes:
|
||||
core:
|
||||
- message: >+
|
||||
- Final deprecation and removal of the following functions:
|
||||
- `plagiarism_plugin::get_configs()`
|
||||
- `plagiarism_plugin::get_file_results()`
|
||||
- `plagiarism_plugin::update_status()`, please use `{plugin name}_before_standard_top_of_body_html` instead.
|
||||
- Final deprecation and removal of `plagiarism_get_file_results()`.
|
||||
Please use `plagiarism_get_links()` instead.
|
||||
- Final deprecation and removal of `plagiarism_update_status()`. Please
|
||||
use `{plugin name}_before_standard_top_of_body_html()` instead.
|
||||
|
||||
type: deprecated
|
||||
@@ -1,6 +0,0 @@
|
||||
issueNumber: MDL-71748
|
||||
notes:
|
||||
core:
|
||||
- message: >
|
||||
The previously deprecated function `core_text::reset_caches()` has been removed and can no longer be used.
|
||||
type: removed
|
||||
@@ -1,7 +0,0 @@
|
||||
issueNumber: MDL-71823
|
||||
notes:
|
||||
core_user:
|
||||
- message: >-
|
||||
New `\core_user\hook\extend_user_menu` hook added to allow third party
|
||||
plugin to extend the user menu navigation
|
||||
type: improved
|
||||
@@ -1,6 +0,0 @@
|
||||
issueNumber: MDL-72353
|
||||
notes:
|
||||
report:
|
||||
- message: >-
|
||||
The previously deprecated `report_helper::save_selected_report` method has been removed and can no longer be used
|
||||
type: removed
|
||||
@@ -1,11 +0,0 @@
|
||||
issueNumber: MDL-72397
|
||||
notes:
|
||||
qbank_managecategories:
|
||||
- message: >
|
||||
Category lists are now generated by templates.
|
||||
The following classes have been deprecated:
|
||||
|
||||
- `question_category_list`
|
||||
|
||||
- `question_category_list_item`
|
||||
type: deprecated
|
||||
@@ -1,22 +0,0 @@
|
||||
issueNumber: MDL-72397
|
||||
notes:
|
||||
qbank_managecategories:
|
||||
- message: >
|
||||
The methods in the `question_category_object` class that are still
|
||||
required following this change have been split between
|
||||
`\qbank_managecategories\question_categories` (for the parts used within
|
||||
this plugin for display a list of categories) and
|
||||
`\core_question\category_manager` (for the parts used for generate CRUD
|
||||
operations on question categories, including outside of this plugin).
|
||||
This will allow `question_category_object` to be deprecated, and avoids
|
||||
other parts of the system wishing to manipulate question categories from
|
||||
having to violate cross-component communication rules.
|
||||
type: changed
|
||||
- message: >
|
||||
The methods `question_is_only_child_of_top_category_in_context`,
|
||||
`question_is_top_category` and `question_can_delete_cat` from
|
||||
`qbank_managecategories\helper` class have been deprecated and moved to
|
||||
the `\core_question\category_manager` class, minus the misleading
|
||||
`question_` prefix. Following the creation of this class, it does not
|
||||
make sense for them to live inside the `qbank_managecategories` plugin.
|
||||
type: deprecated
|
||||
@@ -1,18 +0,0 @@
|
||||
issueNumber: MDL-72397
|
||||
notes:
|
||||
qbank_managecategories:
|
||||
- message: >
|
||||
`qbank_managecategories\question_category_object` is now completely
|
||||
deprecated. Its methods have either been migrated to
|
||||
`qbank_managecategories\question_categories`,
|
||||
`core_question\category_manager`, or are no longer used at all.
|
||||
type: deprecated
|
||||
core:
|
||||
- message: >
|
||||
`moodle_list` and `list_item` were only used by
|
||||
`qbank_managecategories`, and these usages have been removed, so
|
||||
these classes (and thus all of listlib.php) are now deprecated.
|
||||
This method was the only usage of the `QUESTION_PAGE_LENGTH`
|
||||
constant, which was defined in `question_category_object.php`,
|
||||
and so is also now deprecated.
|
||||
type: deprecated
|
||||
@@ -1,7 +0,0 @@
|
||||
issueNumber: MDL-72424
|
||||
notes:
|
||||
mod_feedback:
|
||||
- message: >-
|
||||
The `feedback_check_is_switchrole` method has been deprecated as it
|
||||
didn't work
|
||||
type: deprecated
|
||||
@@ -1,11 +0,0 @@
|
||||
issueNumber: MDL-72620
|
||||
notes:
|
||||
repository_onedrive:
|
||||
- message: >
|
||||
The following previously deprecated methods have been removed and can no
|
||||
longer be used:
|
||||
|
||||
- `can_import_skydrive_files`
|
||||
|
||||
- `import_skydrive_files`
|
||||
type: removed
|
||||
@@ -1,11 +0,0 @@
|
||||
issueNumber: MDL-72786
|
||||
notes:
|
||||
report_eventlist:
|
||||
- message: >-
|
||||
The following deprecated methods in `report_eventlist_list_generator` have
|
||||
been removed:
|
||||
|
||||
- `get_core_events_list()`
|
||||
|
||||
- `get_non_core_event_list()`
|
||||
type: deprecated
|
||||
@@ -1,7 +0,0 @@
|
||||
issueNumber: MDL-73165
|
||||
notes:
|
||||
core:
|
||||
- message: >
|
||||
The following previously deprecated methods have been removed and can no longer be used:
|
||||
- `renderer_base::should_display_main_logo`
|
||||
type: removed
|
||||
@@ -1,5 +0,0 @@
|
||||
issueNumber: MDL-73284
|
||||
notes:
|
||||
core_message:
|
||||
- message: Final deprecation `MESSAGE_DEFAULT_LOGGEDOFF` / `MESSAGE_DEFAULT_LOGGEDIN`.
|
||||
type: removed
|
||||
@@ -1,7 +0,0 @@
|
||||
issueNumber: MDL-73976
|
||||
notes:
|
||||
core_course:
|
||||
- message: >-
|
||||
The previously deprecated `print_course_request_buttons` method has been
|
||||
removed and can no longer be used
|
||||
type: removed
|
||||
@@ -1,8 +0,0 @@
|
||||
issueNumber: MDL-74050
|
||||
notes:
|
||||
mod_assign:
|
||||
- message: >
|
||||
A new web service called `mod_assign_remove_submission` has been created
|
||||
to remove the submission for a specific user ID and assignment activity
|
||||
ID
|
||||
type: improved
|
||||
@@ -1,7 +0,0 @@
|
||||
issueNumber: MDL-74251
|
||||
notes:
|
||||
theme:
|
||||
- message: >-
|
||||
Removed all references to `iconhelp`, `icon-pre`, `icon-post`, `iconlarge`, and
|
||||
`iconsort` classes
|
||||
type: removed
|
||||
@@ -1,5 +0,0 @@
|
||||
issueNumber: MDL-74484
|
||||
notes:
|
||||
core:
|
||||
- message: Final deprecation of `print_error()`. Use `moodle_exception` instead.
|
||||
type: removed
|
||||
@@ -1,9 +0,0 @@
|
||||
issueNumber: MDL-74581
|
||||
notes:
|
||||
core_grades:
|
||||
- message: >
|
||||
The following previously deprecated Behat step helper methods have been
|
||||
removed and can no longer be used:
|
||||
- `behat_grade::select_in_gradebook_navigation_selector`
|
||||
- `behat_grade::select_in_gradebook_tabs`
|
||||
type: removed
|
||||
@@ -1,18 +0,0 @@
|
||||
issueNumber: MDL-74583
|
||||
notes:
|
||||
core_reportbuilder:
|
||||
- message: >-
|
||||
Support for the following entity classes, renamed since 4.1, has now
|
||||
been removed completely:
|
||||
|
||||
|
||||
- `core_admin\local\entities\task_log`
|
||||
|
||||
- `core_cohort\local\entities\cohort`
|
||||
|
||||
- `core_cohort\local\entities\cohort_member`
|
||||
|
||||
- `core_course\local\entities\course_category`
|
||||
|
||||
- `report_configlog\local\entities\config_change`
|
||||
type: removed
|
||||
@@ -1,5 +0,0 @@
|
||||
issueNumber: MDL-74843
|
||||
notes:
|
||||
core:
|
||||
- message: 'Final deprecation of `\core\task\manager::ensure_adhoc_task_qos()`'
|
||||
type: removed
|
||||
@@ -1,8 +0,0 @@
|
||||
issueNumber: MDL-74939
|
||||
notes:
|
||||
core:
|
||||
- message: >-
|
||||
Support for deprecated `<component>\local\views\secondary`
|
||||
block/activity namespace, for overriding secondary navigation, is now
|
||||
entirely removed
|
||||
type: removed
|
||||
@@ -1,8 +0,0 @@
|
||||
issueNumber: MDL-75022
|
||||
notes:
|
||||
core_backup:
|
||||
- message: >-
|
||||
Final deprecation and removal of `core_backup\copy\copy` in
|
||||
`backup/util/ui/classes/copy.php`. Please use `copy_helper` from
|
||||
`backup/util/helper/copy_helper.class.php` instead.
|
||||
type: removed
|
||||
@@ -1,7 +0,0 @@
|
||||
issueNumber: MDL-75025
|
||||
notes:
|
||||
core_backup:
|
||||
- message: >-
|
||||
Final deprecation of `base_controller::get_copy()`. Please use
|
||||
`restore_controller::get_copy()` instead.
|
||||
type: removed
|
||||
@@ -1,6 +0,0 @@
|
||||
issueNumber: MDL-75025
|
||||
notes:
|
||||
core_backup:
|
||||
- message: 'Final deprecation of `base_controller::set_copy()`. Please use a restore
|
||||
controller for storing copy information instead.'
|
||||
type: removed
|
||||
@@ -1,6 +0,0 @@
|
||||
issueNumber: MDL-75850
|
||||
notes:
|
||||
core_files:
|
||||
- message: |
|
||||
A new hook, `\core_files\hook\after_file_created`, has been created to allow the inspection of files after they have been saved in the filesystem.
|
||||
type: improved
|
||||
@@ -1,7 +0,0 @@
|
||||
issueNumber: MDL-76392
|
||||
notes:
|
||||
core_reportbuilder:
|
||||
- message: >-
|
||||
System reports now support native entity column aggregation via each
|
||||
columns `set_aggregation()` method
|
||||
type: improved
|
||||
@@ -1,8 +0,0 @@
|
||||
issueNumber: MDL-76690
|
||||
notes:
|
||||
core_reportbuilder:
|
||||
- message: >
|
||||
The following previously deprecated local helper methods have been removed and can no longer be used:
|
||||
- `audience::get_all_audiences_menu_types`
|
||||
- `report::get_available_columns`
|
||||
type: removed
|
||||
@@ -1,5 +0,0 @@
|
||||
issueNumber: MDL-77167
|
||||
notes:
|
||||
core:
|
||||
- message: Remove deprecation layer for YUI Events. The deprecation layer was introduced with MDL-70990 and MDL-72291.
|
||||
type: removed
|
||||
@@ -1,7 +0,0 @@
|
||||
issueNumber: MDL-78030
|
||||
notes:
|
||||
core_user:
|
||||
- message: >-
|
||||
The `participants_search::get_total_participants_count()` is no longer
|
||||
used since the total count can be obtained from `::get_participants()`
|
||||
type: deprecated
|
||||
@@ -1,21 +0,0 @@
|
||||
issueNumber: MDL-78030
|
||||
notes:
|
||||
core:
|
||||
- message: |
|
||||
Two new functions have been introduced in the \moodle_database class:
|
||||
- `get_counted_records_sql()`
|
||||
- `get_counted_recordset_sql()`
|
||||
|
||||
These methods are compatible with all databases.
|
||||
|
||||
They will check the current running database engine and apply the COUNT window function if it is supported,
|
||||
otherwise, they will use the usual COUNT function.
|
||||
|
||||
The COUNT window function optimization is applied to the following databases:
|
||||
- PostgreSQL
|
||||
- MariaDB
|
||||
- Oracle
|
||||
|
||||
Note: MySQL and SQL Server do not use this optimization due to insignificant performance differences before and
|
||||
after the improvement.
|
||||
type: improved
|
||||
@@ -1,7 +0,0 @@
|
||||
issueNumber: MDL-78293
|
||||
notes:
|
||||
core_user:
|
||||
- message: >
|
||||
The visibility of the methods: `check_access_for_dynamic_submission()` and `get_options()`
|
||||
in `\core_user\form\private_files` has been changed from protected to public.
|
||||
type: changed
|
||||
@@ -1,7 +0,0 @@
|
||||
issueNumber: MDL-78662
|
||||
notes:
|
||||
core_question:
|
||||
- message: >
|
||||
A new utility function `format_question_fragment` has been created so
|
||||
that question content can filter based on filters.
|
||||
type: improved
|
||||
@@ -1,8 +0,0 @@
|
||||
issueNumber: MDL-78773
|
||||
notes:
|
||||
report:
|
||||
- message: >
|
||||
The `report_helper::print_report_selector` method accepts an additional
|
||||
argument for adding content to the tertiary navigation to align with the
|
||||
report selector
|
||||
type: changed
|
||||
@@ -1,5 +0,0 @@
|
||||
issueNumber: MDL-78776
|
||||
notes:
|
||||
tool:
|
||||
- message: The Convert to InnoDB plugin (`tool_innodb`) has been completely removed.
|
||||
type: removed
|
||||
@@ -1,7 +0,0 @@
|
||||
issueNumber: MDL-79011
|
||||
notes:
|
||||
core:
|
||||
- message: >-
|
||||
The `after_config()` callback has been converted to a hook,
|
||||
`\core\hook\after_config`.
|
||||
type: improved
|
||||
@@ -1,7 +0,0 @@
|
||||
issueNumber: MDL-79496
|
||||
notes:
|
||||
core_webservice:
|
||||
- message: >-
|
||||
The `token_table` and `token_filter` classes have been deprecated, in
|
||||
favour of new report builder implementation.
|
||||
type: deprecated
|
||||
@@ -1,7 +0,0 @@
|
||||
issueNumber: MDL-79546
|
||||
notes:
|
||||
quiz:
|
||||
- message: >-
|
||||
The functions `quiz_overview_report::regrade_attempts` and `regrade_batch_of_attempts` now
|
||||
have a new optional parameter $slots to only regrade some slots in each attempt (default all).
|
||||
type: improved
|
||||
@@ -1,7 +0,0 @@
|
||||
issueNumber: MDL-79628
|
||||
notes:
|
||||
core:
|
||||
- message: >-
|
||||
The `navigation_cache` class now uses the Moodle Universal Cache (MUC) to store the navigation cache data instead of storing
|
||||
it in the global `$SESSION` variable.
|
||||
type: fixed
|
||||
@@ -1,6 +0,0 @@
|
||||
issueNumber: MDL-79628
|
||||
notes:
|
||||
core:
|
||||
- message: >-
|
||||
The `$timeout` property of the `navigation_cache` class has been deprecated.
|
||||
type: deprecated
|
||||
@@ -1,9 +0,0 @@
|
||||
issueNumber: MDL-80017
|
||||
notes:
|
||||
core_grades:
|
||||
- message: >
|
||||
The grade `itemname` property contained in the return structure of
|
||||
the following external methods is now PARAM_RAW:
|
||||
- `core_grades_get_gradeitems`
|
||||
- `gradereport_user_get_grade_items`
|
||||
type: changed
|
||||
@@ -1,11 +0,0 @@
|
||||
issueNumber: MDL-80275
|
||||
notes:
|
||||
core:
|
||||
- message: >
|
||||
The class autoloader has been moved to an earlier point in the Moodle
|
||||
bootstrap.
|
||||
|
||||
|
||||
Autoloaded classes are now available to scripts using the
|
||||
`ABORT_AFTER_CONFIG` constant.
|
||||
type: changed
|
||||
@@ -1,18 +0,0 @@
|
||||
issueNumber: MDL-80741
|
||||
notes:
|
||||
mod_assign:
|
||||
- message: >
|
||||
The default option "Never" for `attemptreopenmethod` setting, which
|
||||
disallowed multiple attempts at the assignment, has been removed. This
|
||||
option was unnecessary because limiting attempts to 1 through the
|
||||
`maxattempts` setting achieves the same behavior.
|
||||
type: removed
|
||||
- message: >
|
||||
The constant `ASSIGN_ATTEMPT_REOPEN_METHOD_NONE` has been deprecated,
|
||||
and a new default value for `attemptreopenmethod` has been set to
|
||||
"Automatically until pass".
|
||||
type: deprecated
|
||||
- message: >
|
||||
A new default value for `attemptreopenmethod` has been set to
|
||||
"Automatically until pass".
|
||||
type: improved
|
||||
@@ -1,20 +0,0 @@
|
||||
issueNumber: MDL-80744
|
||||
notes:
|
||||
core_grades:
|
||||
- message: >-
|
||||
The behat step definition
|
||||
`behat_grade::i_confirm_in_search_within_the_gradebook_widget_exists` has
|
||||
been deprecated. Please use
|
||||
`behat_general::i_confirm_in_search_combobox_exists` instead.
|
||||
type: deprecated
|
||||
- message: >-
|
||||
The behat step definition
|
||||
`behat_grade::i_confirm_in_search_within_the_gradebook_widget_does_not_exist`
|
||||
has been deprecated. Please use
|
||||
`behat_general::i_confirm_in_search_combobox_does_not_exist` instead.
|
||||
type: deprecated
|
||||
- message: >-
|
||||
The behat step definition `behat_grade::i_click_on_in_search_widget` has
|
||||
been deprecated. Please use `behat_general::i_click_on_in_search_combobox`
|
||||
instead.
|
||||
type: deprecated
|
||||
@@ -1,23 +0,0 @@
|
||||
issueNumber: MDL-80745
|
||||
notes:
|
||||
core_grades:
|
||||
- message: >
|
||||
The `core_grades_renderer::group_selector()` method has been deprecated.
|
||||
Please use `\core_course\output\actionbar\renderer` to render a
|
||||
`group_selector` renderable instead.
|
||||
type: deprecated
|
||||
gradereport_grader:
|
||||
- message: >
|
||||
The `gradereport_grader/group` ESM has been deprecated. Please use
|
||||
`core_course/actionbar/group` instead.
|
||||
type: deprecated
|
||||
gradereport_singleview:
|
||||
- message: >
|
||||
The `gradereport_singleview/group` ESM has been deprecated. Please use
|
||||
`core_course/actionbar/group` instead.
|
||||
type: deprecated
|
||||
gradereport_user:
|
||||
- message: >
|
||||
The `gradereport_user/group` ESM has been deprecated. Please use
|
||||
`core_course/actionbar/group` instead.
|
||||
type: deprecated
|
||||
@@ -1,9 +0,0 @@
|
||||
issueNumber: MDL-80747
|
||||
notes:
|
||||
core:
|
||||
- message: >
|
||||
The `\core\output\select_menu` widget now supports rendering dividers
|
||||
between menu options. Empty elements (null or empty strings) within the
|
||||
array of options are considered and rendered as dividers in the dropdown
|
||||
menu.
|
||||
type: improved
|
||||
@@ -1,9 +0,0 @@
|
||||
issueNumber: MDL-80747
|
||||
notes:
|
||||
core:
|
||||
- message: >
|
||||
The `core\output\select_menu` widget now supports a new feature: inline
|
||||
labels. You can render the label inside the combobox widget by passing
|
||||
`true` to the `$inlinelabel` parameter when calling the `->set_label()`
|
||||
method.
|
||||
type: improved
|
||||
@@ -1,11 +0,0 @@
|
||||
issueNumber: MDL-80748
|
||||
notes:
|
||||
core_table:
|
||||
- message: >+
|
||||
A new `$reponsive` property (defaulting to `true`) has been added to the
|
||||
`core_table\flexible_table` class.
|
||||
|
||||
This property allows you to control whether the table is rendered as a
|
||||
responsive table.
|
||||
|
||||
type: improved
|
||||
@@ -1,19 +0,0 @@
|
||||
issueNumber: MDL-80750
|
||||
notes:
|
||||
mod_assign:
|
||||
- message: >
|
||||
A new method named `get_grading_batch_operation_details` has been added
|
||||
to the `assign_feedback_plugin` abstract class. Assignment feedback
|
||||
plugins can now override this method to define bulk action buttons that
|
||||
will appear in the sticky footer on the assignment grading page.
|
||||
type: improved
|
||||
- message: >
|
||||
The `assign_feedback_plugin::get_grading_batch_operations` method is now
|
||||
deprecated. Use
|
||||
`assign_feedback_plugin::get_grading_batch_operation_details` instead.
|
||||
type: deprecated
|
||||
- message: >
|
||||
The `assign_grading_table::plugingradingbatchoperations` property is
|
||||
removed. You can use
|
||||
`assign_feedback_plugin::get_grading_batch_operation_details` instead.
|
||||
type: deprecated
|
||||
@@ -1,8 +0,0 @@
|
||||
issueNumber: MDL-80752
|
||||
notes:
|
||||
mod_assign:
|
||||
- message: >
|
||||
The `$submissionpluginenabled` and `$submissioncount` parameters from
|
||||
the constructor of the `mod_assign\output::grading_actionmenu` class
|
||||
have been deprecated.
|
||||
type: deprecated
|
||||
@@ -1,8 +0,0 @@
|
||||
issueNumber: MDL-80890
|
||||
notes:
|
||||
core:
|
||||
- message: >
|
||||
A new hook called `core\hook\output\after_http_headers` has been
|
||||
created. This hook allow plugins to modify the content after headers are
|
||||
sent.
|
||||
type: improved
|
||||
@@ -1,9 +0,0 @@
|
||||
issueNumber: MDL-80962
|
||||
notes:
|
||||
factor_sms:
|
||||
- message: >
|
||||
The following classes are removed as the SMS feature now takes advantage of core_sms API:
|
||||
- sms_sent (admin/tool/mfa/factor/sms/classes/event/sms_sent.php)
|
||||
- aws_sns (admin/tool/mfa/factor/sms/classes/local/smsgateway/aws_sns.php)
|
||||
- gateway_interface (admin/tool/mfa/factor/sms/classes/local/smsgateway/gateway_interface.php)
|
||||
type: removed
|
||||
@@ -1,9 +0,0 @@
|
||||
issueNumber: MDL-80962
|
||||
notes:
|
||||
core:
|
||||
- message: >
|
||||
The following classes are deprecated as they are handled by core_sms API and smsgateway_aws plugin:
|
||||
- admin_settings_aws_region (lib/classes/aws/admin_settings_aws_region.php)
|
||||
- aws_helper (lib/classes/aws/aws_helper.php)
|
||||
- client_factory (lib/classes/aws/client_factory.php)
|
||||
type: deprecated
|
||||
@@ -1,8 +0,0 @@
|
||||
issueNumber: MDL-80962
|
||||
notes:
|
||||
core:
|
||||
- message: >
|
||||
The following test classes are removed as their base classes are deprecated:
|
||||
- admin_settings_aws_region_test (lib/tests/aws/admin_settings_aws_region_test.php)
|
||||
- aws_helper_test (lib/tests/aws/aws_helper_test.php)
|
||||
type: removed
|
||||
@@ -1,13 +0,0 @@
|
||||
issueNumber: MDL-81031
|
||||
notes:
|
||||
core:
|
||||
- message: |
|
||||
The following classes have been renamed.
|
||||
Existing classes are currently unaffected.
|
||||
|
||||
| Old class name | New class name |
|
||||
|
||||
| --- | --- |
|
||||
|
||||
| `\core_user` | `\core\user` |
|
||||
type: improved
|
||||
@@ -1,8 +0,0 @@
|
||||
issueNumber: MDL-81125
|
||||
notes:
|
||||
core_question:
|
||||
- message: >
|
||||
`column_base::from_column_name` now has an `$ignoremissing` parameter, which can
|
||||
be used to ignore if the class does not exist, instead of throwing an
|
||||
exception.
|
||||
type: changed
|
||||
@@ -1,11 +0,0 @@
|
||||
issueNumber: MDL-81168
|
||||
notes:
|
||||
core_reportbuilder:
|
||||
- message: >
|
||||
In order to better support float values in filter forms, the following
|
||||
filter types now cast given SQL prior to comparison:
|
||||
|
||||
- `duration`
|
||||
- `filesize`
|
||||
- `number`
|
||||
type: changed
|
||||
@@ -1,6 +0,0 @@
|
||||
issueNumber: MDL-81274
|
||||
notes:
|
||||
mod_data:
|
||||
- message: >-
|
||||
The `data_add_record` method accepts a new `$approved` parameter to set the corresponding state of the new record
|
||||
type: improved
|
||||
@@ -1,7 +0,0 @@
|
||||
issueNumber: MDL-81282
|
||||
notes:
|
||||
core:
|
||||
- message: >-
|
||||
New DML constant `SQL_INT_MAX` to define the size of a large integer
|
||||
with cross database platform support
|
||||
type: improved
|
||||
@@ -1,7 +0,0 @@
|
||||
issueNumber: MDL-81321
|
||||
notes:
|
||||
mod_data:
|
||||
- message: >-
|
||||
The `mod_data_renderer::render_fields_footer` method has been deprecated
|
||||
as it's no longer used
|
||||
type: deprecated
|
||||
@@ -1,6 +0,0 @@
|
||||
issueNumber: MDL-81330
|
||||
notes:
|
||||
core_reportbuilder:
|
||||
- message: >
|
||||
The base datasource `add_all_from_entities` method accepts a new optional parameter to specify which entities to add elements from
|
||||
type: changed
|
||||
@@ -1,12 +0,0 @@
|
||||
issueNumber: MDL-81428
|
||||
notes:
|
||||
core_message:
|
||||
- message: >
|
||||
The `\core_message\helper::togglecontact_link_params` now accepts a new
|
||||
optional param called `isrequested` to indicate the status of the
|
||||
contact request
|
||||
type: changed
|
||||
- message: >
|
||||
The `core_message/remove_contact_button` template is deprecated and will
|
||||
be removed in the future version
|
||||
type: deprecated
|
||||
@@ -1,8 +0,0 @@
|
||||
issueNumber: MDL-81433
|
||||
notes:
|
||||
core_reportbuilder:
|
||||
- message: >
|
||||
The following external methods now return tags data relevant to each custom report:
|
||||
- `core_reportbuilder_list_reports`
|
||||
- `core_reportbuilder_retrieve_report`
|
||||
type: improved
|
||||
@@ -1,6 +0,0 @@
|
||||
issueNumber: MDL-81434
|
||||
notes:
|
||||
core_reportbuilder:
|
||||
- message: |2
|
||||
Added a new database helper method `sql_replace_parameters` to help ensure uniqueness of parameters within a SQL expression
|
||||
type: improved
|
||||
@@ -1,14 +0,0 @@
|
||||
issueNumber: MDL-81523
|
||||
notes:
|
||||
core:
|
||||
- message: >
|
||||
All the setup and tear down methods of `PHPUnit` now are required to,
|
||||
always, call to their parent counterparts. This is a good practice to
|
||||
avoid future problems, especially when updating to PHPUnit >= 10.
|
||||
|
||||
This includes the following methods:
|
||||
- `setUp()`
|
||||
- `tearDown()`
|
||||
- `setUpBeforeClass()`
|
||||
- `tearDownAfterClass()`
|
||||
type: fixed
|
||||
@@ -1,9 +0,0 @@
|
||||
issueNumber: MDL-81533
|
||||
notes:
|
||||
core:
|
||||
- message: >
|
||||
The function `update_display_mode` will update the eye icon (enabled/disabled) in the availability.
|
||||
The `$pluginname` is represented to the plugin need to update.
|
||||
The `$displaymode` is represented to the eye icon. Whether it enabled or disabled.
|
||||
|
||||
type: improved
|
||||
@@ -1,7 +0,0 @@
|
||||
issueNumber: MDL-81572
|
||||
notes:
|
||||
editor_tiny:
|
||||
- message: >-
|
||||
The `helplinktext` language string is no longer required by editor
|
||||
plugins, instead the `pluginname` will be used in the help dialogue
|
||||
type: changed
|
||||
@@ -1,5 +0,0 @@
|
||||
issueNumber: MDL-81597
|
||||
notes:
|
||||
output:
|
||||
- message: 'Added a new `renderer_base::get_page` getter method'
|
||||
type: improved
|
||||
@@ -1,5 +0,0 @@
|
||||
issueNumber: MDL-81597
|
||||
notes:
|
||||
theme:
|
||||
- message: New `core/context_header` mustache template has been added. This template can be overridden by themes to modify the context header
|
||||
type: improved
|
||||
@@ -1,8 +0,0 @@
|
||||
issueNumber: MDL-81610
|
||||
notes:
|
||||
core_courseformat:
|
||||
- message: >
|
||||
The constructor of `\core_courseformat\output\local\state\cm` has been updated to accept a new optional parameter, `$istrackeduser`.
|
||||
|
||||
If `istrackeduser` is pre-computed for the course module's course, it can be provided here to avoid an additional function call.
|
||||
type: improved
|
||||
@@ -1,11 +0,0 @@
|
||||
issueNumber: MDL-81675
|
||||
notes:
|
||||
core_course:
|
||||
- message: >-
|
||||
- New optional `sectionNum` parameter has been added to `activitychooser`
|
||||
AMD module initializer.
|
||||
- New option `sectionnum` parameter has been added to
|
||||
`get_course_content_items()` external function.
|
||||
- New optional `sectionnum` parameter has been added to
|
||||
`get_content_items_for_user_in_course()` function.
|
||||
type: improved
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user