Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | 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
    /**
1441 ariadna 70
     * Private method to call core_courseformat_create_module webservice.
71
     *
72
     * @deprecated since Moodle 5.0 MDL-83469.
73
     * @todo MDL-83851 This will be deleted in Moodle 6.0.
74
     * @method _callEditWebservice
75
     * @param {number} courseId
76
     * @param {string} modName module name
77
     * @param {number} targetSectionNum target section number
78
     * @param {number} targetCmId optional target cm id
79
     */
80
    async _callAddModuleWebservice(courseId, modName, targetSectionNum, targetCmId) {
81
        log.debug('_callAddModuleWebservice() is deprecated. Use _callNewModuleWebservice() instead');
82
        const args = {
83
            courseid: courseId,
84
            modname: modName,
85
            targetsectionnum: targetSectionNum,
86
        };
87
        if (targetCmId) {
88
            args.targetcmid = targetCmId;
89
        }
90
        let ajaxresult = await ajax.call([{
91
            methodname: 'core_courseformat_create_module',
92
            args,
93
        }])[0];
94
        return JSON.parse(ajaxresult);
95
    }
96
 
97
    /**
98
     * Private method to call core_courseformat_new_module webservice.
99
     *
100
     * @method _callEditWebservice
101
     * @param {number} courseId
102
     * @param {string} modName module name
103
     * @param {number} targetSectionId target section number
104
     * @param {number} targetCmId optional target cm id
105
     */
106
    async _callNewModuleWebservice(courseId, modName, targetSectionId, targetCmId) {
107
        const args = {
108
            courseid: courseId,
109
            modname: modName,
110
            targetsectionid: targetSectionId,
111
        };
112
        if (targetCmId) {
113
            args.targetcmid = targetCmId;
114
        }
115
        let ajaxresult = await ajax.call([{
116
            methodname: 'core_courseformat_new_module',
117
            args,
118
        }])[0];
119
        return JSON.parse(ajaxresult);
120
    }
121
 
122
    /**
1 efrain 123
     * Execute a basic section state action.
124
     * @param {StateManager} stateManager the current state manager
125
     * @param {string} action the action name
126
     * @param {array} sectionIds the section ids
127
     * @param {number} targetSectionId optional target section id (for moving actions)
128
     * @param {number} targetCmId optional target cm id (for moving actions)
129
     */
130
    async _sectionBasicAction(stateManager, action, sectionIds, targetSectionId, targetCmId) {
131
        const logEntry = this._getLoggerEntry(stateManager, action, sectionIds, {
132
            targetSectionId,
133
            targetCmId,
134
            itemType: 'section',
135
        });
136
        const course = stateManager.get('course');
137
        this.sectionLock(stateManager, sectionIds, true);
138
        const updates = await this._callEditWebservice(
139
            action,
140
            course.id,
141
            sectionIds,
142
            targetSectionId,
143
            targetCmId
144
        );
145
        this.bulkReset(stateManager);
146
        stateManager.processUpdates(updates);
147
        this.sectionLock(stateManager, sectionIds, false);
148
        stateManager.addLoggerEntry(await logEntry);
149
    }
150
 
151
    /**
152
     * Execute a basic course module state action.
153
     * @param {StateManager} stateManager the current state manager
154
     * @param {string} action the action name
155
     * @param {array} cmIds the cm ids
156
     * @param {number} targetSectionId optional target section id (for moving actions)
157
     * @param {number} targetCmId optional target cm id (for moving actions)
158
     */
159
    async _cmBasicAction(stateManager, action, cmIds, targetSectionId, targetCmId) {
160
        const logEntry = this._getLoggerEntry(stateManager, action, cmIds, {
161
            targetSectionId,
162
            targetCmId,
163
            itemType: 'cm',
164
        });
165
        const course = stateManager.get('course');
166
        this.cmLock(stateManager, cmIds, true);
167
        const updates = await this._callEditWebservice(
168
            action,
169
            course.id,
170
            cmIds,
171
            targetSectionId,
172
            targetCmId
173
        );
174
        this.bulkReset(stateManager);
175
        stateManager.processUpdates(updates);
176
        this.cmLock(stateManager, cmIds, false);
177
        stateManager.addLoggerEntry(await logEntry);
178
    }
179
 
180
    /**
181
     * Get log entry for the current action.
182
     * @param {StateManager} stateManager the current state manager
183
     * @param {string} action the action name
184
     * @param {int[]|null} itemIds the element ids
185
     * @param {Object|undefined} data extra params for the log entry
186
     * @param {string|undefined} data.itemType the element type (will be taken from action if none)
187
     * @param {int|null|undefined} data.targetSectionId the target section id
188
     * @param {int|null|undefined} data.targetCmId the target cm id
189
     * @param {String|null|undefined} data.component optional component (for format plugins)
1441 ariadna 190
     * @param {Object|undefined} [data.feedbackParams] the params to build the feedback message
1 efrain 191
     * @return {Object} the log entry
192
     */
193
    async _getLoggerEntry(stateManager, action, itemIds, data = {}) {
194
        if (!isLoggerSet) {
195
            // In case the logger has not been set from init(), ensure we set the logger.
196
            stateManager.setLogger(new SRLogger());
197
            isLoggerSet = true;
198
        }
1441 ariadna 199
        let feedbackParams = {
1 efrain 200
            action,
201
            itemType: data.itemType ?? action.split('_')[0],
202
        };
203
        let batch = '';
204
        if (itemIds.length > 1) {
205
            feedbackParams.count = itemIds.length;
206
            batch = '_batch';
207
        } else if (itemIds.length === 1) {
208
            const itemInfo = stateManager.get(feedbackParams.itemType, itemIds[0]);
209
            feedbackParams.name = itemInfo.title ?? itemInfo.name;
210
            // Apply shortener for modules like label.
211
        }
212
        if (data.targetSectionId) {
213
            feedbackParams.targetSectionName = stateManager.get('section', data.targetSectionId).title;
214
        }
215
        if (data.targetCmId) {
216
            feedbackParams.targetCmName = stateManager.get('cm', data.targetCmId).name;
217
        }
1441 ariadna 218
        if (data.feedbackParams) {
219
            feedbackParams = {...feedbackParams, ...data.feedbackParams};
220
        }
1 efrain 221
 
222
        const message = await getString(
223
            `${action.toLowerCase()}_feedback${batch}`,
224
            data.component ?? 'core_courseformat',
225
            feedbackParams
226
        );
227
 
228
        return {
229
            feedbackMessage: message,
230
        };
231
    }
232
 
233
    /**
234
     * Mutation module initialize.
235
     *
236
     * The reactive instance will execute this method when addMutations or setMutation is invoked.
237
     *
238
     * @param {StateManager} stateManager the state manager
239
     */
240
    init(stateManager) {
241
        // Add a method to prepare the fields when some update is coming from the server.
242
        stateManager.addUpdateTypes({
243
            prepareFields: this._prepareFields,
244
        });
245
        // Use the screen reader-only logger (SRLogger) to handle the feedback messages from the mutations.
246
        stateManager.setLogger(new SRLogger());
247
        isLoggerSet = true;
248
    }
249
 
250
    /**
251
     * Add default values to state elements.
252
     *
253
     * This method is called every time a webservice returns a update state message.
254
     *
255
     * @param {Object} stateManager the state manager
256
     * @param {String} updateName the state element to update
257
     * @param {Object} fields the new data
258
     * @returns {Object} final fields data
259
     */
260
    _prepareFields(stateManager, updateName, fields) {
261
        // Any update should unlock the element.
262
        fields.locked = false;
263
        return fields;
264
    }
265
 
266
    /**
267
     * Hides sections.
268
     * @param {StateManager} stateManager the current state manager
269
     * @param {array} sectionIds the list of section ids
270
     */
271
    async sectionHide(stateManager, sectionIds) {
272
        await this._sectionBasicAction(stateManager, 'section_hide', sectionIds);
273
    }
274
 
275
    /**
276
     * Show sections.
277
     * @param {StateManager} stateManager the current state manager
278
     * @param {array} sectionIds the list of section ids
279
     */
280
    async sectionShow(stateManager, sectionIds) {
281
        await this._sectionBasicAction(stateManager, 'section_show', sectionIds);
282
    }
283
 
284
    /**
285
     * Show cms.
286
     * @param {StateManager} stateManager the current state manager
287
     * @param {array} cmIds the list of cm ids
288
     */
289
    async cmShow(stateManager, cmIds) {
290
        await this._cmBasicAction(stateManager, 'cm_show', cmIds);
291
    }
292
 
293
    /**
294
     * Hide cms.
295
     * @param {StateManager} stateManager the current state manager
296
     * @param {array} cmIds the list of cm ids
297
     */
298
    async cmHide(stateManager, cmIds) {
299
        await this._cmBasicAction(stateManager, 'cm_hide', cmIds);
300
    }
301
 
302
    /**
303
     * Stealth cms.
304
     * @param {StateManager} stateManager the current state manager
305
     * @param {array} cmIds the list of cm ids
306
     */
307
    async cmStealth(stateManager, cmIds) {
308
        await this._cmBasicAction(stateManager, 'cm_stealth', cmIds);
309
    }
310
 
311
    /**
312
     * Duplicate course modules
313
     * @param {StateManager} stateManager the current state manager
314
     * @param {array} cmIds the list of course modules ids
315
     * @param {number|undefined} targetSectionId the optional target sectionId
316
     * @param {number|undefined} targetCmId the target course module id
317
     */
318
    async cmDuplicate(stateManager, cmIds, targetSectionId, targetCmId) {
319
        const logEntry = this._getLoggerEntry(stateManager, 'cm_duplicate', cmIds);
320
        const course = stateManager.get('course');
321
        // Lock all target sections.
322
        const sectionIds = new Set();
323
        if (targetSectionId) {
324
            sectionIds.add(targetSectionId);
325
        } else {
326
            cmIds.forEach((cmId) => {
327
                const cm = stateManager.get('cm', cmId);
328
                sectionIds.add(cm.sectionid);
329
            });
330
        }
331
        this.sectionLock(stateManager, Array.from(sectionIds), true);
332
 
333
        const updates = await this._callEditWebservice('cm_duplicate', course.id, cmIds, targetSectionId, targetCmId);
334
        this.bulkReset(stateManager);
335
        stateManager.processUpdates(updates);
336
 
337
        this.sectionLock(stateManager, Array.from(sectionIds), false);
338
        stateManager.addLoggerEntry(await logEntry);
339
    }
340
 
341
    /**
342
     * Move course modules to specific course location.
343
     *
344
     * Note that one of targetSectionId or targetCmId should be provided in order to identify the
345
     * new location:
346
     *  - targetCmId: the activities will be located avobe the target cm. The targetSectionId
347
     *                value will be ignored in this case.
348
     *  - targetSectionId: the activities will be appended to the section. In this case
349
     *                     targetSectionId should not be present.
350
     *
351
     * @param {StateManager} stateManager the current state manager
352
     * @param {array} cmids the list of cm ids to move
353
     * @param {number} targetSectionId the target section id
354
     * @param {number} targetCmId the target course module id
355
     */
356
    async cmMove(stateManager, cmids, targetSectionId, targetCmId) {
357
        if (!targetSectionId && !targetCmId) {
358
            throw new Error(`Mutation cmMove requires targetSectionId or targetCmId`);
359
        }
360
        const course = stateManager.get('course');
361
        this.cmLock(stateManager, cmids, true);
362
        const updates = await this._callEditWebservice('cm_move', course.id, cmids, targetSectionId, targetCmId);
363
        this.bulkReset(stateManager);
364
        stateManager.processUpdates(updates);
365
        this.cmLock(stateManager, cmids, false);
366
    }
367
 
368
    /**
369
     * Move course modules after a specific course location.
370
     *
371
     * @param {StateManager} stateManager the current state manager
372
     * @param {array} sectionIds the list of section ids to move
373
     * @param {number} targetSectionId the target section id
374
     */
375
    async sectionMoveAfter(stateManager, sectionIds, targetSectionId) {
376
        if (!targetSectionId) {
377
            throw new Error(`Mutation sectionMoveAfter requires targetSectionId`);
378
        }
379
        const course = stateManager.get('course');
380
        this.sectionLock(stateManager, sectionIds, true);
381
        const updates = await this._callEditWebservice('section_move_after', course.id, sectionIds, targetSectionId);
382
        this.bulkReset(stateManager);
383
        stateManager.processUpdates(updates);
384
        this.sectionLock(stateManager, sectionIds, false);
385
    }
386
 
387
    /**
388
     * Add a new section to a specific course location.
389
     *
390
     * @param {StateManager} stateManager the current state manager
391
     * @param {number} targetSectionId optional the target section id
392
     */
393
    async addSection(stateManager, targetSectionId) {
394
        if (!targetSectionId) {
395
            targetSectionId = 0;
396
        }
397
        const course = stateManager.get('course');
398
        const updates = await this._callEditWebservice('section_add', course.id, [], targetSectionId);
399
        stateManager.processUpdates(updates);
1441 ariadna 400
        const logEntry = this._getLoggerEntry(stateManager, 'section_add', []);
401
        stateManager.addLoggerEntry(await logEntry);
1 efrain 402
    }
403
 
404
    /**
405
     * Delete sections.
406
     *
407
     * @param {StateManager} stateManager the current state manager
408
     * @param {array} sectionIds the list of course modules ids
409
     */
410
    async sectionDelete(stateManager, sectionIds) {
411
        const course = stateManager.get('course');
1441 ariadna 412
        const logEntry = this._getLoggerEntry(stateManager, 'section_delete', sectionIds);
1 efrain 413
        const updates = await this._callEditWebservice('section_delete', course.id, sectionIds);
414
        this.bulkReset(stateManager);
415
        stateManager.processUpdates(updates);
1441 ariadna 416
        stateManager.addLoggerEntry(await logEntry);
1 efrain 417
    }
418
 
419
    /**
420
     * Delete cms.
421
     * @param {StateManager} stateManager the current state manager
422
     * @param {array} cmIds the list of section ids
423
     */
424
    async cmDelete(stateManager, cmIds) {
425
        const course = stateManager.get('course');
1441 ariadna 426
        const logEntry = this._getLoggerEntry(stateManager, 'cm_delete', cmIds);
1 efrain 427
        this.cmLock(stateManager, cmIds, true);
428
        const updates = await this._callEditWebservice('cm_delete', course.id, cmIds);
429
        this.bulkReset(stateManager);
430
        this.cmLock(stateManager, cmIds, false);
431
        stateManager.processUpdates(updates);
1441 ariadna 432
        stateManager.addLoggerEntry(await logEntry);
1 efrain 433
    }
434
 
435
    /**
1441 ariadna 436
     * Add a new module to a specific course section.
437
     *
438
     * @deprecated since Moodle 5.0 MDL-83469.
439
     * @todo MDL-83851 This will be deleted in Moodle 6.0.
440
     * @param {StateManager} stateManager the current state manager
441
     * @param {string} modName the modulename to add
442
     * @param {number} targetSectionNum the target section number
443
     * @param {number} targetCmId optional the target cm id
444
     */
445
    async addModule(stateManager, modName, targetSectionNum, targetCmId) {
446
        log.debug('addModule() is deprecated. Use newModule() instead');
447
        if (!modName) {
448
            throw new Error(`Mutation addModule requires moduleName`);
449
        }
450
        if (!targetSectionNum) {
451
            throw new Error(`Mutation addModule requires targetSectionNum`);
452
        }
453
        if (!targetCmId) {
454
            targetCmId = 0;
455
        }
456
        const course = stateManager.get('course');
457
        const updates = await this._callAddModuleWebservice(course.id, modName, targetSectionNum, targetCmId);
458
        stateManager.processUpdates(updates);
459
    }
460
 
461
    /**
462
     * Add a new module to a specific course section.
463
     *
464
     * @param {StateManager} stateManager the current state manager
465
     * @param {string} modName the modulename to add
466
     * @param {number} targetSectionId the target section id
467
     * @param {number} targetCmId optional the target cm id
468
     */
469
    async newModule(stateManager, modName, targetSectionId, targetCmId) {
470
        if (!modName) {
471
            throw new Error(`Mutation newModule requires moduleName`);
472
        }
473
        if (!targetSectionId) {
474
            throw new Error(`Mutation newModule requires targetSectionId`);
475
        }
476
        if (!targetCmId) {
477
            targetCmId = 0;
478
        }
479
        const course = stateManager.get('course');
480
        const pluginname = await getString(
481
            'pluginname',
482
            `${modName.toLowerCase()}`,
483
        );
484
        const logEntry = this._getLoggerEntry(stateManager, 'cm_add', [], {
485
            feedbackParams: {
486
                'modname': pluginname,
487
            },
488
        });
489
        const updates = await this._callNewModuleWebservice(course.id, modName, targetSectionId, targetCmId);
490
        stateManager.processUpdates(updates);
491
        stateManager.addLoggerEntry(await logEntry);
492
    }
493
 
494
    /**
1 efrain 495
     * Mark or unmark course modules as dragging.
496
     *
497
     * @param {StateManager} stateManager the current state manager
498
     * @param {array} cmIds the list of course modules ids
499
     * @param {bool} dragValue the new dragging value
500
     */
501
    cmDrag(stateManager, cmIds, dragValue) {
502
        this.setPageItem(stateManager);
503
        this._setElementsValue(stateManager, 'cm', cmIds, 'dragging', dragValue);
504
    }
505
 
506
    /**
507
     * Mark or unmark course sections as dragging.
508
     *
509
     * @param {StateManager} stateManager the current state manager
510
     * @param {array} sectionIds the list of section ids
511
     * @param {bool} dragValue the new dragging value
512
     */
513
    sectionDrag(stateManager, sectionIds, dragValue) {
514
        this.setPageItem(stateManager);
515
        this._setElementsValue(stateManager, 'section', sectionIds, 'dragging', dragValue);
516
    }
517
 
518
    /**
519
     * Mark or unmark course modules as complete.
520
     *
521
     * @param {StateManager} stateManager the current state manager
522
     * @param {array} cmIds the list of course modules ids
523
     * @param {bool} complete the new completion value
524
     */
1441 ariadna 525
    async cmCompletion(stateManager, cmIds, complete) {
526
        const newState = (complete) ? 1 : 0;
527
        const action = (newState == 1) ? 'cm_complete' : 'cm_uncomplete';
528
        const logEntry = this._getLoggerEntry(stateManager, action, cmIds);
529
        stateManager.setReadOnly(false);
530
        cmIds.forEach((id) => {
531
            const element = stateManager.get('cm', id);
532
            if (element) {
533
                element.isoverallcomplete = complete;
534
                element.completionstate = newState;
535
            }
536
        });
537
        stateManager.setReadOnly(true);
538
        stateManager.addLoggerEntry(await logEntry);
1 efrain 539
    }
540
 
541
    /**
542
     * Move cms to the right: indent = 1.
543
     * @param {StateManager} stateManager the current state manager
544
     * @param {array} cmIds the list of cm ids
545
     */
546
    async cmMoveRight(stateManager, cmIds) {
547
        await this._cmBasicAction(stateManager, 'cm_moveright', cmIds);
548
    }
549
 
550
    /**
551
     * Move cms to the left: indent = 0.
552
     * @param {StateManager} stateManager the current state manager
553
     * @param {array} cmIds the list of cm ids
554
     */
555
    async cmMoveLeft(stateManager, cmIds) {
556
        await this._cmBasicAction(stateManager, 'cm_moveleft', cmIds);
557
    }
558
 
559
    /**
560
     * Set cms group mode to NOGROUPS.
561
     * @param {StateManager} stateManager the current state manager
562
     * @param {array} cmIds the list of cm ids
563
     */
564
    async cmNoGroups(stateManager, cmIds) {
565
        await this._cmBasicAction(stateManager, 'cm_nogroups', cmIds);
566
    }
567
 
568
    /**
569
     * Set cms group mode to VISIBLEGROUPS.
570
     * @param {StateManager} stateManager the current state manager
571
     * @param {array} cmIds the list of cm ids
572
     */
573
    async cmVisibleGroups(stateManager, cmIds) {
574
        await this._cmBasicAction(stateManager, 'cm_visiblegroups', cmIds);
575
    }
576
 
577
    /**
578
     * Set cms group mode to SEPARATEGROUPS.
579
     * @param {StateManager} stateManager the current state manager
580
     * @param {array} cmIds the list of cm ids
581
     */
582
    async cmSeparateGroups(stateManager, cmIds) {
583
        await this._cmBasicAction(stateManager, 'cm_separategroups', cmIds);
584
    }
585
 
586
    /**
587
     * Lock or unlock course modules.
588
     *
589
     * @param {StateManager} stateManager the current state manager
590
     * @param {array} cmIds the list of course modules ids
591
     * @param {bool} lockValue the new locked value
592
     */
593
    cmLock(stateManager, cmIds, lockValue) {
594
        this._setElementsValue(stateManager, 'cm', cmIds, 'locked', lockValue);
595
    }
596
 
597
    /**
598
     * Lock or unlock course sections.
599
     *
600
     * @param {StateManager} stateManager the current state manager
601
     * @param {array} sectionIds the list of section ids
602
     * @param {bool} lockValue the new locked value
603
     */
604
    sectionLock(stateManager, sectionIds, lockValue) {
605
        this._setElementsValue(stateManager, 'section', sectionIds, 'locked', lockValue);
606
    }
607
 
608
    _setElementsValue(stateManager, name, ids, fieldName, newValue) {
609
        stateManager.setReadOnly(false);
610
        ids.forEach((id) => {
611
            const element = stateManager.get(name, id);
612
            if (element) {
613
                element[fieldName] = newValue;
614
            }
615
        });
616
        stateManager.setReadOnly(true);
617
    }
618
 
619
    /**
620
     * Set the page current item.
621
     *
622
     * Only one element of the course state can be the page item at a time.
623
     *
624
     * There are several actions that can alter the page current item. For example, when the user is in an activity
625
     * page, the page item is always the activity one. However, in a course page, when the user scrolls to an element,
626
     * this element get the page item.
627
     *
628
     * If the page item is static means that it is not meant to change. This is important because
629
     * static page items has some special logic. For example, if a cm is the static page item
630
     * and it is inside a collapsed section, the course index will expand the section to make it visible.
631
     *
632
     * @param {StateManager} stateManager the current state manager
633
     * @param {String|undefined} type the element type (section or cm). Undefined will remove the current page item.
634
     * @param {Number|undefined} id the element id
635
     * @param {boolean|undefined} isStatic if the page item is static
636
     */
637
    setPageItem(stateManager, type, id, isStatic) {
638
        let newPageItem;
639
        if (type !== undefined) {
640
            newPageItem = stateManager.get(type, id);
641
            if (!newPageItem) {
642
                return;
643
            }
644
        }
1441 ariadna 645
        const course = stateManager.get('course');
646
        if (course.pageItem && course.pageItem.type === type && course.pageItem.id === id) {
647
            return;
648
        }
1 efrain 649
        stateManager.setReadOnly(false);
650
        // Remove the current page item.
651
        course.pageItem = null;
652
        // Save the new page item.
653
        if (newPageItem) {
654
            course.pageItem = {
655
                id,
656
                type,
657
                sectionId: (type == 'section') ? newPageItem.id : newPageItem.sectionid,
658
                isStatic,
659
            };
660
        }
661
        stateManager.setReadOnly(true);
662
    }
663
 
664
    /**
665
     * Unlock all course elements.
666
     *
667
     * @param {StateManager} stateManager the current state manager
668
     */
669
    unlockAll(stateManager) {
670
        const state = stateManager.state;
671
        stateManager.setReadOnly(false);
672
        state.section.forEach((section) => {
673
            section.locked = false;
674
        });
675
        state.cm.forEach((cm) => {
676
            cm.locked = false;
677
        });
678
        stateManager.setReadOnly(true);
679
    }
680
 
681
    /**
682
     * Update the course index collapsed attribute of some sections.
683
     *
684
     * @param {StateManager} stateManager the current state manager
685
     * @param {array} sectionIds the affected section ids
686
     * @param {boolean} collapsed the new collapsed value
687
     */
688
    async sectionIndexCollapsed(stateManager, sectionIds, collapsed) {
689
        const affectedSections = this._updateStateSectionPreference(stateManager, 'indexcollapsed', sectionIds, collapsed);
690
        if (!affectedSections) {
691
            return;
692
        }
693
        const course = stateManager.get('course');
694
        let actionName = 'section_index_collapsed';
695
        if (!collapsed) {
696
            actionName = 'section_index_expanded';
697
        }
698
        await this._callEditWebservice(actionName, course.id, affectedSections);
699
    }
700
 
701
    /**
702
     * Update the course index collapsed attribute of all sections.
703
     *
704
     * @param {StateManager} stateManager the current state manager
705
     * @param {boolean} collapsed the new collapsed value
706
     */
707
    async allSectionsIndexCollapsed(stateManager, collapsed) {
708
        const sectionIds = stateManager.getIds('section');
709
        this.sectionIndexCollapsed(stateManager, sectionIds, collapsed);
710
    }
711
 
712
    /**
713
     * Update the course content collapsed attribute of some sections.
714
     *
715
     * @param {StateManager} stateManager the current state manager
716
     * @param {array} sectionIds the affected section ids
717
     * @param {boolean} collapsed the new collapsed value
718
     */
719
    async sectionContentCollapsed(stateManager, sectionIds, collapsed) {
720
        const affectedSections = this._updateStateSectionPreference(stateManager, 'contentcollapsed', sectionIds, collapsed);
721
        if (!affectedSections) {
722
            return;
723
        }
724
        const course = stateManager.get('course');
725
        let actionName = 'section_content_collapsed';
726
        if (!collapsed) {
727
            actionName = 'section_content_expanded';
728
        }
729
        await this._callEditWebservice(actionName, course.id, affectedSections);
730
    }
731
 
732
    /**
733
     * Private batch update for a section preference attribute.
734
     *
735
     * @param {StateManager} stateManager the current state manager
736
     * @param {string} preferenceName the preference name
737
     * @param {array} sectionIds the affected section ids
738
     * @param {boolean} preferenceValue the new preferenceValue value
739
     * @return {Number[]|null} sections ids with the preference value true or null if no update is required
740
     */
741
    _updateStateSectionPreference(stateManager, preferenceName, sectionIds, preferenceValue) {
742
        stateManager.setReadOnly(false);
743
        const affectedSections = [];
744
        // Check if we need to update preferences.
745
        sectionIds.forEach(sectionId => {
746
            const section = stateManager.get('section', sectionId);
747
            if (section === undefined) {
748
                stateManager.setReadOnly(true);
749
                return null;
750
            }
751
            const newValue = preferenceValue ?? section[preferenceName];
752
            if (section[preferenceName] != newValue) {
753
                section[preferenceName] = newValue;
754
                affectedSections.push(section.id);
755
            }
756
        });
757
        stateManager.setReadOnly(true);
758
        return affectedSections;
759
    }
760
 
761
    /**
762
     * Enable/disable bulk editing.
763
     *
764
     * Note: reenabling the bulk will clean the current selection.
765
     *
766
     * @param {StateManager} stateManager the current state manager
767
     * @param {Boolean} enabled the new bulk state.
768
     */
769
    bulkEnable(stateManager, enabled) {
770
        const state = stateManager.state;
771
        stateManager.setReadOnly(false);
772
        state.bulk.enabled = enabled;
773
        state.bulk.selectedType = '';
774
        state.bulk.selection = [];
775
        stateManager.setReadOnly(true);
776
    }
777
 
778
    /**
779
     * Reset the current selection.
780
     * @param {StateManager} stateManager the current state manager
781
     */
782
    bulkReset(stateManager) {
783
        const state = stateManager.state;
784
        stateManager.setReadOnly(false);
785
        state.bulk.selectedType = '';
786
        state.bulk.selection = [];
787
        stateManager.setReadOnly(true);
788
    }
789
 
790
    /**
791
     * Select a list of cms.
792
     * @param {StateManager} stateManager the current state manager
793
     * @param {array} cmIds the list of cm ids
794
     */
795
    cmSelect(stateManager, cmIds) {
796
        this._addIdsToSelection(stateManager, 'cm', cmIds);
797
    }
798
 
799
    /**
800
     * Unselect a list of cms.
801
     * @param {StateManager} stateManager the current state manager
802
     * @param {array} cmIds the list of cm ids
803
     */
804
    cmUnselect(stateManager, cmIds) {
805
        this._removeIdsFromSelection(stateManager, 'cm', cmIds);
806
    }
807
 
808
    /**
809
     * Select a list of sections.
810
     * @param {StateManager} stateManager the current state manager
811
     * @param {array} sectionIds the list of cm ids
812
     */
813
    sectionSelect(stateManager, sectionIds) {
814
        this._addIdsToSelection(stateManager, 'section', sectionIds);
815
    }
816
 
817
    /**
818
     * Unselect a list of sections.
819
     * @param {StateManager} stateManager the current state manager
820
     * @param {array} sectionIds the list of cm ids
821
     */
822
    sectionUnselect(stateManager, sectionIds) {
823
        this._removeIdsFromSelection(stateManager, 'section', sectionIds);
824
    }
825
 
826
    /**
827
     * Add some ids to the current bulk selection.
828
     * @param {StateManager} stateManager the current state manager
829
     * @param {String} typeName the type name (section/cm)
830
     * @param {array} ids the list of ids
831
     */
832
    _addIdsToSelection(stateManager, typeName, ids) {
833
        const bulk = stateManager.state.bulk;
834
        if (!bulk?.enabled) {
835
            throw new Error(`Bulk is not enabled`);
836
        }
837
        if (bulk?.selectedType !== "" && bulk?.selectedType !== typeName) {
838
            throw new Error(`Cannot add ${typeName} to the current selection`);
839
        }
840
 
841
        // Stored ids are strings for compatability with HTML data attributes.
842
        ids = ids.map(value => value.toString());
843
 
844
        stateManager.setReadOnly(false);
845
        bulk.selectedType = typeName;
846
        const newSelection = new Set([...bulk.selection, ...ids]);
847
        bulk.selection = [...newSelection];
848
        stateManager.setReadOnly(true);
849
    }
850
 
851
    /**
852
     * Remove some ids to the current bulk selection.
853
     *
854
     * The method resets the selection type if the current selection is empty.
855
     *
856
     * @param {StateManager} stateManager the current state manager
857
     * @param {String} typeName the type name (section/cm)
858
     * @param {array} ids the list of ids
859
     */
860
    _removeIdsFromSelection(stateManager, typeName, ids) {
861
        const bulk = stateManager.state.bulk;
862
        if (!bulk?.enabled) {
863
            throw new Error(`Bulk is not enabled`);
864
        }
865
        if (bulk?.selectedType !== "" && bulk?.selectedType !== typeName) {
866
            throw new Error(`Cannot remove ${typeName} from the current selection`);
867
        }
868
 
869
        // Stored ids are strings for compatability with HTML data attributes.
870
        ids = ids.map(value => value.toString());
871
 
872
        stateManager.setReadOnly(false);
873
        const IdsToFilter = new Set(ids);
874
        bulk.selection = bulk.selection.filter(current => !IdsToFilter.has(current));
875
        if (bulk.selection.length === 0) {
876
            bulk.selectedType = '';
877
        }
878
        stateManager.setReadOnly(true);
879
    }
880
 
881
    /**
882
     * Get updated state data related to some cm ids.
883
     *
884
     * @method cmState
885
     * @param {StateManager} stateManager the current state
886
     * @param {array} cmids the list of cm ids to update
887
     */
888
    async cmState(stateManager, cmids) {
889
        this.cmLock(stateManager, cmids, true);
890
        const course = stateManager.get('course');
891
        const updates = await this._callEditWebservice('cm_state', course.id, cmids);
892
        stateManager.processUpdates(updates);
893
        this.cmLock(stateManager, cmids, false);
894
    }
895
 
896
    /**
897
     * Get updated state data related to some section ids.
898
     *
899
     * @method sectionState
900
     * @param {StateManager} stateManager the current state
901
     * @param {array} sectionIds the list of section ids to update
902
     */
903
    async sectionState(stateManager, sectionIds) {
904
        this.sectionLock(stateManager, sectionIds, true);
905
        const course = stateManager.get('course');
906
        const updates = await this._callEditWebservice('section_state', course.id, sectionIds);
907
        stateManager.processUpdates(updates);
908
        this.sectionLock(stateManager, sectionIds, false);
909
    }
910
 
911
    /**
912
     * Get the full updated state data of the course.
913
     *
914
     * @param {StateManager} stateManager the current state
915
     */
916
    async courseState(stateManager) {
917
        const course = stateManager.get('course');
918
        const updates = await this._callEditWebservice('course_state', course.id);
919
        stateManager.processUpdates(updates);
920
    }
921
 
922
}