@falkenslab/moodle-driver 0.4.1 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/areas/activities/build.d.ts +47 -10
- package/dist/areas/activities/build.js +157 -24
- package/dist/areas/activities/build.js.map +1 -1
- package/dist/areas/activities/conditions.d.ts +77 -0
- package/dist/areas/activities/conditions.js +216 -0
- package/dist/areas/activities/conditions.js.map +1 -0
- package/dist/areas/activities/content.d.ts +3 -0
- package/dist/areas/activities/content.js +1 -0
- package/dist/areas/activities/content.js.map +1 -1
- package/dist/areas/assignments/grader.d.ts +32 -0
- package/dist/areas/assignments/grader.js +66 -2
- package/dist/areas/assignments/grader.js.map +1 -1
- package/dist/areas/assignments/grading.d.ts +69 -6
- package/dist/areas/assignments/grading.js +173 -13
- package/dist/areas/assignments/grading.js.map +1 -1
- package/dist/areas/assignments/student.d.ts +14 -0
- package/dist/areas/assignments/student.js +43 -2
- package/dist/areas/assignments/student.js.map +1 -1
- package/dist/areas/backup/backup.d.ts +86 -0
- package/dist/areas/backup/backup.js +368 -0
- package/dist/areas/backup/backup.js.map +1 -0
- package/dist/areas/calendar/calendar.d.ts +27 -0
- package/dist/areas/calendar/calendar.js +115 -9
- package/dist/areas/calendar/calendar.js.map +1 -1
- package/dist/areas/completion/completion.d.ts +3 -0
- package/dist/areas/completion/completion.js.map +1 -1
- package/dist/areas/courses/list.d.ts +2 -0
- package/dist/areas/courses/list.js.map +1 -1
- package/dist/areas/courses/reset.d.ts +51 -0
- package/dist/areas/courses/reset.js +123 -0
- package/dist/areas/courses/reset.js.map +1 -0
- package/dist/areas/courses/state.d.ts +17 -0
- package/dist/areas/courses/state.js +37 -6
- package/dist/areas/courses/state.js.map +1 -1
- package/dist/areas/courses/structure.d.ts +14 -2
- package/dist/areas/courses/structure.js +11 -5
- package/dist/areas/courses/structure.js.map +1 -1
- package/dist/areas/forums/discussions.d.ts +8 -0
- package/dist/areas/forums/discussions.js +10 -0
- package/dist/areas/forums/discussions.js.map +1 -1
- package/dist/areas/forums/grading.d.ts +49 -0
- package/dist/areas/forums/grading.js +125 -0
- package/dist/areas/forums/grading.js.map +1 -0
- package/dist/areas/forums/posts.d.ts +37 -0
- package/dist/areas/forums/posts.js +141 -6
- package/dist/areas/forums/posts.js.map +1 -1
- package/dist/areas/grades/export.d.ts +38 -0
- package/dist/areas/grades/export.js +88 -0
- package/dist/areas/grades/export.js.map +1 -0
- package/dist/areas/grades/gradebook.d.ts +50 -0
- package/dist/areas/grades/gradebook.js +120 -0
- package/dist/areas/grades/gradebook.js.map +1 -1
- package/dist/areas/grades/singleview.d.ts +39 -0
- package/dist/areas/grades/singleview.js +186 -0
- package/dist/areas/grades/singleview.js.map +1 -0
- package/dist/areas/h5p/h5p.d.ts +2 -0
- package/dist/areas/h5p/h5p.js +12 -7
- package/dist/areas/h5p/h5p.js.map +1 -1
- package/dist/areas/lessons/lessons.d.ts +1 -0
- package/dist/areas/lessons/lessons.js.map +1 -1
- package/dist/areas/messages/messages.d.ts +32 -0
- package/dist/areas/messages/messages.js +44 -2
- package/dist/areas/messages/messages.js.map +1 -1
- package/dist/areas/participants/enrolments.d.ts +60 -0
- package/dist/areas/participants/enrolments.js +218 -0
- package/dist/areas/participants/enrolments.js.map +1 -0
- package/dist/areas/participants/groups.d.ts +75 -0
- package/dist/areas/participants/groups.js +201 -0
- package/dist/areas/participants/groups.js.map +1 -0
- package/dist/areas/participants/methods.d.ts +56 -0
- package/dist/areas/participants/methods.js +154 -0
- package/dist/areas/participants/methods.js.map +1 -0
- package/dist/areas/participants/participants.d.ts +11 -0
- package/dist/areas/participants/participants.js +24 -19
- package/dist/areas/participants/participants.js.map +1 -1
- package/dist/areas/questions/banks.d.ts +36 -1
- package/dist/areas/questions/banks.js +140 -17
- package/dist/areas/questions/banks.js.map +1 -1
- package/dist/areas/quizzes/attempts.d.ts +2 -0
- package/dist/areas/quizzes/attempts.js +9 -0
- package/dist/areas/quizzes/attempts.js.map +1 -1
- package/dist/areas/quizzes/grading.d.ts +50 -0
- package/dist/areas/quizzes/grading.js +131 -0
- package/dist/areas/quizzes/grading.js.map +1 -0
- package/dist/areas/quizzes/results.d.ts +3 -0
- package/dist/areas/quizzes/results.js +2 -1
- package/dist/areas/quizzes/results.js.map +1 -1
- package/dist/areas/quizzes/structure.d.ts +65 -9
- package/dist/areas/quizzes/structure.js +222 -23
- package/dist/areas/quizzes/structure.js.map +1 -1
- package/dist/areas/ratings/ratings.d.ts +26 -0
- package/dist/areas/ratings/ratings.js +108 -0
- package/dist/areas/ratings/ratings.js.map +1 -0
- package/dist/areas/reports/dates.d.ts +18 -0
- package/dist/areas/reports/dates.js +134 -0
- package/dist/areas/reports/dates.js.map +1 -0
- package/dist/areas/reports/reports.d.ts +143 -0
- package/dist/areas/reports/reports.js +267 -0
- package/dist/areas/reports/reports.js.map +1 -0
- package/dist/areas/scorm/scorm.d.ts +1 -0
- package/dist/areas/scorm/scorm.js.map +1 -1
- package/dist/areas/site/users.d.ts +7 -1
- package/dist/areas/site/users.js +9 -2
- package/dist/areas/site/users.js.map +1 -1
- package/dist/areas/surveys/choice.d.ts +2 -0
- package/dist/areas/surveys/choice.js +6 -3
- package/dist/areas/surveys/choice.js.map +1 -1
- package/dist/areas/surveys/surveys.d.ts +3 -0
- package/dist/areas/surveys/surveys.js +1 -0
- package/dist/areas/surveys/surveys.js.map +1 -1
- package/dist/areas/workshops/workshops.d.ts +133 -0
- package/dist/areas/workshops/workshops.js +335 -0
- package/dist/areas/workshops/workshops.js.map +1 -0
- package/dist/browser/background.d.ts +43 -0
- package/dist/browser/background.js +164 -0
- package/dist/browser/background.js.map +1 -0
- package/dist/browser/host.d.ts +30 -0
- package/dist/browser/host.js +373 -0
- package/dist/browser/host.js.map +1 -0
- package/dist/browser/manager.d.ts +9 -0
- package/dist/browser/manager.js +8 -0
- package/dist/browser/manager.js.map +1 -1
- package/dist/categories.d.ts +1 -0
- package/dist/categories.js +67 -0
- package/dist/categories.js.map +1 -1
- package/dist/cli/args.d.ts +8 -0
- package/dist/cli/args.js +49 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/background.d.ts +23 -0
- package/dist/cli/background.js +128 -0
- package/dist/cli/background.js.map +1 -0
- package/dist/cli/command.d.ts +66 -0
- package/dist/cli/command.js +328 -0
- package/dist/cli/command.js.map +1 -0
- package/dist/cli/commands.generated.js +2635 -577
- package/dist/cli/commands.generated.js.map +1 -1
- package/dist/cli/config.d.ts +2 -0
- package/dist/cli/config.js +2 -0
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/main.js +30 -10
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/run.d.ts +3 -14
- package/dist/cli/run.js +9 -185
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/shell.d.ts +9 -0
- package/dist/cli/shell.js +233 -0
- package/dist/cli/shell.js.map +1 -0
- package/dist/client.d.ts +10 -120
- package/dist/client.js +77 -8
- package/dist/client.js.map +1 -1
- package/dist/core/errors.d.ts +1 -1
- package/dist/core/errors.js.map +1 -1
- package/dist/core/files.d.ts +5 -0
- package/dist/core/files.js +7 -3
- package/dist/core/files.js.map +1 -1
- package/dist/core/forms.d.ts +33 -3
- package/dist/core/forms.js +105 -8
- package/dist/core/forms.js.map +1 -1
- package/dist/core/http.d.ts +7 -0
- package/dist/core/http.js +28 -7
- package/dist/core/http.js.map +1 -1
- package/dist/core/login.d.ts +3 -0
- package/dist/core/login.js +2 -0
- package/dist/core/login.js.map +1 -1
- package/dist/core/session.d.ts +12 -1
- package/dist/core/session.js +12 -1
- package/dist/core/session.js.map +1 -1
- package/dist/index.d.ts +35 -11
- package/dist/index.js +32 -12
- package/dist/index.js.map +1 -1
- package/dist/internals.d.ts +9 -1
- package/dist/internals.js +9 -4
- package/dist/internals.js.map +1 -1
- package/dist/moodle-methods.generated.d.ts +1127 -0
- package/dist/moodle-methods.generated.js +4 -0
- package/dist/moodle-methods.generated.js.map +1 -0
- package/dist/options.d.ts +2 -0
- package/dist/options.js.map +1 -1
- package/dist/testing/fakeMoodle.d.ts +9 -2
- package/dist/testing/fakeMoodle.js +5 -3
- package/dist/testing/fakeMoodle.js.map +1 -1
- package/dist/testing/index.d.ts +7 -0
- package/dist/testing/index.js.map +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import { MoodleServiceError, callService } from "../../core/ajax.js";
|
|
2
|
+
import { dateSelectorValues, openForm, setFields, submitForm, uploadToDraft, formValue } from "../../core/forms.js";
|
|
3
|
+
import { moodleErrorMessage, openSitePage } from "../../core/session.js";
|
|
4
|
+
import { checkLocalFiles, downloadFile } from "../../core/files.js";
|
|
5
|
+
import { MoodleError } from "../../core/errors.js";
|
|
6
|
+
import { decodeEntities } from "../../core/text.js";
|
|
7
|
+
/** Moodle refused a copy, backup, restore or import, or didn't finish it in time. */
|
|
8
|
+
export class BackupError extends MoodleError {
|
|
9
|
+
name = "BackupError";
|
|
10
|
+
constructor(message) {
|
|
11
|
+
super(message, "backup");
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/** How long the driver waits for Moodle's background tasks unless told: ten minutes. */
|
|
15
|
+
const DEFAULT_TIMEOUT_MS = 10 * 60 * 1000;
|
|
16
|
+
const POLL_MS = 3000;
|
|
17
|
+
// Moodle's backup controller statuses (backup::STATUS_*).
|
|
18
|
+
const FINISHED_ERR = 900;
|
|
19
|
+
const FINISHED_OK = 1000;
|
|
20
|
+
const pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
21
|
+
/** Asks `progress` until Moodle says the operation finished; throws `BackupError` when it failed or the time ran out. */
|
|
22
|
+
async function waitFor(what, timeoutMs, progress) {
|
|
23
|
+
const deadline = Date.now() + timeoutMs;
|
|
24
|
+
for (;;) {
|
|
25
|
+
const status = await progress();
|
|
26
|
+
if (status === FINISHED_OK)
|
|
27
|
+
return;
|
|
28
|
+
if (status === FINISHED_ERR)
|
|
29
|
+
throw new BackupError(`Moodle couldn't finish the ${what}: it ended with an error (its details are in the site's logs and on the restore page).`);
|
|
30
|
+
if (Date.now() > deadline) {
|
|
31
|
+
throw new BackupError(`Moodle hasn't finished the ${what} after ${Math.round(timeoutMs / 1000)} seconds: the site runs it in its scheduled tasks (cron), which may be slow or not running. It goes on in the background.`);
|
|
32
|
+
}
|
|
33
|
+
await pause(POLL_MS);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** Waits for an asynchronous backup or restore (`core_backup_get_async_backup_progress`). */
|
|
37
|
+
function waitForAsync(context, siteUrl, what, backupId, contextId, timeoutMs) {
|
|
38
|
+
return waitFor(what, timeoutMs, async () => {
|
|
39
|
+
const [progress] = await callService(context, siteUrl, "core_backup_get_async_backup_progress", { backupids: [backupId], contextid: contextId });
|
|
40
|
+
return progress?.status;
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/** A course found by its short name among those the person can see (`core_course_search_courses`). */
|
|
44
|
+
async function courseByShortName(context, siteUrl, shortName) {
|
|
45
|
+
const { courses } = await callService(context, siteUrl, "core_course_search_courses", {
|
|
46
|
+
criterianame: "search",
|
|
47
|
+
criteriavalue: shortName,
|
|
48
|
+
page: 0,
|
|
49
|
+
perpage: 50,
|
|
50
|
+
});
|
|
51
|
+
const found = courses.find((course) => decodeEntities(course.shortname) === shortName);
|
|
52
|
+
return found ? toRef(siteUrl, found) : undefined;
|
|
53
|
+
}
|
|
54
|
+
function toRef(siteUrl, raw) {
|
|
55
|
+
return {
|
|
56
|
+
id: raw.id,
|
|
57
|
+
fullName: decodeEntities(raw.fullname),
|
|
58
|
+
shortName: decodeEntities(raw.shortname),
|
|
59
|
+
...(raw.categoryid ? { categoryId: raw.categoryid } : {}),
|
|
60
|
+
url: new URL(`course/view.php?id=${raw.id}`, siteUrl).href,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/** Opens a wizard's page; a page without its form becomes `BackupError` with `refusal` and Moodle's own message. */
|
|
64
|
+
async function openWizard(context, siteUrl, path, formMarker, refusal) {
|
|
65
|
+
const page = await openSitePage(context, siteUrl, path);
|
|
66
|
+
try {
|
|
67
|
+
if ((await page.locator(`form.mform [name="${formMarker}"]`).count()) === 0) {
|
|
68
|
+
const moodle = await moodleErrorMessage(page);
|
|
69
|
+
throw new BackupError(`${refusal}${moodle ? `: ${moodle}` : "."}`);
|
|
70
|
+
}
|
|
71
|
+
await page.close();
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
await page.close().catch(() => undefined);
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
// Opened again as a form: its editors and dependencies ready.
|
|
78
|
+
return openForm(context, siteUrl, path);
|
|
79
|
+
}
|
|
80
|
+
/** The course context id a wizard's page works in (`M.cfg.contextid`). */
|
|
81
|
+
const pageContextId = (page) => page.evaluate(() => Number(globalThis.M?.cfg?.contextid ?? 0));
|
|
82
|
+
/** The backup id of an asynchronous operation's progress bar on a wizard's last page, when Moodle made it asynchronous. */
|
|
83
|
+
const asyncBackupId = async (page) => (await page.locator("[data-backupid]").first().getAttribute("data-backupid").catch(() => null)) ?? undefined;
|
|
84
|
+
/**
|
|
85
|
+
* Copies a course into a new one, as Moodle's "Copy course" does: its content (and its people's
|
|
86
|
+
* data and enrolments, `withUsers`), under a new full and short name, in a category (the course's
|
|
87
|
+
* unless given), with new dates. Goes through Moodle's copy service, then waits for the copy, which
|
|
88
|
+
* Moodle makes in its scheduled tasks, up to `timeoutMs` (ten minutes). Needs the rights to back
|
|
89
|
+
* up, restore and create courses (`moodle/backup:backupcourse`, `moodle/restore:restorecourse`,
|
|
90
|
+
* `moodle/course:create`); without them, `BackupError` says so.
|
|
91
|
+
*/
|
|
92
|
+
export async function copyCourse(context, siteUrl, courseId, { fullName, shortName, categoryId, start, end, withUsers = false, visible, timeoutMs = DEFAULT_TIMEOUT_MS, }) {
|
|
93
|
+
if (!fullName.trim() || !shortName.trim())
|
|
94
|
+
throw new MoodleError("A copy needs a full name and a short name.", "invalid-input");
|
|
95
|
+
const page = await openWizard(context, siteUrl, `backup/copy.php?id=${courseId}`, "_qf__core_backup_output_copy_form", `You can't copy course ${courseId}: Moodle asks for the rights to back up, restore and create courses (moodle/backup:backupcourse, moodle/restore:restorecourse, moodle/course:create)`);
|
|
96
|
+
let formData;
|
|
97
|
+
try {
|
|
98
|
+
const values = { fullname: fullName, shortname: shortName };
|
|
99
|
+
if (categoryId !== undefined)
|
|
100
|
+
values.category = String(categoryId);
|
|
101
|
+
if (visible !== undefined)
|
|
102
|
+
values.visible = visible ? "1" : "0";
|
|
103
|
+
if (start)
|
|
104
|
+
Object.assign(values, await dateSelectorValues(page, "startdate", start));
|
|
105
|
+
if (end)
|
|
106
|
+
Object.assign(values, { "enddate[enabled]": true }, await dateSelectorValues(page, "enddate", end));
|
|
107
|
+
values.userdata = withUsers ? "1" : "0";
|
|
108
|
+
await setFields(page, values);
|
|
109
|
+
// With users, everyone's enrolments are kept (each role's checkbox).
|
|
110
|
+
if (withUsers) {
|
|
111
|
+
const roles = await page.evaluate(() => Array.from(globalThis.document.querySelectorAll('form.mform input[type="checkbox"][name^="role_"]')).map((box) => box.name));
|
|
112
|
+
if (roles.length > 0)
|
|
113
|
+
await setFields(page, Object.fromEntries(roles.map((name) => [name, true])));
|
|
114
|
+
}
|
|
115
|
+
// The form as Moodle's own page sends it to the service: urlencoded, without its buttons.
|
|
116
|
+
formData = await page.evaluate(() => {
|
|
117
|
+
const g = globalThis;
|
|
118
|
+
const entries = Array.from(new g.FormData(g.document.querySelector("form.mform"))).filter((entry) => typeof entry[1] === "string");
|
|
119
|
+
return new g.URLSearchParams(entries).toString();
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
finally {
|
|
123
|
+
await page.close().catch(() => undefined);
|
|
124
|
+
}
|
|
125
|
+
let ids;
|
|
126
|
+
try {
|
|
127
|
+
ids = JSON.parse(await callService(context, siteUrl, "core_backup_submit_copy_form", { jsonformdata: JSON.stringify(formData) }));
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
if (error instanceof MoodleServiceError && error.errorcode === "copyformfail") {
|
|
131
|
+
throw new BackupError(`Moodle refused the copy's settings: the short name "${shortName}" may be taken already, or the end date is before the start.`);
|
|
132
|
+
}
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
135
|
+
await waitFor("copy", timeoutMs, async () => {
|
|
136
|
+
const [progress] = await callService(context, siteUrl, "core_backup_get_copy_progress", { copies: [{ backupid: ids.backupid, restoreid: ids.restoreid, operation: "backup" }] });
|
|
137
|
+
return progress?.status;
|
|
138
|
+
});
|
|
139
|
+
const course = await courseByShortName(context, siteUrl, shortName);
|
|
140
|
+
if (!course)
|
|
141
|
+
throw new BackupError(`Moodle finished the copy, but no course with short name "${shortName}" is visible to you.`);
|
|
142
|
+
return { changed: `Course ${courseId} copied into course ${course.id} "${course.fullName}".`, course };
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Backs a course up to a `.mbz` file, through Moodle's backup wizard (initial settings, schema,
|
|
146
|
+
* confirmation), and downloads it into `destDir`. Without users' data unless `withUsers`, which
|
|
147
|
+
* needs `moodle/backup:userinfo` (`anonymize` makes their names anonymous). An asynchronous backup
|
|
148
|
+
* is waited for up to `timeoutMs` (ten minutes). Moodle keeps the file in the course's or the
|
|
149
|
+
* person's backup area too.
|
|
150
|
+
*/
|
|
151
|
+
export async function backupCourse(context, siteUrl, courseId, destDir, { withUsers = false, anonymize, timeoutMs = DEFAULT_TIMEOUT_MS } = {}) {
|
|
152
|
+
const page = await openWizard(context, siteUrl, `backup/backup.php?id=${courseId}`, "_qf__backup_initial_form", `Moodle offers no backup of course ${courseId}: you aren't allowed (moodle/backup:backupcourse), or a backup of it is already in progress`);
|
|
153
|
+
let fileName;
|
|
154
|
+
let contextId;
|
|
155
|
+
let backupId;
|
|
156
|
+
try {
|
|
157
|
+
const usersField = await page.locator('form.mform input[name="setting_root_users"]:not([type="hidden"])').count();
|
|
158
|
+
if (withUsers && usersField === 0)
|
|
159
|
+
throw new BackupError(`You can't back up course ${courseId} with its users' data: Moodle asks for moodle/backup:userinfo.`);
|
|
160
|
+
const settings = {};
|
|
161
|
+
if (usersField > 0)
|
|
162
|
+
settings.setting_root_users = withUsers;
|
|
163
|
+
if (withUsers && anonymize !== undefined)
|
|
164
|
+
settings.setting_root_anonymize = anonymize;
|
|
165
|
+
if (Object.keys(settings).length > 0)
|
|
166
|
+
await setFields(page, settings);
|
|
167
|
+
contextId = await pageContextId(page);
|
|
168
|
+
await submitForm(page, "submitbutton"); // To the schema settings.
|
|
169
|
+
await submitForm(page, "submitbutton"); // To the confirmation.
|
|
170
|
+
fileName = (await formValue(page, "setting_root_filename")) ?? "";
|
|
171
|
+
if (!fileName)
|
|
172
|
+
throw new BackupError(`Moodle's backup wizard for course ${courseId} didn't reach its confirmation stage.`);
|
|
173
|
+
await submitForm(page, "submitbutton"); // Perform the backup.
|
|
174
|
+
backupId = await asyncBackupId(page);
|
|
175
|
+
}
|
|
176
|
+
finally {
|
|
177
|
+
await page.close().catch(() => undefined);
|
|
178
|
+
}
|
|
179
|
+
if (backupId)
|
|
180
|
+
await waitForAsync(context, siteUrl, "backup", backupId, contextId, timeoutMs);
|
|
181
|
+
// The file's link from the restore page's backup areas (the course's, the person's).
|
|
182
|
+
const files = await openSitePage(context, siteUrl, `backup/restorefile.php?contextid=${contextId}`);
|
|
183
|
+
let link;
|
|
184
|
+
try {
|
|
185
|
+
const hrefs = await files.locator('a[href*="/pluginfile.php/"]').evaluateAll((links) => links.map((link) => link.getAttribute("href") ?? ""));
|
|
186
|
+
link = hrefs.find((href) => decodeURIComponent(new URL(href).pathname).endsWith(`/${fileName}`));
|
|
187
|
+
}
|
|
188
|
+
finally {
|
|
189
|
+
await files.close().catch(() => undefined);
|
|
190
|
+
}
|
|
191
|
+
if (!link)
|
|
192
|
+
throw new BackupError(`Moodle made the backup "${fileName}", but it isn't in the backup areas you can see.`);
|
|
193
|
+
const file = await downloadFile(context, siteUrl, link, destDir);
|
|
194
|
+
return { changed: `Course ${courseId} backed up to ${file.name}.`, file };
|
|
195
|
+
}
|
|
196
|
+
// Moodle's restore targets (backup::TARGET_*), and the wizard's stages (restore_ui::STAGE_*).
|
|
197
|
+
const TARGET_CURRENT_DELETING = 0;
|
|
198
|
+
const TARGET_CURRENT_ADDING = 1;
|
|
199
|
+
const TARGET_NEW_COURSE = 2;
|
|
200
|
+
const STAGE_SETTINGS = 4;
|
|
201
|
+
/** A page's context id, from the `M.cfg` of a page of the site (a course's, a category's). */
|
|
202
|
+
async function contextIdOf(context, siteUrl, path, missing) {
|
|
203
|
+
const page = await openSitePage(context, siteUrl, path);
|
|
204
|
+
try {
|
|
205
|
+
const level = await page.evaluate(() => globalThis.M?.cfg?.contextid ?? 0);
|
|
206
|
+
const found = await page.locator("body#page-course-view-topics, body[id^='page-course-view'], body#page-course-index-category").count();
|
|
207
|
+
if (!level || found === 0)
|
|
208
|
+
throw new BackupError(missing);
|
|
209
|
+
return level;
|
|
210
|
+
}
|
|
211
|
+
finally {
|
|
212
|
+
await page.close().catch(() => undefined);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/** Submits a wizard stage with its "next" button. */
|
|
216
|
+
async function nextStage(page) {
|
|
217
|
+
await submitForm(page, "submitbutton");
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Restores a backup file (`.mbz`) through Moodle's restore wizard: into a new course in a category
|
|
221
|
+
* (`into: "new"`, with `categoryId`, which needs `moodle/course:create` there), or into an existing
|
|
222
|
+
* course (`into: <course id>`), its content added to the course's (`mode: "merge"`, the default) or
|
|
223
|
+
* replacing it (`"replace"`: the course's content is deleted first). A new course takes the backup's
|
|
224
|
+
* names unless `fullName` and `shortName` are given. Users' data is restored when `withUsers` and the
|
|
225
|
+
* backup has it. An asynchronous restore is waited for up to `timeoutMs` (ten minutes). Needs
|
|
226
|
+
* `moodle/restore:restorecourse` where it restores.
|
|
227
|
+
*/
|
|
228
|
+
export async function restoreCourse(context, siteUrl, file, { into, categoryId, mode = "merge", fullName, shortName, withUsers = false, timeoutMs = DEFAULT_TIMEOUT_MS, }) {
|
|
229
|
+
await checkLocalFiles([file]);
|
|
230
|
+
if (into === "new" && categoryId === undefined)
|
|
231
|
+
throw new MoodleError('A restore into a new course needs the category to create it in (categoryId).', "invalid-input");
|
|
232
|
+
if (into !== "new" && mode !== "merge" && mode !== "replace")
|
|
233
|
+
throw new MoodleError(`Unknown restore mode "${String(mode)}": use "merge" or "replace".`, "invalid-input");
|
|
234
|
+
// The wizard works in the context it restores into: the category's for a new course, the course's otherwise.
|
|
235
|
+
const contextId = into === "new"
|
|
236
|
+
? await contextIdOf(context, siteUrl, `course/index.php?categoryid=${categoryId}`, `There's no course category ${categoryId}, or you can't see it.`)
|
|
237
|
+
: await contextIdOf(context, siteUrl, `course/view.php?id=${into}`, `There's no course ${into}, or you can't open it.`);
|
|
238
|
+
const page = await openWizard(context, siteUrl, `backup/restorefile.php?contextid=${contextId}`, "_qf__course_restore_form", into === "new"
|
|
239
|
+
? `You can't restore into a new course in category ${categoryId}: Moodle asks for moodle/restore:restorecourse and moodle/course:create there`
|
|
240
|
+
: `You can't restore into course ${into}: Moodle asks for moodle/restore:restorecourse in it`);
|
|
241
|
+
let restoreId;
|
|
242
|
+
let newShortName;
|
|
243
|
+
try {
|
|
244
|
+
const draft = await formValue(page, "backupfile");
|
|
245
|
+
if (!draft)
|
|
246
|
+
throw new BackupError("Moodle's restore page has no file field to upload the backup to.");
|
|
247
|
+
await uploadToDraft(page, draft, [file]);
|
|
248
|
+
await submitForm(page, "submitbutton"); // To the confirmation: Moodle unpacks the file.
|
|
249
|
+
const filepath = await page.locator('input[name="filepath"]').first().getAttribute("value").catch(() => null);
|
|
250
|
+
if (!filepath) {
|
|
251
|
+
const moodle = await moodleErrorMessage(page);
|
|
252
|
+
throw new BackupError(`Moodle didn't accept ${file} as a backup${moodle ? `: ${moodle}` : " (it isn't a Moodle backup, or it's damaged)."}`);
|
|
253
|
+
}
|
|
254
|
+
// The destination, as its course selector would send it: Moodle shows the new-course choice only to who may create courses.
|
|
255
|
+
const sesskey = await page.evaluate(() => globalThis.M.cfg.sesskey);
|
|
256
|
+
const target = into === "new" ? TARGET_NEW_COURSE : mode === "replace" ? TARGET_CURRENT_DELETING : TARGET_CURRENT_ADDING;
|
|
257
|
+
const query = new URLSearchParams({ contextid: String(contextId), stage: String(STAGE_SETTINGS), filepath, target: String(target), targetid: String(into === "new" ? categoryId : into), sesskey });
|
|
258
|
+
await page.goto(new URL(`backup/restore.php?${query}`, siteUrl).href, { waitUntil: "domcontentloaded" });
|
|
259
|
+
if ((await page.locator('form.mform [name="_qf__restore_settings_form"]').count()) === 0) {
|
|
260
|
+
const moodle = await moodleErrorMessage(page);
|
|
261
|
+
throw new BackupError(`Moodle didn't take the restore's destination${moodle ? `: ${moodle}` : "."}`);
|
|
262
|
+
}
|
|
263
|
+
const usersField = await page.locator('form.mform input[name="setting_root_users"]:not([type="hidden"])').count();
|
|
264
|
+
if (withUsers && usersField === 0)
|
|
265
|
+
throw new BackupError("This restore can't include users' data: the backup has none, or Moodle asks for moodle/restore:userinfo.");
|
|
266
|
+
if (usersField > 0)
|
|
267
|
+
await setFields(page, { setting_root_users: withUsers });
|
|
268
|
+
await nextStage(page); // To the schema: the course's names, and what to restore.
|
|
269
|
+
// Each name is a "default or custom" field: its [customize] checkbox, then its [value].
|
|
270
|
+
for (const [field, value] of [
|
|
271
|
+
["setting_course_course_fullname", fullName],
|
|
272
|
+
["setting_course_course_shortname", shortName],
|
|
273
|
+
]) {
|
|
274
|
+
if (value === undefined)
|
|
275
|
+
continue;
|
|
276
|
+
const custom = await page.locator(`form.mform input[type="checkbox"][name="${field}[customize]"]`).count();
|
|
277
|
+
await setFields(page, { ...(custom > 0 ? { [`${field}[customize]`]: true } : {}), [`${field}[value]`]: value });
|
|
278
|
+
}
|
|
279
|
+
newShortName = (await formValue(page, "setting_course_course_shortname[value]")) ?? shortName;
|
|
280
|
+
await nextStage(page); // To the review.
|
|
281
|
+
await nextStage(page); // Perform the restore.
|
|
282
|
+
// Moodle may stop at a precheck (warnings to confirm) before going on.
|
|
283
|
+
if ((await page.locator('form [name="substage"]').count()) > 0)
|
|
284
|
+
await nextStage(page);
|
|
285
|
+
restoreId = await asyncBackupId(page);
|
|
286
|
+
if (!restoreId && (await page.locator(".backup_stage_current").count()) === 0) {
|
|
287
|
+
const moodle = await moodleErrorMessage(page);
|
|
288
|
+
throw new BackupError(`Moodle didn't perform the restore${moodle ? `: ${moodle}` : "."}`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
finally {
|
|
292
|
+
await page.close().catch(() => undefined);
|
|
293
|
+
}
|
|
294
|
+
if (restoreId)
|
|
295
|
+
await waitForAsync(context, siteUrl, "restore", restoreId, contextId, timeoutMs);
|
|
296
|
+
if (into !== "new") {
|
|
297
|
+
const course = (await courseById(context, siteUrl, into)) ?? { id: into, fullName: "", shortName: "", url: new URL(`course/view.php?id=${into}`, siteUrl).href };
|
|
298
|
+
return { changed: `Backup ${file} restored into course ${into} (${mode === "replace" ? "its content replaced" : "merged with its content"}).`, course };
|
|
299
|
+
}
|
|
300
|
+
const course = newShortName ? await courseByShortName(context, siteUrl, newShortName) : undefined;
|
|
301
|
+
if (!course)
|
|
302
|
+
throw new BackupError(`Moodle finished the restore, but the new course${newShortName ? ` "${newShortName}"` : ""} isn't visible to you.`);
|
|
303
|
+
return { changed: `Backup ${file} restored into new course ${course.id} "${course.fullName}".`, course };
|
|
304
|
+
}
|
|
305
|
+
/** A course by id (`core_course_get_courses`), when the person may see it. */
|
|
306
|
+
async function courseById(context, siteUrl, courseId) {
|
|
307
|
+
try {
|
|
308
|
+
const [course] = await callService(context, siteUrl, "core_course_get_courses", { options: { ids: [courseId] } });
|
|
309
|
+
return course ? toRef(siteUrl, course) : undefined;
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
if (error instanceof MoodleServiceError)
|
|
313
|
+
return undefined;
|
|
314
|
+
throw error;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Imports activities from another course into a course, as Moodle's "Import" does: every activity
|
|
319
|
+
* of `fromCourseId`, or only `activityIds`, added to `toCourseId`'s content (with their sections),
|
|
320
|
+
* without users' data. Needs `moodle/restore:restoretargetimport` in the course imported into and
|
|
321
|
+
* `moodle/backup:backuptargetimport` in the other. Returns the activities the course has afterwards
|
|
322
|
+
* that it didn't have before, by id.
|
|
323
|
+
*/
|
|
324
|
+
export async function importActivities(context, siteUrl, fromCourseId, toCourseId, { activityIds } = {}) {
|
|
325
|
+
if (fromCourseId === toCourseId)
|
|
326
|
+
throw new MoodleError("A course can't import from itself.", "invalid-input");
|
|
327
|
+
const page = await openSitePage(context, siteUrl, `backup/import.php?id=${toCourseId}&importid=${fromCourseId}`);
|
|
328
|
+
let before = [];
|
|
329
|
+
try {
|
|
330
|
+
const stage = async () => (await formValue(page, "stage").catch(() => undefined)) ?? "";
|
|
331
|
+
if ((await page.locator('form.mform [name="backup"]').count()) === 0) {
|
|
332
|
+
const moodle = await moodleErrorMessage(page);
|
|
333
|
+
throw new BackupError(`You can't import from course ${fromCourseId} into course ${toCourseId}: Moodle asks for moodle/restore:restoretargetimport in the one and moodle/backup:backuptargetimport in the other${moodle ? `: ${moodle}` : "."}`);
|
|
334
|
+
}
|
|
335
|
+
before = await courseActivityIds(context, siteUrl, toCourseId);
|
|
336
|
+
await page.waitForLoadState("load");
|
|
337
|
+
// The initial settings, when Moodle shows them, then the schema (what to import).
|
|
338
|
+
if (Number(await stage()) === 1)
|
|
339
|
+
await nextStage(page);
|
|
340
|
+
if (activityIds) {
|
|
341
|
+
const boxes = await page.evaluate(() => Array.from(globalThis.document.querySelectorAll('form.mform input[type="checkbox"][name^="setting_activity_"][name$="_included"]')).map((box) => box.name));
|
|
342
|
+
// setting_activity_<module>_<course module id>_included.
|
|
343
|
+
const idOf = (name) => Number(/_(\d+)_included$/.exec(name)?.[1]);
|
|
344
|
+
const missing = activityIds.filter((id) => !boxes.some((name) => idOf(name) === id));
|
|
345
|
+
if (missing.length > 0)
|
|
346
|
+
throw new BackupError(`Course ${fromCourseId} has no activity ${missing.join(", ")} to import: take the ids from its structure.`);
|
|
347
|
+
await setFields(page, Object.fromEntries(boxes.map((name) => [name, activityIds.includes(idOf(name))])));
|
|
348
|
+
}
|
|
349
|
+
await nextStage(page); // To the confirmation.
|
|
350
|
+
await nextStage(page); // Perform the import.
|
|
351
|
+
if ((await page.locator(".backup_stage_current").count()) === 0) {
|
|
352
|
+
const moodle = await moodleErrorMessage(page);
|
|
353
|
+
throw new BackupError(`Moodle didn't perform the import${moodle ? `: ${moodle}` : "."}`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
finally {
|
|
357
|
+
await page.close().catch(() => undefined);
|
|
358
|
+
}
|
|
359
|
+
const after = await courseActivityIds(context, siteUrl, toCourseId);
|
|
360
|
+
const added = after.filter((id) => !before.includes(id));
|
|
361
|
+
return { changed: `Imported ${added.length} activit${added.length === 1 ? "y" : "ies"} from course ${fromCourseId} into course ${toCourseId}.`, activityIds: added };
|
|
362
|
+
}
|
|
363
|
+
/** The course module ids of a course's activities (`core_courseformat_get_state`). */
|
|
364
|
+
async function courseActivityIds(context, siteUrl, courseId) {
|
|
365
|
+
const state = JSON.parse(await callService(context, siteUrl, "core_courseformat_get_state", { courseid: courseId }));
|
|
366
|
+
return (state.cm ?? []).map((cm) => Number(cm.id));
|
|
367
|
+
}
|
|
368
|
+
//# sourceMappingURL=backup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backup.js","sourceRoot":"","sources":["../../../src/areas/backup/backup.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,11 +5,13 @@ export declare class CalendarError extends MoodleError {
|
|
|
5
5
|
name: string;
|
|
6
6
|
constructor(message: string);
|
|
7
7
|
}
|
|
8
|
+
/** An event of the person's calendar, as `upcomingEvents` lists it. */
|
|
8
9
|
export interface CalendarEvent {
|
|
9
10
|
id: number;
|
|
10
11
|
name: string;
|
|
11
12
|
/** The event's kind: "course", "group", "user", or an activity's ("due", "open"…). */
|
|
12
13
|
type: string;
|
|
14
|
+
/** When it starts (ISO 8601). */
|
|
13
15
|
start: string;
|
|
14
16
|
durationMinutes?: number;
|
|
15
17
|
courseId?: number;
|
|
@@ -37,6 +39,31 @@ export declare function createEvent(context: BrowserContext, siteUrl: string, {
|
|
|
37
39
|
changed: string;
|
|
38
40
|
event: CalendarEvent;
|
|
39
41
|
}>;
|
|
42
|
+
/** One calendar event by its id, as `upcomingEvents` gives them. */
|
|
43
|
+
export declare function calendarEvent(context: BrowserContext, siteUrl: string, eventId: number): Promise<CalendarEvent>;
|
|
44
|
+
/** What to change in a calendar event: only what's given changes. */
|
|
45
|
+
export interface EventChanges {
|
|
46
|
+
name?: string;
|
|
47
|
+
/** HTML, sent as written. */
|
|
48
|
+
description?: string;
|
|
49
|
+
start?: Date;
|
|
50
|
+
/** The new length in minutes; 0 leaves the event without one. */
|
|
51
|
+
durationMinutes?: number;
|
|
52
|
+
/** The new end, instead of a length in minutes. */
|
|
53
|
+
until?: Date;
|
|
54
|
+
location?: string;
|
|
55
|
+
/** For an event of a repeated series: change every event of the series (Moodle's default), or only this one. */
|
|
56
|
+
wholeSeries?: boolean;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Changes a calendar event through the calendar's own form, opened on it: only what's given
|
|
60
|
+
* changes. A new start keeps the event's length unless a new one is given. Returns the event as the
|
|
61
|
+
* calendar has it afterwards.
|
|
62
|
+
*/
|
|
63
|
+
export declare function updateEvent(context: BrowserContext, siteUrl: string, eventId: number, changes: EventChanges): Promise<{
|
|
64
|
+
changed: string;
|
|
65
|
+
event: CalendarEvent;
|
|
66
|
+
}>;
|
|
40
67
|
/** Deletes a calendar event the person can manage. */
|
|
41
68
|
export declare function deleteEvent(context: BrowserContext, siteUrl: string, eventId: number): Promise<{
|
|
42
69
|
changed: string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { MoodleServiceError, callService } from "../../core/ajax.js";
|
|
1
|
+
import { MoodleServiceError, callService, readPageConfig } from "../../core/ajax.js";
|
|
2
2
|
import { isoDate } from "../courses/list.js";
|
|
3
|
-
import { setFields } from "../../core/forms.js";
|
|
3
|
+
import { dateSelectorValues, setFields } from "../../core/forms.js";
|
|
4
4
|
import { decodeEntities } from "../../core/text.js";
|
|
5
5
|
import { MoodleError } from "../../core/errors.js";
|
|
6
6
|
/** Moodle refused the event, or it isn't there. */
|
|
@@ -55,19 +55,125 @@ export async function createEvent(context, siteUrl, { courseId, name, start, dur
|
|
|
55
55
|
if (description !== undefined)
|
|
56
56
|
values["description[text]"] = description;
|
|
57
57
|
await setFields(page, values, "form");
|
|
58
|
-
formdata = await page
|
|
59
|
-
const g = globalThis;
|
|
60
|
-
return new g.URLSearchParams(new g.FormData(g.document.querySelector("form"))).toString();
|
|
61
|
-
});
|
|
58
|
+
formdata = await formData(page);
|
|
62
59
|
}
|
|
63
60
|
finally {
|
|
64
61
|
await page.close().catch(() => undefined);
|
|
65
62
|
}
|
|
63
|
+
const event = await submitEventForm(context, siteUrl, formdata, `Moodle refused the event "${name}": check its name, start and duration.`);
|
|
64
|
+
return { changed: `Event ${event.id} "${event.name}" added to course ${courseId}'s calendar.`, event };
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The event form's fields, encoded as the calendar's page sends them to the service, without those
|
|
68
|
+
* of the other event types. The form hides a group event's course and group, a category event's
|
|
69
|
+
* category… unless the event is of that type; outside Moodle's page its group select still holds its
|
|
70
|
+
* first group, and Moodle saves it into a course event, which only that group then sees (confirmed
|
|
71
|
+
* empirically, 5.2).
|
|
72
|
+
*/
|
|
73
|
+
function formData(page) {
|
|
74
|
+
return page.evaluate(() => {
|
|
75
|
+
const g = globalThis;
|
|
76
|
+
const data = new g.URLSearchParams(new g.FormData(g.document.querySelector("form")));
|
|
77
|
+
const type = data.get("eventtype");
|
|
78
|
+
if (type !== "category")
|
|
79
|
+
data.delete("categoryid");
|
|
80
|
+
if (type !== "course")
|
|
81
|
+
data.delete("courseid");
|
|
82
|
+
if (type !== "group") {
|
|
83
|
+
data.delete("groupcourseid");
|
|
84
|
+
data.delete("groupid");
|
|
85
|
+
}
|
|
86
|
+
return data.toString();
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
/** Saves the event form through its service; Moodle answers the event, or that its validation failed. */
|
|
90
|
+
async function submitEventForm(context, siteUrl, formdata, refused) {
|
|
66
91
|
const answer = await callService(context, siteUrl, "core_calendar_submit_create_update_form", { formdata });
|
|
67
92
|
if (!answer.event || answer.validationerror)
|
|
68
|
-
throw new CalendarError(
|
|
69
|
-
|
|
70
|
-
|
|
93
|
+
throw new CalendarError(refused);
|
|
94
|
+
return toEvent(answer.event);
|
|
95
|
+
}
|
|
96
|
+
/** One calendar event by its id, as `upcomingEvents` gives them. */
|
|
97
|
+
export async function calendarEvent(context, siteUrl, eventId) {
|
|
98
|
+
try {
|
|
99
|
+
const { event } = await callService(context, siteUrl, "core_calendar_get_calendar_event_by_id", { eventid: eventId });
|
|
100
|
+
return toEvent(event);
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
// A missing event answers "nopermissions" rather than a missing record (confirmed empirically, 5.2).
|
|
104
|
+
if (error instanceof MoodleServiceError && (/^invalidrecord/.test(error.errorcode) || error.errorcode === "nopermissions")) {
|
|
105
|
+
throw new CalendarError(`There's no calendar event ${eventId} you can see: it was deleted, never existed, or isn't yours to see.`);
|
|
106
|
+
}
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Changes a calendar event through the calendar's own form, opened on it: only what's given
|
|
112
|
+
* changes. A new start keeps the event's length unless a new one is given. Returns the event as the
|
|
113
|
+
* calendar has it afterwards.
|
|
114
|
+
*/
|
|
115
|
+
export async function updateEvent(context, siteUrl, eventId, changes) {
|
|
116
|
+
const { name, description, start, durationMinutes, until, location, wholeSeries } = changes;
|
|
117
|
+
if (durationMinutes !== undefined && until !== undefined)
|
|
118
|
+
throw new MoodleError("Give the event's new length either in minutes or as an end (until), not both.", "invalid-input");
|
|
119
|
+
if (durationMinutes !== undefined && !(Number.isInteger(durationMinutes) && durationMinutes >= 0))
|
|
120
|
+
throw new MoodleError("durationMinutes is a whole number of minutes, 0 for none.", "invalid-input");
|
|
121
|
+
if (name !== undefined && name.trim() === "")
|
|
122
|
+
throw new MoodleError("The event's name is empty.", "invalid-input");
|
|
123
|
+
const current = await calendarEvent(context, siteUrl, eventId);
|
|
124
|
+
if ([name, description, start, durationMinutes, until, location].every((value) => value === undefined))
|
|
125
|
+
return { changed: "Nothing to change.", event: current };
|
|
126
|
+
let fragment;
|
|
127
|
+
try {
|
|
128
|
+
fragment = await callService(context, siteUrl, "core_get_fragment", {
|
|
129
|
+
component: "core_calendar",
|
|
130
|
+
callback: "event_form",
|
|
131
|
+
contextid: 1,
|
|
132
|
+
args: [{ name: "eventid", value: String(eventId) }],
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
if (error instanceof MoodleServiceError && error.errorcode === "nopermissiontoupdatecalendar") {
|
|
137
|
+
throw new CalendarError(`You can't change calendar event ${eventId}${current.activity ? `: it's ${current.activity.type}'s, and changes with the activity's settings` : ""}.`);
|
|
138
|
+
}
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
// The form's dates are in the person's time zone; the fragment comes without the page that says it.
|
|
142
|
+
const timeZone = start !== undefined || until !== undefined ? (await readPageConfig(context, siteUrl)).timeZone : undefined;
|
|
143
|
+
const page = await context.newPage();
|
|
144
|
+
let formdata;
|
|
145
|
+
try {
|
|
146
|
+
await page.setContent(fragment.html);
|
|
147
|
+
const values = {};
|
|
148
|
+
if (name !== undefined)
|
|
149
|
+
values.name = name;
|
|
150
|
+
if (description !== undefined)
|
|
151
|
+
values["description[text]"] = description;
|
|
152
|
+
if (location !== undefined)
|
|
153
|
+
values.location = location;
|
|
154
|
+
if (start !== undefined)
|
|
155
|
+
Object.assign(values, await dateSelectorValues(page, "timestart", start, timeZone));
|
|
156
|
+
if (durationMinutes !== undefined)
|
|
157
|
+
Object.assign(values, durationMinutes === 0 ? { duration: 0 } : { duration: 2, timedurationminutes: durationMinutes });
|
|
158
|
+
else if (until !== undefined)
|
|
159
|
+
Object.assign(values, { duration: 1 }, await dateSelectorValues(page, "timedurationuntil", until, timeZone));
|
|
160
|
+
// The form keeps a length as its end ("until"): a new start would stretch or invert it.
|
|
161
|
+
else if (start !== undefined)
|
|
162
|
+
Object.assign(values, current.durationMinutes ? { duration: 2, timedurationminutes: current.durationMinutes } : { duration: 0 });
|
|
163
|
+
// Only a repeated event's form asks whether the change is for the whole series.
|
|
164
|
+
if (wholeSeries !== undefined && (await page.locator('form [name="repeateditall"]').count()) > 0)
|
|
165
|
+
values.repeateditall = wholeSeries ? 1 : 0;
|
|
166
|
+
await setFields(page, values, "form");
|
|
167
|
+
formdata = await formData(page);
|
|
168
|
+
}
|
|
169
|
+
finally {
|
|
170
|
+
await page.close().catch(() => undefined);
|
|
171
|
+
}
|
|
172
|
+
const event = await submitEventForm(context, siteUrl, formdata, `Moodle refused the changes to event ${eventId}: check its name, start and duration (an end before the start, a start before the course's).`);
|
|
173
|
+
const done = Object.entries(changes)
|
|
174
|
+
.filter(([key, value]) => value !== undefined && key !== "wholeSeries")
|
|
175
|
+
.map(([key]) => key);
|
|
176
|
+
return { changed: `Event ${eventId} "${event.name}" changed (${done.join(", ") || "nothing"}).`, event };
|
|
71
177
|
}
|
|
72
178
|
/** Deletes a calendar event the person can manage. */
|
|
73
179
|
export async function deleteEvent(context, siteUrl, eventId) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"calendar.js","sourceRoot":"","sources":["../../../src/areas/calendar/calendar.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"calendar.js","sourceRoot":"","sources":["../../../src/areas/calendar/calendar.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACrF,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAmB,MAAM,qBAAqB,CAAC;AACrF,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEnD,mDAAmD;AACnD,MAAM,OAAO,aAAc,SAAQ,WAAW;IACnC,IAAI,GAAG,eAAe,CAAC;IAChC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IAC7B,CAAC;CACF;AA4BD,SAAS,OAAO,CAAC,GAAgB;IAC/B,OAAO;QACL,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,IAAI,EAAE,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC;QAC9B,IAAI,EAAE,GAAG,CAAC,SAAS;QACnB,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,SAAS,CAAE;QAC9B,GAAG,CAAC,GAAG,CAAC,YAAY,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,YAAY,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACvF,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAClD,GAAG,CAAC,GAAG,CAAC,UAAU,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,UAAU,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC1G,CAAC;AACJ,CAAC;AAED,+GAA+G;AAC/G,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,OAAuB,EAAE,OAAe,EAAE,EAAE,QAAQ,KAA4B,EAAE;IACrH,uDAAuD;IACvD,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,WAAW,CAA4B,OAAO,EAAE,OAAO,EAAE,0CAA0C,EAAE,EAAE,QAAQ,EAAE,QAAQ,IAAI,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC,CAAC;IAC1K,OAAO,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AAC7B,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,OAAuB,EACvB,OAAe,EACf,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,eAAe,EAAE,WAAW,EAAmG;IAExJ,0GAA0G;IAC1G,MAAM,QAAQ,GAAG,MAAM,WAAW,CAAmB,OAAO,EAAE,OAAO,EAAE,mBAAmB,EAAE;QAC1F,SAAS,EAAE,eAAe;QAC1B,QAAQ,EAAE,YAAY;QACtB,SAAS,EAAE,CAAC;QACZ,IAAI,EAAE;YACJ,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,EAAE;YAC7C,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC,EAAE;SACzE;KACF,CAAC,CAAC;IACH,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;IACrC,IAAI,QAAgB,CAAC;IACrB,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACrC,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,CACrC,KAAK,CAAC,IAAI,CAAE,UAAqG,CAAC,QAAQ,CAAC,gBAAgB,CAAC,gCAAgC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CACnM,CAAC;QACF,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC;YAAE,MAAM,IAAI,aAAa,CAAC,kCAAkC,QAAQ,cAAc,CAAC,CAAC;QACjH,MAAM,MAAM,GAA+B,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;QACrG,IAAI,eAAe,KAAK,SAAS;YAAE,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,mBAAmB,EAAE,eAAe,EAAE,CAAC,CAAC;QAChH,IAAI,WAAW,KAAK,SAAS;YAAE,MAAM,CAAC,mBAAmB,CAAC,GAAG,WAAW,CAAC;QACzE,MAAM,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;QACtC,QAAQ,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC;YAAS,CAAC;QACT,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAC5C,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,6BAA6B,IAAI,wCAAwC,CAAC,CAAC;IAC3I,OAAO,EAAE,OAAO,EAAE,SAAS,KAAK,CAAC,EAAE,KAAK,KAAK,CAAC,IAAI,qBAAqB,QAAQ,cAAc,EAAE,KAAK,EAAE,CAAC;AACzG,CAAC;AAED;;;;;;GAMG;AACH,SAAS,QAAQ,CAAC,IAAU;IAC1B,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE;QACxB,MAAM,CAAC,GAAG,UAIT,CAAC;QACF,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACrF,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QACnC,IAAI,IAAI,KAAK,UAAU;YAAE,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;QACnD,IAAI,IAAI,KAAK,QAAQ;YAAE,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC/C,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;YACrB,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;YAC7B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QACzB,CAAC;QACD,OAAO,IAAI,CAAC,QAAQ,EAAE,CAAC;IACzB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,yGAAyG;AACzG,KAAK,UAAU,eAAe,CAAC,OAAuB,EAAE,OAAe,EAAE,QAAgB,EAAE,OAAe;IACxG,MAAM,MAAM,GAAG,MAAM,WAAW,CAAqD,OAAO,EAAE,OAAO,EAAE,yCAAyC,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;IAChK,IAAI,CAAC,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,eAAe;QAAE,MAAM,IAAI,aAAa,CAAC,OAAO,CAAC,CAAC;IAC9E,OAAO,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC/B,CAAC;AAED,oEAAoE;AACpE,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,OAAuB,EAAE,OAAe,EAAE,OAAe;IAC3F,IAAI,CAAC;QACH,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,WAAW,CAAyB,OAAO,EAAE,OAAO,EAAE,wCAAwC,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;QAC9I,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC;IACxB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,qGAAqG;QACrG,IAAI,KAAK,YAAY,kBAAkB,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,KAAK,CAAC,SAAS,KAAK,eAAe,CAAC,EAAE,CAAC;YAC3H,MAAM,IAAI,aAAa,CAAC,6BAA6B,OAAO,qEAAqE,CAAC,CAAC;QACrI,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAiBD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAuB,EAAE,OAAe,EAAE,OAAe,EAAE,OAAqB;IAChH,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC;IAC5F,IAAI,eAAe,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS;QAAE,MAAM,IAAI,WAAW,CAAC,+EAA+E,EAAE,eAAe,CAAC,CAAC;IAClL,IAAI,eAAe,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,eAAe,CAAC,IAAI,eAAe,IAAI,CAAC,CAAC;QAAE,MAAM,IAAI,WAAW,CAAC,2DAA2D,EAAE,eAAe,CAAC,CAAC;IACvM,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,MAAM,IAAI,WAAW,CAAC,4BAA4B,EAAE,eAAe,CAAC,CAAC;IACnH,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IAC/D,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC;QAAE,OAAO,EAAE,OAAO,EAAE,oBAAoB,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;IAEjK,IAAI,QAA0B,CAAC;IAC/B,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,WAAW,CAAmB,OAAO,EAAE,OAAO,EAAE,mBAAmB,EAAE;YACpF,SAAS,EAAE,eAAe;YAC1B,QAAQ,EAAE,YAAY;YACtB,SAAS,EAAE,CAAC;YACZ,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;SACpD,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,kBAAkB,IAAI,KAAK,CAAC,SAAS,KAAK,8BAA8B,EAAE,CAAC;YAC9F,MAAM,IAAI,aAAa,CAAC,mCAAmC,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,OAAO,CAAC,QAAQ,CAAC,IAAI,8CAA8C,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QACjL,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;IACD,oGAAoG;IACpG,MAAM,QAAQ,GAAG,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,cAAc,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC;IAC5H,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;IACrC,IAAI,QAAgB,CAAC;IACrB,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACrC,MAAM,MAAM,GAA+B,EAAE,CAAC;QAC9C,IAAI,IAAI,KAAK,SAAS;YAAE,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC;QAC3C,IAAI,WAAW,KAAK,SAAS;YAAE,MAAM,CAAC,mBAAmB,CAAC,GAAG,WAAW,CAAC;QACzE,IAAI,QAAQ,KAAK,SAAS;YAAE,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACvD,IAAI,KAAK,KAAK,SAAS;YAAE,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,kBAAkB,CAAC,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC;QAC7G,IAAI,eAAe,KAAK,SAAS;YAAE,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,mBAAmB,EAAE,eAAe,EAAE,CAAC,CAAC;aACrJ,IAAI,KAAK,KAAK,SAAS;YAAE,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,kBAAkB,CAAC,IAAI,EAAE,mBAAmB,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC;QAC3I,wFAAwF;aACnF,IAAI,KAAK,KAAK,SAAS;YAAE,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,mBAAmB,EAAE,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC;QAC/J,gFAAgF;QAChF,IAAI,WAAW,KAAK,SAAS,IAAI,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,6BAA6B,CAAC,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC;YAAE,MAAM,CAAC,aAAa,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7I,MAAM,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;QACtC,QAAQ,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC;YAAS,CAAC;QACT,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAC5C,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,uCAAuC,OAAO,8FAA8F,CAAC,CAAC;IAC9M,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;SACjC,MAAM,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,IAAI,GAAG,KAAK,aAAa,CAAC;SACtE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC;IACvB,OAAO,EAAE,OAAO,EAAE,SAAS,OAAO,KAAK,KAAK,CAAC,IAAI,cAAc,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,SAAS,IAAI,EAAE,KAAK,EAAE,CAAC;AAC3G,CAAC;AAED,sDAAsD;AACtD,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAuB,EAAE,OAAe,EAAE,OAAe;IACzF,IAAI,CAAC;QACH,MAAM,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,sCAAsC,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;IACjI,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,kBAAkB,IAAI,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC;YAAE,MAAM,IAAI,aAAa,CAAC,6BAA6B,OAAO,qCAAqC,CAAC,CAAC;QACtL,MAAM,KAAK,CAAC;IACd,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,SAAS,OAAO,WAAW,EAAE,CAAC;AAClD,CAAC"}
|