Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
// This file is part of Moodle - http://moodle.org/
2
//
3
// Moodle is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, either version 3 of the License, or
6
// (at your option) any later version.
7
//
8
// Moodle is distributed in the hope that it will be useful,
9
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
// GNU General Public License for more details.
12
//
13
// You should have received a copy of the GNU General Public License
14
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
15
 
16
import ajax from 'core/ajax';
17
import {getString} from "core/str";
18
import log from 'core/log';
19
import SRLogger from "core/local/reactive/srlogger";
20
 
21
/**
22
 * Flag to determine whether the screen reader-only logger has already been set, so we only need to set it once.
23
 *
24
 * @type {boolean}
25
 */
26
let isLoggerSet = false;
27
 
28
/**
29
 * Default mutation manager
30
 *
31
 * @module     core_courseformat/local/courseeditor/mutations
32
 * @class     core_courseformat/local/courseeditor/mutations
33
 * @copyright  2021 Ferran Recio <ferran@moodle.com>
34
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
35
 */
36
export default class {
37
 
38
    // All course editor mutations for Moodle 4.0 will be located in this file.
39
 
40
    /**
41
     * Private method to call core_courseformat_update_course webservice.
42
     *
43
     * @method _callEditWebservice
44
     * @param {string} action
45
     * @param {number} courseId
46
     * @param {array} ids
47
     * @param {number} targetSectionId optional target section id (for moving actions)
48
     * @param {number} targetCmId optional target cm id (for moving actions)
49
     */
50
    async _callEditWebservice(action, courseId, ids, targetSectionId, targetCmId) {
51
        const args = {
52
            action,
53
            courseid: courseId,
54
            ids,
55
        };
56
        if (targetSectionId) {
57
            args.targetsectionid = targetSectionId;
58
        }
59
        if (targetCmId) {
60
            args.targetcmid = targetCmId;
61
        }
62
        let ajaxresult = await ajax.call([{
63
            methodname: 'core_courseformat_update_course',
64
            args,
65
        }])[0];
66
        return JSON.parse(ajaxresult);
67
    }
68
 
69
    /**
70
     * Execute a basic section state action.
71
     * @param {StateManager} stateManager the current state manager
72
     * @param {string} action the action name
73
     * @param {array} sectionIds the section ids
74
     * @param {number} targetSectionId optional target section id (for moving actions)
75
     * @param {number} targetCmId optional target cm id (for moving actions)
76
     */
77
    async _sectionBasicAction(stateManager, action, sectionIds, targetSectionId, targetCmId) {
78
        const logEntry = this._getLoggerEntry(stateManager, action, sectionIds, {
79
            targetSectionId,
80
            targetCmId,
81
            itemType: 'section',
82
        });
83
        const course = stateManager.get('course');
84
        this.sectionLock(stateManager, sectionIds, true);
85
        const updates = await this._callEditWebservice(
86
            action,
87
            course.id,
88
            sectionIds,
89
            targetSectionId,
90
            targetCmId
91
        );
92
        this.bulkReset(stateManager);
93
        stateManager.processUpdates(updates);
94
        this.sectionLock(stateManager, sectionIds, false);
95
        stateManager.addLoggerEntry(await logEntry);
96
    }
97
 
98
    /**
99
     * Execute a basic course module state action.
100
     * @param {StateManager} stateManager the current state manager
101
     * @param {string} action the action name
102
     * @param {array} cmIds the cm ids
103
     * @param {number} targetSectionId optional target section id (for moving actions)
104
     * @param {number} targetCmId optional target cm id (for moving actions)
105
     */
106
    async _cmBasicAction(stateManager, action, cmIds, targetSectionId, targetCmId) {
107
        const logEntry = this._getLoggerEntry(stateManager, action, cmIds, {
108
            targetSectionId,
109
            targetCmId,
110
            itemType: 'cm',
111
        });
112
        const course = stateManager.get('course');
113
        this.cmLock(stateManager, cmIds, true);
114
        const updates = await this._callEditWebservice(
115
            action,
116
            course.id,
117
            cmIds,
118
            targetSectionId,
119
            targetCmId
120
        );
121
        this.bulkReset(stateManager);
122
        stateManager.processUpdates(updates);
123
        this.cmLock(stateManager, cmIds, false);
124
        stateManager.addLoggerEntry(await logEntry);
125
    }
126
 
127
    /**
128
     * Get log entry for the current action.
129
     * @param {StateManager} stateManager the current state manager
130
     * @param {string} action the action name
131
     * @param {int[]|null} itemIds the element ids
132
     * @param {Object|undefined} data extra params for the log entry
133
     * @param {string|undefined} data.itemType the element type (will be taken from action if none)
134
     * @param {int|null|undefined} data.targetSectionId the target section id
135
     * @param {int|null|undefined} data.targetCmId the target cm id
136
     * @param {String|null|undefined} data.component optional component (for format plugins)
137
     * @return {Object} the log entry
138
     */
139
    async _getLoggerEntry(stateManager, action, itemIds, data = {}) {
140
        if (!isLoggerSet) {
141
            // In case the logger has not been set from init(), ensure we set the logger.
142
            stateManager.setLogger(new SRLogger());
143
            isLoggerSet = true;
144
        }
145
        const feedbackParams = {
146
            action,
147
            itemType: data.itemType ?? action.split('_')[0],
148
        };
149
        let batch = '';
150
        if (itemIds.length > 1) {
151
            feedbackParams.count = itemIds.length;
152
            batch = '_batch';
153
        } else if (itemIds.length === 1) {
154
            const itemInfo = stateManager.get(feedbackParams.itemType, itemIds[0]);
155
            feedbackParams.name = itemInfo.title ?? itemInfo.name;
156
            // Apply shortener for modules like label.
157
        }
158
        if (data.targetSectionId) {
159
            feedbackParams.targetSectionName = stateManager.get('section', data.targetSectionId).title;
160
        }
161
        if (data.targetCmId) {
162
            feedbackParams.targetCmName = stateManager.get('cm', data.targetCmId).name;
163
        }
164
 
165
        const message = await getString(
166
            `${action.toLowerCase()}_feedback${batch}`,
167
            data.component ?? 'core_courseformat',
168
            feedbackParams
169
        );
170
 
171
        return {
172
            feedbackMessage: message,
173
        };
174
    }
175
 
176
    /**
177
     * Mutation module initialize.
178
     *
179
     * The reactive instance will execute this method when addMutations or setMutation is invoked.
180
     *
181
     * @param {StateManager} stateManager the state manager
182
     */
183
    init(stateManager) {
184
        // Add a method to prepare the fields when some update is coming from the server.
185
        stateManager.addUpdateTypes({
186
            prepareFields: this._prepareFields,
187
        });
188
        // Use the screen reader-only logger (SRLogger) to handle the feedback messages from the mutations.
189
        stateManager.setLogger(new SRLogger());
190
        isLoggerSet = true;
191
    }
192
 
193
    /**
194
     * Add default values to state elements.
195
     *
196
     * This method is called every time a webservice returns a update state message.
197
     *
198
     * @param {Object} stateManager the state manager
199
     * @param {String} updateName the state element to update
200
     * @param {Object} fields the new data
201
     * @returns {Object} final fields data
202
     */
203
    _prepareFields(stateManager, updateName, fields) {
204
        // Any update should unlock the element.
205
        fields.locked = false;
206
        return fields;
207
    }
208
 
209
    /**
210
     * Hides sections.
211
     * @param {StateManager} stateManager the current state manager
212
     * @param {array} sectionIds the list of section ids
213
     */
214
    async sectionHide(stateManager, sectionIds) {
215
        await this._sectionBasicAction(stateManager, 'section_hide', sectionIds);
216
    }
217
 
218
    /**
219
     * Show sections.
220
     * @param {StateManager} stateManager the current state manager
221
     * @param {array} sectionIds the list of section ids
222
     */
223
    async sectionShow(stateManager, sectionIds) {
224
        await this._sectionBasicAction(stateManager, 'section_show', sectionIds);
225
    }
226
 
227
    /**
228
     * Show cms.
229
     * @param {StateManager} stateManager the current state manager
230
     * @param {array} cmIds the list of cm ids
231
     */
232
    async cmShow(stateManager, cmIds) {
233
        await this._cmBasicAction(stateManager, 'cm_show', cmIds);
234
    }
235
 
236
    /**
237
     * Hide cms.
238
     * @param {StateManager} stateManager the current state manager
239
     * @param {array} cmIds the list of cm ids
240
     */
241
    async cmHide(stateManager, cmIds) {
242
        await this._cmBasicAction(stateManager, 'cm_hide', cmIds);
243
    }
244
 
245
    /**
246
     * Stealth cms.
247
     * @param {StateManager} stateManager the current state manager
248
     * @param {array} cmIds the list of cm ids
249
     */
250
    async cmStealth(stateManager, cmIds) {
251
        await this._cmBasicAction(stateManager, 'cm_stealth', cmIds);
252
    }
253
 
254
    /**
255
     * Duplicate course modules
256
     * @param {StateManager} stateManager the current state manager
257
     * @param {array} cmIds the list of course modules ids
258
     * @param {number|undefined} targetSectionId the optional target sectionId
259
     * @param {number|undefined} targetCmId the target course module id
260
     */
261
    async cmDuplicate(stateManager, cmIds, targetSectionId, targetCmId) {
262
        const logEntry = this._getLoggerEntry(stateManager, 'cm_duplicate', cmIds);
263
        const course = stateManager.get('course');
264
        // Lock all target sections.
265
        const sectionIds = new Set();
266
        if (targetSectionId) {
267
            sectionIds.add(targetSectionId);
268
        } else {
269
            cmIds.forEach((cmId) => {
270
                const cm = stateManager.get('cm', cmId);
271
                sectionIds.add(cm.sectionid);
272
            });
273
        }
274
        this.sectionLock(stateManager, Array.from(sectionIds), true);
275
 
276
        const updates = await this._callEditWebservice('cm_duplicate', course.id, cmIds, targetSectionId, targetCmId);
277
        this.bulkReset(stateManager);
278
        stateManager.processUpdates(updates);
279
 
280
        this.sectionLock(stateManager, Array.from(sectionIds), false);
281
        stateManager.addLoggerEntry(await logEntry);
282
    }
283
 
284
    /**
285
     * Move course modules to specific course location.
286
     *
287
     * Note that one of targetSectionId or targetCmId should be provided in order to identify the
288
     * new location:
289
     *  - targetCmId: the activities will be located avobe the target cm. The targetSectionId
290
     *                value will be ignored in this case.
291
     *  - targetSectionId: the activities will be appended to the section. In this case
292
     *                     targetSectionId should not be present.
293
     *
294
     * @param {StateManager} stateManager the current state manager
295
     * @param {array} cmids the list of cm ids to move
296
     * @param {number} targetSectionId the target section id
297
     * @param {number} targetCmId the target course module id
298
     */
299
    async cmMove(stateManager, cmids, targetSectionId, targetCmId) {
300
        if (!targetSectionId && !targetCmId) {
301
            throw new Error(`Mutation cmMove requires targetSectionId or targetCmId`);
302
        }
303
        const course = stateManager.get('course');
304
        this.cmLock(stateManager, cmids, true);
305
        const updates = await this._callEditWebservice('cm_move', course.id, cmids, targetSectionId, targetCmId);
306
        this.bulkReset(stateManager);
307
        stateManager.processUpdates(updates);
308
        this.cmLock(stateManager, cmids, false);
309
    }
310
 
311
    /**
312
     * Move course modules to specific course location.
313
     *
314
     * @deprecated since Moodle 4.4 MDL-77038.
315
     * @todo MDL-80116 This will be deleted in Moodle 4.8.
316
     * @param {StateManager} stateManager the current state manager
317
     * @param {array} sectionIds the list of section ids to move
318
     * @param {number} targetSectionId the target section id
319
     */
320
    async sectionMove(stateManager, sectionIds, targetSectionId) {
321
        log.debug('sectionMove() is deprecated. Use sectionMoveAfter() instead');
322
        if (!targetSectionId) {
323
            throw new Error(`Mutation sectionMove requires targetSectionId`);
324
        }
325
        const course = stateManager.get('course');
326
        this.sectionLock(stateManager, sectionIds, true);
327
        const updates = await this._callEditWebservice('section_move', course.id, sectionIds, targetSectionId);
328
        this.bulkReset(stateManager);
329
        stateManager.processUpdates(updates);
330
        this.sectionLock(stateManager, sectionIds, false);
331
    }
332
 
333
    /**
334
     * Move course modules after a specific course location.
335
     *
336
     * @param {StateManager} stateManager the current state manager
337
     * @param {array} sectionIds the list of section ids to move
338
     * @param {number} targetSectionId the target section id
339
     */
340
    async sectionMoveAfter(stateManager, sectionIds, targetSectionId) {
341
        if (!targetSectionId) {
342
            throw new Error(`Mutation sectionMoveAfter requires targetSectionId`);
343
        }
344
        const course = stateManager.get('course');
345
        this.sectionLock(stateManager, sectionIds, true);
346
        const updates = await this._callEditWebservice('section_move_after', course.id, sectionIds, targetSectionId);
347
        this.bulkReset(stateManager);
348
        stateManager.processUpdates(updates);
349
        this.sectionLock(stateManager, sectionIds, false);
350
    }
351
 
352
    /**
353
     * Add a new section to a specific course location.
354
     *
355
     * @param {StateManager} stateManager the current state manager
356
     * @param {number} targetSectionId optional the target section id
357
     */
358
    async addSection(stateManager, targetSectionId) {
359
        if (!targetSectionId) {
360
            targetSectionId = 0;
361
        }
362
        const course = stateManager.get('course');
363
        const updates = await this._callEditWebservice('section_add', course.id, [], targetSectionId);
364
        stateManager.processUpdates(updates);
365
    }
366
 
367
    /**
368
     * Delete sections.
369
     *
370
     * @param {StateManager} stateManager the current state manager
371
     * @param {array} sectionIds the list of course modules ids
372
     */
373
    async sectionDelete(stateManager, sectionIds) {
374
        const course = stateManager.get('course');
375
        const updates = await this._callEditWebservice('section_delete', course.id, sectionIds);
376
        this.bulkReset(stateManager);
377
        stateManager.processUpdates(updates);
378
    }
379
 
380
    /**
381
     * Delete cms.
382
     * @param {StateManager} stateManager the current state manager
383
     * @param {array} cmIds the list of section ids
384
     */
385
    async cmDelete(stateManager, cmIds) {
386
        const course = stateManager.get('course');
387
        this.cmLock(stateManager, cmIds, true);
388
        const updates = await this._callEditWebservice('cm_delete', course.id, cmIds);
389
        this.bulkReset(stateManager);
390
        this.cmLock(stateManager, cmIds, false);
391
        stateManager.processUpdates(updates);
392
    }
393
 
394
    /**
395
     * Mark or unmark course modules as dragging.
396
     *
397
     * @param {StateManager} stateManager the current state manager
398
     * @param {array} cmIds the list of course modules ids
399
     * @param {bool} dragValue the new dragging value
400
     */
401
    cmDrag(stateManager, cmIds, dragValue) {
402
        this.setPageItem(stateManager);
403
        this._setElementsValue(stateManager, 'cm', cmIds, 'dragging', dragValue);
404
    }
405
 
406
    /**
407
     * Mark or unmark course sections as dragging.
408
     *
409
     * @param {StateManager} stateManager the current state manager
410
     * @param {array} sectionIds the list of section ids
411
     * @param {bool} dragValue the new dragging value
412
     */
413
    sectionDrag(stateManager, sectionIds, dragValue) {
414
        this.setPageItem(stateManager);
415
        this._setElementsValue(stateManager, 'section', sectionIds, 'dragging', dragValue);
416
    }
417
 
418
    /**
419
     * Mark or unmark course modules as complete.
420
     *
421
     * @param {StateManager} stateManager the current state manager
422
     * @param {array} cmIds the list of course modules ids
423
     * @param {bool} complete the new completion value
424
     */
425
    cmCompletion(stateManager, cmIds, complete) {
426
        const newValue = (complete) ? 1 : 0;
427
        this._setElementsValue(stateManager, 'cm', cmIds, 'completionstate', newValue);
428
    }
429
 
430
    /**
431
     * Move cms to the right: indent = 1.
432
     * @param {StateManager} stateManager the current state manager
433
     * @param {array} cmIds the list of cm ids
434
     */
435
    async cmMoveRight(stateManager, cmIds) {
436
        await this._cmBasicAction(stateManager, 'cm_moveright', cmIds);
437
    }
438
 
439
    /**
440
     * Move cms to the left: indent = 0.
441
     * @param {StateManager} stateManager the current state manager
442
     * @param {array} cmIds the list of cm ids
443
     */
444
    async cmMoveLeft(stateManager, cmIds) {
445
        await this._cmBasicAction(stateManager, 'cm_moveleft', cmIds);
446
    }
447
 
448
    /**
449
     * Set cms group mode to NOGROUPS.
450
     * @param {StateManager} stateManager the current state manager
451
     * @param {array} cmIds the list of cm ids
452
     */
453
    async cmNoGroups(stateManager, cmIds) {
454
        await this._cmBasicAction(stateManager, 'cm_nogroups', cmIds);
455
    }
456
 
457
    /**
458
     * Set cms group mode to VISIBLEGROUPS.
459
     * @param {StateManager} stateManager the current state manager
460
     * @param {array} cmIds the list of cm ids
461
     */
462
    async cmVisibleGroups(stateManager, cmIds) {
463
        await this._cmBasicAction(stateManager, 'cm_visiblegroups', cmIds);
464
    }
465
 
466
    /**
467
     * Set cms group mode to SEPARATEGROUPS.
468
     * @param {StateManager} stateManager the current state manager
469
     * @param {array} cmIds the list of cm ids
470
     */
471
    async cmSeparateGroups(stateManager, cmIds) {
472
        await this._cmBasicAction(stateManager, 'cm_separategroups', cmIds);
473
    }
474
 
475
    /**
476
     * Lock or unlock course modules.
477
     *
478
     * @param {StateManager} stateManager the current state manager
479
     * @param {array} cmIds the list of course modules ids
480
     * @param {bool} lockValue the new locked value
481
     */
482
    cmLock(stateManager, cmIds, lockValue) {
483
        this._setElementsValue(stateManager, 'cm', cmIds, 'locked', lockValue);
484
    }
485
 
486
    /**
487
     * Lock or unlock course sections.
488
     *
489
     * @param {StateManager} stateManager the current state manager
490
     * @param {array} sectionIds the list of section ids
491
     * @param {bool} lockValue the new locked value
492
     */
493
    sectionLock(stateManager, sectionIds, lockValue) {
494
        this._setElementsValue(stateManager, 'section', sectionIds, 'locked', lockValue);
495
    }
496
 
497
    _setElementsValue(stateManager, name, ids, fieldName, newValue) {
498
        stateManager.setReadOnly(false);
499
        ids.forEach((id) => {
500
            const element = stateManager.get(name, id);
501
            if (element) {
502
                element[fieldName] = newValue;
503
            }
504
        });
505
        stateManager.setReadOnly(true);
506
    }
507
 
508
    /**
509
     * Set the page current item.
510
     *
511
     * Only one element of the course state can be the page item at a time.
512
     *
513
     * There are several actions that can alter the page current item. For example, when the user is in an activity
514
     * page, the page item is always the activity one. However, in a course page, when the user scrolls to an element,
515
     * this element get the page item.
516
     *
517
     * If the page item is static means that it is not meant to change. This is important because
518
     * static page items has some special logic. For example, if a cm is the static page item
519
     * and it is inside a collapsed section, the course index will expand the section to make it visible.
520
     *
521
     * @param {StateManager} stateManager the current state manager
522
     * @param {String|undefined} type the element type (section or cm). Undefined will remove the current page item.
523
     * @param {Number|undefined} id the element id
524
     * @param {boolean|undefined} isStatic if the page item is static
525
     */
526
    setPageItem(stateManager, type, id, isStatic) {
527
        let newPageItem;
528
        if (type !== undefined) {
529
            newPageItem = stateManager.get(type, id);
530
            if (!newPageItem) {
531
                return;
532
            }
533
        }
534
        stateManager.setReadOnly(false);
535
        // Remove the current page item.
536
        const course = stateManager.get('course');
537
        course.pageItem = null;
538
        // Save the new page item.
539
        if (newPageItem) {
540
            course.pageItem = {
541
                id,
542
                type,
543
                sectionId: (type == 'section') ? newPageItem.id : newPageItem.sectionid,
544
                isStatic,
545
            };
546
        }
547
        stateManager.setReadOnly(true);
548
    }
549
 
550
    /**
551
     * Unlock all course elements.
552
     *
553
     * @param {StateManager} stateManager the current state manager
554
     */
555
    unlockAll(stateManager) {
556
        const state = stateManager.state;
557
        stateManager.setReadOnly(false);
558
        state.section.forEach((section) => {
559
            section.locked = false;
560
        });
561
        state.cm.forEach((cm) => {
562
            cm.locked = false;
563
        });
564
        stateManager.setReadOnly(true);
565
    }
566
 
567
    /**
568
     * Update the course index collapsed attribute of some sections.
569
     *
570
     * @param {StateManager} stateManager the current state manager
571
     * @param {array} sectionIds the affected section ids
572
     * @param {boolean} collapsed the new collapsed value
573
     */
574
    async sectionIndexCollapsed(stateManager, sectionIds, collapsed) {
575
        const affectedSections = this._updateStateSectionPreference(stateManager, 'indexcollapsed', sectionIds, collapsed);
576
        if (!affectedSections) {
577
            return;
578
        }
579
        const course = stateManager.get('course');
580
        let actionName = 'section_index_collapsed';
581
        if (!collapsed) {
582
            actionName = 'section_index_expanded';
583
        }
584
        await this._callEditWebservice(actionName, course.id, affectedSections);
585
    }
586
 
587
    /**
588
     * Update the course index collapsed attribute of all sections.
589
     *
590
     * @param {StateManager} stateManager the current state manager
591
     * @param {boolean} collapsed the new collapsed value
592
     */
593
    async allSectionsIndexCollapsed(stateManager, collapsed) {
594
        const sectionIds = stateManager.getIds('section');
595
        this.sectionIndexCollapsed(stateManager, sectionIds, collapsed);
596
    }
597
 
598
    /**
599
     * Update the course content collapsed attribute of some sections.
600
     *
601
     * @param {StateManager} stateManager the current state manager
602
     * @param {array} sectionIds the affected section ids
603
     * @param {boolean} collapsed the new collapsed value
604
     */
605
    async sectionContentCollapsed(stateManager, sectionIds, collapsed) {
606
        const affectedSections = this._updateStateSectionPreference(stateManager, 'contentcollapsed', sectionIds, collapsed);
607
        if (!affectedSections) {
608
            return;
609
        }
610
        const course = stateManager.get('course');
611
        let actionName = 'section_content_collapsed';
612
        if (!collapsed) {
613
            actionName = 'section_content_expanded';
614
        }
615
        await this._callEditWebservice(actionName, course.id, affectedSections);
616
    }
617
 
618
    /**
619
     * Private batch update for a section preference attribute.
620
     *
621
     * @param {StateManager} stateManager the current state manager
622
     * @param {string} preferenceName the preference name
623
     * @param {array} sectionIds the affected section ids
624
     * @param {boolean} preferenceValue the new preferenceValue value
625
     * @return {Number[]|null} sections ids with the preference value true or null if no update is required
626
     */
627
    _updateStateSectionPreference(stateManager, preferenceName, sectionIds, preferenceValue) {
628
        stateManager.setReadOnly(false);
629
        const affectedSections = [];
630
        // Check if we need to update preferences.
631
        sectionIds.forEach(sectionId => {
632
            const section = stateManager.get('section', sectionId);
633
            if (section === undefined) {
634
                stateManager.setReadOnly(true);
635
                return null;
636
            }
637
            const newValue = preferenceValue ?? section[preferenceName];
638
            if (section[preferenceName] != newValue) {
639
                section[preferenceName] = newValue;
640
                affectedSections.push(section.id);
641
            }
642
        });
643
        stateManager.setReadOnly(true);
644
        return affectedSections;
645
    }
646
 
647
    /**
648
     * Enable/disable bulk editing.
649
     *
650
     * Note: reenabling the bulk will clean the current selection.
651
     *
652
     * @param {StateManager} stateManager the current state manager
653
     * @param {Boolean} enabled the new bulk state.
654
     */
655
    bulkEnable(stateManager, enabled) {
656
        const state = stateManager.state;
657
        stateManager.setReadOnly(false);
658
        state.bulk.enabled = enabled;
659
        state.bulk.selectedType = '';
660
        state.bulk.selection = [];
661
        stateManager.setReadOnly(true);
662
    }
663
 
664
    /**
665
     * Reset the current selection.
666
     * @param {StateManager} stateManager the current state manager
667
     */
668
    bulkReset(stateManager) {
669
        const state = stateManager.state;
670
        stateManager.setReadOnly(false);
671
        state.bulk.selectedType = '';
672
        state.bulk.selection = [];
673
        stateManager.setReadOnly(true);
674
    }
675
 
676
    /**
677
     * Select a list of cms.
678
     * @param {StateManager} stateManager the current state manager
679
     * @param {array} cmIds the list of cm ids
680
     */
681
    cmSelect(stateManager, cmIds) {
682
        this._addIdsToSelection(stateManager, 'cm', cmIds);
683
    }
684
 
685
    /**
686
     * Unselect a list of cms.
687
     * @param {StateManager} stateManager the current state manager
688
     * @param {array} cmIds the list of cm ids
689
     */
690
    cmUnselect(stateManager, cmIds) {
691
        this._removeIdsFromSelection(stateManager, 'cm', cmIds);
692
    }
693
 
694
    /**
695
     * Select a list of sections.
696
     * @param {StateManager} stateManager the current state manager
697
     * @param {array} sectionIds the list of cm ids
698
     */
699
    sectionSelect(stateManager, sectionIds) {
700
        this._addIdsToSelection(stateManager, 'section', sectionIds);
701
    }
702
 
703
    /**
704
     * Unselect a list of sections.
705
     * @param {StateManager} stateManager the current state manager
706
     * @param {array} sectionIds the list of cm ids
707
     */
708
    sectionUnselect(stateManager, sectionIds) {
709
        this._removeIdsFromSelection(stateManager, 'section', sectionIds);
710
    }
711
 
712
    /**
713
     * Add some ids to the current bulk selection.
714
     * @param {StateManager} stateManager the current state manager
715
     * @param {String} typeName the type name (section/cm)
716
     * @param {array} ids the list of ids
717
     */
718
    _addIdsToSelection(stateManager, typeName, ids) {
719
        const bulk = stateManager.state.bulk;
720
        if (!bulk?.enabled) {
721
            throw new Error(`Bulk is not enabled`);
722
        }
723
        if (bulk?.selectedType !== "" && bulk?.selectedType !== typeName) {
724
            throw new Error(`Cannot add ${typeName} to the current selection`);
725
        }
726
 
727
        // Stored ids are strings for compatability with HTML data attributes.
728
        ids = ids.map(value => value.toString());
729
 
730
        stateManager.setReadOnly(false);
731
        bulk.selectedType = typeName;
732
        const newSelection = new Set([...bulk.selection, ...ids]);
733
        bulk.selection = [...newSelection];
734
        stateManager.setReadOnly(true);
735
    }
736
 
737
    /**
738
     * Remove some ids to the current bulk selection.
739
     *
740
     * The method resets the selection type if the current selection is empty.
741
     *
742
     * @param {StateManager} stateManager the current state manager
743
     * @param {String} typeName the type name (section/cm)
744
     * @param {array} ids the list of ids
745
     */
746
    _removeIdsFromSelection(stateManager, typeName, ids) {
747
        const bulk = stateManager.state.bulk;
748
        if (!bulk?.enabled) {
749
            throw new Error(`Bulk is not enabled`);
750
        }
751
        if (bulk?.selectedType !== "" && bulk?.selectedType !== typeName) {
752
            throw new Error(`Cannot remove ${typeName} from the current selection`);
753
        }
754
 
755
        // Stored ids are strings for compatability with HTML data attributes.
756
        ids = ids.map(value => value.toString());
757
 
758
        stateManager.setReadOnly(false);
759
        const IdsToFilter = new Set(ids);
760
        bulk.selection = bulk.selection.filter(current => !IdsToFilter.has(current));
761
        if (bulk.selection.length === 0) {
762
            bulk.selectedType = '';
763
        }
764
        stateManager.setReadOnly(true);
765
    }
766
 
767
    /**
768
     * Get updated state data related to some cm ids.
769
     *
770
     * @method cmState
771
     * @param {StateManager} stateManager the current state
772
     * @param {array} cmids the list of cm ids to update
773
     */
774
    async cmState(stateManager, cmids) {
775
        this.cmLock(stateManager, cmids, true);
776
        const course = stateManager.get('course');
777
        const updates = await this._callEditWebservice('cm_state', course.id, cmids);
778
        stateManager.processUpdates(updates);
779
        this.cmLock(stateManager, cmids, false);
780
    }
781
 
782
    /**
783
     * Get updated state data related to some section ids.
784
     *
785
     * @method sectionState
786
     * @param {StateManager} stateManager the current state
787
     * @param {array} sectionIds the list of section ids to update
788
     */
789
    async sectionState(stateManager, sectionIds) {
790
        this.sectionLock(stateManager, sectionIds, true);
791
        const course = stateManager.get('course');
792
        const updates = await this._callEditWebservice('section_state', course.id, sectionIds);
793
        stateManager.processUpdates(updates);
794
        this.sectionLock(stateManager, sectionIds, false);
795
    }
796
 
797
    /**
798
     * Get the full updated state data of the course.
799
     *
800
     * @param {StateManager} stateManager the current state
801
     */
802
    async courseState(stateManager) {
803
        const course = stateManager.get('course');
804
        const updates = await this._callEditWebservice('course_state', course.id);
805
        stateManager.processUpdates(updates);
806
    }
807
 
808
}