Merge branch 'MDL-81125-main-alt' of https://github.com/andrewnicols/moodle

This commit is contained in:
Huong Nguyen 2024-05-21 15:33:09 +07:00
commit bd9631bda5
No known key found for this signature in database
GPG Key ID: 40D88AB693A3E72A
154 changed files with 3231 additions and 137 deletions

1
.gitignore vendored
View File

@ -56,3 +56,4 @@ moodle-plugin-ci.phar
.hugo_build.lock
phpcs.xml
jsconfig.json
UPGRADING-CURRENT.md

View File

@ -40,9 +40,20 @@ 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';
@ -55,8 +66,8 @@ const fetchComponentData = () => {
}
}
// The list of components incldues the list of subsystems.
componentData.components = componentData.subsystems;
// The list of components includes the list of subsystems.
componentData.components = {...componentData.subsystems};
// Go through each of the plugintypes.
Object.entries(components.plugintypes).forEach(([pluginType, pluginTypePath]) => {
@ -87,6 +98,20 @@ 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;

View File

@ -0,0 +1,110 @@
// 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;
}

View File

@ -0,0 +1,85 @@
#!/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);
};

View File

@ -0,0 +1,318 @@
#!/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} type
* @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')
.filter((line) => line.trim().length > 0);
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 changev
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);
}
};

View File

@ -0,0 +1,230 @@
// 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";
}

View File

@ -0,0 +1,30 @@
#!/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;

183
.grunt/notes/src/note.mjs Normal file
View File

@ -0,0 +1,183 @@
// 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 } from './noteTypes.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,
});
});
});
});
return combinedNotes;
};
/**
* 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}`))
);
};

View File

@ -0,0 +1,45 @@
// 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/>.
const noteTypes = {
'improved': 'Added',
'removed': 'Removed',
'changed': 'Changed',
'deprecated': 'Deprecated',
'fixed': 'Fixed',
};
/**
* 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;

View File

@ -0,0 +1,153 @@
// 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',
});

103
.grunt/upgradenotes.mjs Executable file
View File

@ -0,0 +1,103 @@
#!/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();

0
.upgradenotes/.gitkeep Normal file
View File

View File

@ -0,0 +1,6 @@
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

View File

@ -0,0 +1,6 @@
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

View File

@ -0,0 +1,6 @@
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

View File

@ -0,0 +1,7 @@
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

View File

@ -0,0 +1,5 @@
issueNumber: MDL-74484
notes:
core:
- message: Final deprecation of print_error(). Use moodle_exception instead.
type: removed

View File

@ -0,0 +1,8 @@
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

View File

@ -0,0 +1,11 @@
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

View File

@ -0,0 +1,6 @@
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

View File

@ -0,0 +1,6 @@
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

View File

@ -0,0 +1,8 @@
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

View File

@ -0,0 +1,6 @@
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

View File

@ -0,0 +1,8 @@
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

7
UPGRADING.md Normal file
View File

@ -0,0 +1,7 @@
# Moodle Upgrade notes
This file contains important information for developers on changes to the Moodle codebase.
More detailed information on key changes can be found in the [Developer update notes](https://moodledev.io/docs/devupdate) for your version of Moodle.
The format of this change log follows the advice given at [Keep a CHANGELOG](https://keepachangelog.com).

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in the tool_behat code.
=== 4.3 ===

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in /admin/tool/brickfield/*.
=== 4.0 ===

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This file describes API changes in /admin/tool/dataprivacy/*
Information provided here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in core libraries and APIs,
information provided here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in the logstore_database code.
=== 3.4 ===

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in /admin/tool/log - plugins,
information provided here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes changes in /admin/tool/lp/* - plugins,
information provided here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes for code that uses MFA.
=== 4.4 ===

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes changes in tool_mobile code.
Information provided here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in the tool_phpunit code.
=== 3.9 ===

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in /admin/tool/* - plugins,
information provided here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in the tool_usertours code.
=== 4.4 ===

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in /admin/*.
=== 4.4 ===

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in analytics sub system,
information provided here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in /auth/cas/*,
information provided here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in /auth/db/*,
information provided here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in /auth/email/*,
information provided here is intended especially for developers.
=== 4.4 ===

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in the auth_ldap code.
=== 3.4 ===

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in /auth/manual/*,
information provided here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in auth_mnet code.
=== 3.3 ===

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in /auth/none/*,
information provided here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in /auth/shibboleth/*,
information provided here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in /auth/* - plugins,
information provided here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in /availability/*.
The information here is intended only for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in /backup/*,
information provided here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in /badges/*,
information provided here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
=== 4.4 ===
* The following previously deprecated methods have been removed and can no longer be used:
- block_calendar_upcoming::get_upcoming_content

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This file describes API changes in the lp block code.
=== 3.7 ===

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This file describes API changes in the myoverview block code.
=== 3.7 ===

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This file describes API changes in the recentlyaccessedcourses block code.
=== 3.8 ===

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This file describes API changes in the recentlyaccesseditems block code.
=== 4.4 ===

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This file describes API changes in the section_links block code.
=== 3.11 ===

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This file describes API changes in the starredcourses block code.
=== 3.8 ===

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in the block tag_youtube code.
=== 3.10.1 ===

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This file describes API changes in the timeline block code.
=== 4.0 ===

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in /blocks/* - activity modules,
information provided here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in /blog/* ,
information provided here is intended especially for developers.

5
cache/upgrade.txt vendored
View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in /cache/stores/* - cache store plugins.
Information provided here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in /calendar/* ,
information provided here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in /cohort/ information provided here is intended
especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in /comment/* ,
information provided here is intended especially for developers.

View File

@ -1,2 +1,7 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This file describes API changes in /communication/provider/*
Information provided here is intended especially for developers.

View File

@ -1,2 +1,7 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This file describes API changes in /communication/*
Information provided here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in /competency/*. The information provided
here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This file describes API changes in /completion/* - completion,
information provided here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in core libraries and APIs,
information provided here is intended especially for developers.

View File

@ -1,11 +1,12 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes for course formats
Overview of this plugin type at https://moodledev.io/docs/apis/plugintypes/format
=== 4.5 ===
* The constructor of core_courseformat\output\local\state\cm has been updated to accept a new optional parameter called is_tracked_user.
If is_tracked_user is pre-computed for this CM's course, it can be provided here to avoid an additional function call.
=== 4.4 ===
* The core_courseformat\output\local\content\section::export_for_template() is not returning hiddenfromstudents and notavailable
directly in the data array anymore. Instead, it returns the visibility data in the 'visibility' key. It means that templates

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in /course/*,
information provided here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in /customfield/field/* - customfield field types,
information provided here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in /customfield/*,
Information provided here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in /dataformat/ download system,
information provided here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in the enrol_database code.
=== 3.11 ===

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in the enrol_ldap code.
=== 3.8 ===

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in /enrol/* - plugins,
information provided here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in /files/*,
information provided here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This file describes API changes in core filter API and plugins,
information provided here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in /grade/export/* - plugins,
information provided here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in /grade/grading/form/* - Advanced grading methods
information provided here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in /grade/report/*,
information provided here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This file describes API changes in /grade/* ;
Information provided here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in /group/*,
information provided here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in core libraries and APIs,
information provided here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in the editor_atto code.
=== 4.1 ===

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in tiny_accessibilitychecker - TinyMCE Accessibility checker plugin,
information provided here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in /lib/editor/tiny/* - TinyMCE editor,
information provided here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in core_form libraries and APIs,
information provided here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in the mlbackend_php code, the
information provided here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in the mlbackend_python code, the
information provided here is intended especially for developers.

View File

@ -1,14 +1,12 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in core libraries and APIs,
information provided here is intended especially for developers.
=== 4.5 ===
* Final deprecation and removal of the function core_text::reset_caches().
* The previously deprecated function `search_generate_text_SQL` has been removed and can no longer be used.
* The following previously deprecated methods have been removed and can no longer be used:
- `renderer_base::should_display_main_logo`
* Final deprecation of print_error(). Use moodle_exception instead.
=== 4.4 ===
* New modinfo methods related to delegated sections (sections controlled by a component):

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in core_xapi libraries and APIs,
information provided here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This file describes API changes for the plugins of the type 'local'.
=== 3.1 ===

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in /media/ plugins,
information provided here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in /message/ messaging system,
information provided here is intended especially for developers.

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in the assign code.
=== 4.3 ===

View File

@ -1,3 +1,8 @@
=== 4.5 Onwards ===
This file has been replaced by UPGRADING.md. See MDL-81125 for further information.
===
This files describes API changes in the bigbluebuttonbn code.
=== 4.3 ===
* Make instance class constructor private and use the factory method to create instances.

Some files were not shown because too many files have changed in this diff Show More