Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
/**
18
 * Library to handle drag and drop course uploads
19
 *
20
 * @package    core
21
 * @subpackage lib
22
 * @copyright  2012 Davo smith
23
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24
 */
25
 
26
defined('MOODLE_INTERNAL') || die();
27
 
28
require_once($CFG->dirroot.'/repository/lib.php');
29
require_once($CFG->dirroot.'/repository/upload/lib.php');
30
require_once($CFG->dirroot.'/course/lib.php');
31
 
32
/**
33
 * Add the Javascript to enable drag and drop upload to a course page
34
 *
35
 * @param object $course The currently displayed course
36
 * @param array $modnames The list of enabled (visible) modules on this site
37
 * @return void
38
 */
39
function dndupload_add_to_course($course, $modnames) {
40
    global $CFG, $PAGE;
41
 
42
    $showstatus = optional_param('notifyeditingon', false, PARAM_BOOL);
43
 
44
    // Get all handlers.
45
    $handler = new dndupload_handler($course, $modnames);
46
    $jsdata = $handler->get_js_data();
47
    if (empty($jsdata->types) && empty($jsdata->filehandlers)) {
48
        return; // No valid handlers - don't enable drag and drop.
49
    }
50
 
51
    // Add the javascript to the page.
52
    $jsmodule = array(
53
        'name' => 'coursedndupload',
54
        'fullpath' => '/course/dndupload.js',
55
        'strings' => array(
56
            array('addfilehere', 'moodle'),
57
            array('dndworkingfiletextlink', 'moodle'),
58
            array('dndworkingfilelink', 'moodle'),
59
            array('dndworkingfiletext', 'moodle'),
60
            array('dndworkingfile', 'moodle'),
61
            array('dndworkingtextlink', 'moodle'),
62
            array('dndworkingtext', 'moodle'),
63
            array('dndworkinglink', 'moodle'),
64
            array('namedfiletoolarge', 'moodle'),
65
            array('actionchoice', 'moodle'),
66
            array('servererror', 'moodle'),
67
            array('filereaderror', 'moodle'),
68
            array('upload', 'moodle'),
69
            array('cancel', 'moodle'),
70
            array('changesmadereallygoaway', 'moodle')
71
        ),
72
        'requires' => array('node', 'event', 'json', 'anim')
73
    );
74
    $vars = array(
75
        array('courseid' => $course->id,
76
              'maxbytes' => get_user_max_upload_file_size($PAGE->context, $CFG->maxbytes, $course->maxbytes),
77
              'handlers' => $handler->get_js_data(),
78
              'showstatus' => $showstatus)
79
    );
80
 
81
    $PAGE->requires->js_init_call('M.course_dndupload.init', $vars, true, $jsmodule);
82
}
83
 
84
 
85
/**
86
 * Stores all the information about the available dndupload handlers
87
 *
88
 * @package    core
89
 * @copyright  2012 Davo Smith
90
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
91
 */
92
class dndupload_handler {
93
 
94
    /**
95
     * @var array A list of all registered mime types that can be dropped onto a course
96
     *            along with the modules that will handle them.
97
     */
98
    protected $types = array();
99
 
100
    /**
101
     * @var array  A list of the different file types (extensions) that different modules
102
     *             will handle.
103
     */
104
    protected $filehandlers = array();
105
 
106
    /**
107
     * @var context_course|null
108
     */
109
    protected $context = null;
110
 
111
    /**
112
     * Gather a list of dndupload handlers from the different mods
113
     *
114
     * @param object $course The course this is being added to (to check course_allowed_module() )
115
     */
116
    public function __construct($course, $modnames = null) {
117
        global $CFG, $PAGE;
118
 
119
        // Add some default types to handle.
120
        // Note: 'Files' type is hard-coded into the Javascript as this needs to be ...
121
        // ... treated a little differently.
122
        $this->register_type('url', array('url', 'text/uri-list', 'text/x-moz-url'), get_string('addlinkhere', 'moodle'),
123
                        get_string('nameforlink', 'moodle'), get_string('whatforlink', 'moodle'), 10);
124
        $this->register_type('text/html', array('text/html'), get_string('addpagehere', 'moodle'),
125
                        get_string('nameforpage', 'moodle'), get_string('whatforpage', 'moodle'), 20);
126
        $this->register_type('text', array('text', 'text/plain'), get_string('addpagehere', 'moodle'),
127
                        get_string('nameforpage', 'moodle'), get_string('whatforpage', 'moodle'), 30);
128
 
129
        $this->context = context_course::instance($course->id);
130
 
131
        // Loop through all modules to find handlers.
132
        $mods = get_plugin_list_with_function('mod', 'dndupload_register');
133
        foreach ($mods as $component => $funcname) {
134
            list($modtype, $modname) = core_component::normalize_component($component);
135
            if ($modnames && !array_key_exists($modname, $modnames)) {
136
                continue; // Module is deactivated (hidden) at the site level.
137
            }
138
            if (!course_allowed_module($course, $modname)) {
139
                continue; // User does not have permission to add this module to the course.
140
            }
141
            $resp = $funcname();
142
            if (!$resp) {
143
                continue;
144
            }
145
            if (isset($resp['files'])) {
146
                foreach ($resp['files'] as $file) {
147
                    $this->register_file_handler($file['extension'], $modname, $file['message']);
148
                }
149
            }
150
            if (isset($resp['addtypes'])) {
151
                foreach ($resp['addtypes'] as $type) {
152
                    if (isset($type['priority'])) {
153
                        $priority = $type['priority'];
154
                    } else {
155
                        $priority = 100;
156
                    }
157
                    if (!isset($type['handlermessage'])) {
158
                        $type['handlermessage'] = '';
159
                    }
160
                    $this->register_type($type['identifier'], $type['datatransfertypes'],
161
                                    $type['addmessage'], $type['namemessage'], $type['handlermessage'], $priority);
162
                }
163
            }
164
            if (isset($resp['types'])) {
165
                foreach ($resp['types'] as $type) {
166
                    $noname = !empty($type['noname']);
167
                    $this->register_type_handler($type['identifier'], $modname, $type['message'], $noname);
168
                }
169
            }
170
            $PAGE->requires->string_for_js('pluginname', $modname);
171
        }
172
    }
173
 
174
    /**
175
     * Used to add a new mime type that can be drag and dropped onto a
176
     * course displayed in a browser window
177
     *
178
     * @param string $identifier The name that this type will be known as
179
     * @param array $datatransfertypes An array of the different types in the browser
180
     *                                 'dataTransfer.types' object that will map to this type
181
     * @param string $addmessage The message to display in the browser when this type is being
182
     *                           dragged onto the page
183
     * @param string $namemessage The message to pop up when asking for the name to give the
184
     *                            course module instance when it is created
185
     * @param string $handlermessage The message to pop up when asking which module should handle this type
186
     * @param int $priority Controls the order in which types are checked by the browser (mainly
187
     *                      needed to check for 'text' last as that is usually given as fallback)
188
     */
189
    protected function register_type($identifier, $datatransfertypes, $addmessage, $namemessage, $handlermessage, $priority=100) {
190
        if ($this->is_known_type($identifier)) {
191
            throw new coding_exception("Type $identifier is already registered");
192
        }
193
 
194
        $add = new stdClass;
195
        $add->identifier = $identifier;
196
        $add->datatransfertypes = $datatransfertypes;
197
        $add->addmessage = $addmessage;
198
        $add->namemessage = $namemessage;
199
        $add->handlermessage = $handlermessage;
200
        $add->priority = $priority;
201
        $add->handlers = array();
202
 
203
        $this->types[$identifier] = $add;
204
    }
205
 
206
    /**
207
     * Used to declare that a particular module will handle a particular type
208
     * of dropped data
209
     *
210
     * @param string $type The name of the type (as declared in register_type)
211
     * @param string $module The name of the module to handle this type
212
     * @param string $message The message to show the user if more than one handler is registered
213
     *                        for a type and the user needs to make a choice between them
214
     * @param bool $noname If true, the 'name' dialog should be disabled in the pop-up.
215
     * @throws coding_exception
216
     */
217
    protected function register_type_handler($type, $module, $message, $noname) {
218
        if (!$this->is_known_type($type)) {
219
            throw new coding_exception("Trying to add handler for unknown type $type");
220
        }
221
 
222
        $add = new stdClass;
223
        $add->type = $type;
224
        $add->module = $module;
225
        $add->message = $message;
226
        $add->noname = $noname ? 1 : 0;
227
 
228
        $this->types[$type]->handlers[] = $add;
229
    }
230
 
231
    /**
232
     * Used to declare that a particular module will handle a particular type
233
     * of dropped file
234
     *
235
     * @param string $extension The file extension to handle ('*' for all types)
236
     * @param string $module The name of the module to handle this type
237
     * @param string $message The message to show the user if more than one handler is registered
238
     *                        for a type and the user needs to make a choice between them
239
     */
240
    protected function register_file_handler($extension, $module, $message) {
241
        $extension = strtolower($extension);
242
 
243
        $add = new stdClass;
244
        $add->extension = $extension;
245
        $add->module = $module;
246
        $add->message = $message;
247
 
248
        $this->filehandlers[] = $add;
249
    }
250
 
251
    /**
252
     * Check to see if the type has been registered
253
     *
254
     * @param string $type The identifier of the type you are interested in
255
     * @return bool True if the type is registered
256
     */
257
    public function is_known_type($type) {
258
        return array_key_exists($type, $this->types);
259
    }
260
 
261
    /**
262
     * Check to see if the module in question has registered to handle the
263
     * type given
264
     *
265
     * @param string $module The name of the module
266
     * @param string $type The identifier of the type
267
     * @return bool True if the module has registered to handle that type
268
     */
269
    public function has_type_handler($module, $type) {
270
        if (!$this->is_known_type($type)) {
271
            throw new coding_exception("Checking for handler for unknown type $type");
272
        }
273
        foreach ($this->types[$type]->handlers as $handler) {
274
            if ($handler->module == $module) {
275
                return true;
276
            }
277
        }
278
        return false;
279
    }
280
 
281
    /**
282
     * Check to see if the module in question has registered to handle files
283
     * with the given extension (or to handle all file types)
284
     *
285
     * @param string $module The name of the module
286
     * @param string $extension The extension of the uploaded file
287
     * @return bool True if the module has registered to handle files with
288
     *              that extension (or to handle all file types)
289
     */
290
    public function has_file_handler($module, $extension) {
291
        foreach ($this->filehandlers as $handler) {
292
            if ($handler->module == $module) {
293
                if ($handler->extension == '*' || $handler->extension == $extension) {
294
                    return true;
295
                }
296
            }
297
        }
298
        return false;
299
    }
300
 
301
    /**
302
     * Gets a list of the file types that are handled by a particular module
303
     *
304
     * @param string $module The name of the module to check
305
     * @return array of file extensions or string '*'
306
     */
307
    public function get_handled_file_types($module) {
308
        $types = array();
309
        foreach ($this->filehandlers as $handler) {
310
            if ($handler->module == $module) {
311
                if ($handler->extension == '*') {
312
                    return '*';
313
                } else {
314
                    // Prepending '.' as otherwise mimeinfo fails.
315
                    $types[] = '.'.$handler->extension;
316
                }
317
            }
318
        }
319
        return $types;
320
    }
321
 
322
    /**
323
     * Returns an object to pass onto the javascript code with data about all the
324
     * registered file / type handlers
325
     *
326
     * @return object Data to pass on to Javascript code
327
     */
328
    public function get_js_data() {
329
        global $CFG;
330
 
331
        $ret = new stdClass;
332
 
333
        // Sort the types by priority.
334
        uasort($this->types, array($this, 'type_compare'));
335
 
336
        $ret->types = array();
337
        if (!empty($CFG->dndallowtextandlinks)) {
338
            foreach ($this->types as $type) {
339
                if (empty($type->handlers)) {
340
                    continue; // Skip any types without registered handlers.
341
                }
342
                $ret->types[] = $type;
343
            }
344
        }
345
 
346
        $ret->filehandlers = $this->filehandlers;
347
        $uploadrepo = repository::get_instances(array('type' => 'upload', 'currentcontext' => $this->context));
348
        if (empty($uploadrepo)) {
349
            $ret->filehandlers = array(); // No upload repo => no file handlers.
350
        }
351
 
352
        return $ret;
353
    }
354
 
355
    /**
356
     * Comparison function used when sorting types by priority
357
     * @param object $type1 first type to compare
358
     * @param object $type2 second type to compare
359
     * @return integer -1 for $type1 < $type2; 1 for $type1 > $type2; 0 for equal
360
     */
361
    protected function type_compare($type1, $type2) {
362
        if ($type1->priority < $type2->priority) {
363
            return -1;
364
        }
365
        if ($type1->priority > $type2->priority) {
366
            return 1;
367
        }
368
        return 0;
369
    }
370
 
371
}
372
 
373
/**
374
 * Processes the upload, creating the course module and returning the result
375
 *
376
 * @package    core
377
 * @copyright  2012 Davo Smith
378
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
379
 */
380
class dndupload_ajax_processor {
381
 
382
    /** Returned when no error has occurred */
383
    const ERROR_OK = 0;
384
 
385
    /** @var object The course that we are uploading to */
386
    protected $course = null;
387
 
388
    /** @var context_course The course context for capability checking */
389
    protected $context = null;
390
 
391
    /** @var int The section number we are uploading to */
392
    protected $section = null;
393
 
394
    /** @var string The type of upload (e.g. 'Files', 'text/plain') */
395
    protected $type = null;
396
 
397
    /** @var object The details of the module type that will be created */
398
    protected $module= null;
399
 
400
    /** @var object The course module that has been created */
401
    protected $cm = null;
402
 
403
    /** @var dndupload_handler used to check the allowed file types */
404
    protected $dnduploadhandler = null;
405
 
406
    /** @var string The name to give the new activity instance */
407
    protected $displayname = null;
408
 
409
    /**
410
     * Set up some basic information needed to handle the upload
411
     *
412
     * @param int $courseid The ID of the course we are uploading to
413
     * @param int $section The section number we are uploading to
414
     * @param string $type The type of upload (as reported by the browser)
415
     * @param string $modulename The name of the module requested to handle this upload
416
     */
417
    public function __construct($courseid, $section, $type, $modulename) {
418
        global $DB;
419
 
420
        if (!defined('AJAX_SCRIPT')) {
421
            throw new coding_exception('dndupload_ajax_processor should only be used within AJAX requests');
422
        }
423
 
424
        $this->course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
425
 
426
        require_login($this->course, false);
427
        $this->context = context_course::instance($this->course->id);
428
 
429
        if (!is_number($section) || $section < 0) {
430
            throw new coding_exception("Invalid section number $section");
431
        }
432
        $this->section = $section;
433
        $this->type = $type;
434
 
435
        if (!$this->module = $DB->get_record('modules', array('name' => $modulename))) {
436
            throw new coding_exception("Module $modulename does not exist");
437
        }
438
 
439
        $this->dnduploadhandler = new dndupload_handler($this->course);
440
    }
441
 
442
    /**
443
     * Check if this upload is a 'file' upload
444
     *
445
     * @return bool true if it is a 'file' upload, false otherwise
446
     */
447
    protected function is_file_upload() {
448
        return ($this->type == 'Files');
449
    }
450
 
451
    /**
452
     * Process the upload - creating the module in the course and returning the result to the browser
453
     *
454
     * @param string $displayname optional the name (from the browser) to give the course module instance
455
     * @param string $content optional the content of the upload (for non-file uploads)
456
     */
457
    public function process($displayname = null, $content = null) {
458
        require_capability('moodle/course:manageactivities', $this->context);
459
 
460
        if ($this->is_file_upload()) {
461
            require_capability('moodle/course:managefiles', $this->context);
462
            if ($content != null) {
463
                throw new moodle_exception('fileuploadwithcontent', 'moodle');
464
            }
465
        } else {
466
            if (empty($content)) {
467
                throw new moodle_exception('dnduploadwithoutcontent', 'moodle');
468
            }
469
        }
470
 
471
        require_sesskey();
472
 
473
        $this->displayname = $displayname;
474
 
475
        if ($this->is_file_upload()) {
476
            $this->handle_file_upload();
477
        } else {
478
            $this->handle_other_upload($content);
479
        }
480
    }
481
 
482
    /**
483
     * Handle uploads containing files - create the course module, ask the upload repository
484
     * to process the file, ask the mod to set itself up, then return the result to the browser
485
     */
486
    protected function handle_file_upload() {
487
        global $CFG;
488
 
489
        // Add the file to a draft file area.
490
        $draftitemid = file_get_unused_draft_itemid();
491
        $maxbytes = get_user_max_upload_file_size($this->context, $CFG->maxbytes, $this->course->maxbytes);
492
        $types = $this->dnduploadhandler->get_handled_file_types($this->module->name);
493
        $repo = repository::get_instances(array('type' => 'upload', 'currentcontext' => $this->context));
494
        if (empty($repo)) {
495
            throw new moodle_exception('errornouploadrepo', 'moodle');
496
        }
497
        $repo = reset($repo); // Get the first (and only) upload repo.
498
        // Pre-emptively purge the navigation cache so the upload repo can close the session.
499
        navigation_cache::destroy_volatile_caches();
500
        $details = $repo->process_upload(null, $maxbytes, $types, '/', $draftitemid);
501
        if (empty($this->displayname)) {
502
            $this->displayname = $this->display_name_from_file($details['file']);
503
        }
504
 
505
        // Create a course module to hold the new instance.
506
        $this->create_course_module();
507
 
508
        // Ask the module to set itself up.
509
        $moduledata = $this->prepare_module_data($draftitemid);
510
        $instanceid = plugin_callback('mod', $this->module->name, 'dndupload', 'handle', array($moduledata), 'invalidfunction');
511
        if ($instanceid === 'invalidfunction') {
512
            throw new coding_exception("{$this->module->name} does not support drag and drop upload (missing {$this->module->name}_dndupload_handle function");
513
        }
514
 
515
        // Finish setting up the course module.
516
        $this->finish_setup_course_module($instanceid);
517
    }
518
 
519
    /**
520
     * Handle uploads not containing file - create the course module, ask the mod to
521
     * set itself up, then return the result to the browser
522
     *
523
     * @param string $content the content uploaded to the browser
524
     */
525
    protected function handle_other_upload($content) {
526
        // Check this plugin is registered to handle this type of upload
527
        if (!$this->dnduploadhandler->has_type_handler($this->module->name, $this->type)) {
528
            $info = (object)array('modname' => $this->module->name, 'type' => $this->type);
529
            throw new moodle_exception('moddoesnotsupporttype', 'moodle', $info);
530
        }
531
 
532
        // Create a course module to hold the new instance.
533
        $this->create_course_module();
534
 
535
        // Ask the module to set itself up.
536
        $moduledata = $this->prepare_module_data(null, $content);
537
        $instanceid = plugin_callback('mod', $this->module->name, 'dndupload', 'handle', array($moduledata), 'invalidfunction');
538
        if ($instanceid === 'invalidfunction') {
539
            throw new coding_exception("{$this->module->name} does not support drag and drop upload (missing {$this->module->name}_dndupload_handle function");
540
        }
541
 
542
        // Finish setting up the course module.
543
        $this->finish_setup_course_module($instanceid);
544
    }
545
 
546
    /**
547
     * Generate the name of the mod instance from the name of the file
548
     * (remove the extension and convert underscore => space
549
     *
550
     * @param string $filename the filename of the uploaded file
551
     * @return string the display name to use
552
     */
553
    protected function display_name_from_file($filename) {
554
        $pos = core_text::strrpos($filename, '.');
555
        if ($pos) { // Want to skip if $pos === 0 OR $pos === false.
556
            $filename = core_text::substr($filename, 0, $pos);
557
        }
558
        return str_replace('_', ' ', $filename);
559
    }
560
 
561
    /**
562
     * Create the coursemodule to hold the file/content that has been uploaded
563
     */
564
    protected function create_course_module() {
565
        global $CFG;
566
        require_once($CFG->dirroot.'/course/modlib.php');
567
        list($module, $context, $cw, $cm, $data) = prepare_new_moduleinfo_data($this->course, $this->module->name, $this->section);
568
 
569
        $data->coursemodule = $data->id = add_course_module($data);
570
        $this->cm = $data;
571
    }
572
 
573
    /**
574
     * Gather together all the details to pass on to the mod, so that it can initialise it's
575
     * own database tables
576
     *
577
     * @param int $draftitemid optional the id of the draft area containing the file (for file uploads)
578
     * @param string $content optional the content dropped onto the course (for non-file uploads)
579
     * @return object data to pass on to the mod, containing:
580
     *              string $type the 'type' as registered with dndupload_handler (or 'Files')
581
     *              object $course the course the upload was for
582
     *              int $draftitemid optional the id of the draft area containing the files
583
     *              int $coursemodule id of the course module that has already been created
584
     *              string $displayname the name to use for this activity (can be overriden by the mod)
585
     */
586
    protected function prepare_module_data($draftitemid = null, $content = null) {
587
        $data = new stdClass();
588
        $data->type = $this->type;
589
        $data->course = $this->course;
590
        if ($draftitemid) {
591
            $data->draftitemid = $draftitemid;
592
        } else if ($content) {
593
            $data->content = $content;
594
        }
595
        $data->coursemodule = $this->cm->id;
596
        $data->displayname = $this->displayname;
597
        return $data;
598
    }
599
 
600
    /**
601
     * Called after the mod has set itself up, to finish off any course module settings
602
     * (set instance id, add to correct section, set visibility, etc.) and send the response
603
     *
604
     * @param int $instanceid id returned by the mod when it was created
605
     */
606
    protected function finish_setup_course_module($instanceid) {
607
        global $DB, $USER;
608
 
609
        if (!$instanceid) {
610
            // Something has gone wrong - undo everything we can.
611
            course_delete_module($this->cm->id);
612
            throw new moodle_exception('errorcreatingactivity', 'moodle', '', $this->module->name);
613
        }
614
 
615
        // Note the section visibility
616
        $visible = get_fast_modinfo($this->course)->get_section_info($this->section)->visible;
617
 
618
        $DB->set_field('course_modules', 'instance', $instanceid, array('id' => $this->cm->id));
619
 
620
        \course_modinfo::purge_course_module_cache($this->course->id, $this->cm->id);
621
        // Rebuild the course cache after update action
622
        rebuild_course_cache($this->course->id, true, true);
623
 
624
        $sectionid = course_add_cm_to_section($this->course, $this->cm->id, $this->section);
625
 
626
        set_coursemodule_visible($this->cm->id, $visible);
627
        if (!$visible) {
628
            $DB->set_field('course_modules', 'visibleold', 1, array('id' => $this->cm->id));
629
        }
630
 
631
        // retrieve the final info about this module.
632
        $info = get_fast_modinfo($this->course);
633
        if (!isset($info->cms[$this->cm->id])) {
634
            // The course module has not been properly created in the course - undo everything.
635
            course_delete_module($this->cm->id);
636
            throw new moodle_exception('errorcreatingactivity', 'moodle', '', $this->module->name);
637
        }
638
        $mod = $info->get_cm($this->cm->id);
639
 
640
        // Trigger course module created event.
641
        $event = \core\event\course_module_created::create_from_cm($mod);
642
        $event->trigger();
643
 
644
        $this->send_response($mod);
645
    }
646
 
647
    /**
648
     * Send the details of the newly created activity back to the client browser
649
     *
650
     * @param cm_info $mod details of the mod just created
651
     */
652
    protected function send_response($mod) {
653
        global $OUTPUT, $PAGE;
654
 
655
        $resp = new stdClass();
656
        $resp->error = self::ERROR_OK;
657
        $resp->elementid = 'module-' . $mod->id;
658
        $resp->cmid = $mod->id;
659
 
660
        $format = course_get_format($this->course);
661
        $renderer = $format->get_renderer($PAGE);
662
        $modinfo = $format->get_modinfo();
663
        $section = $modinfo->get_section_info($mod->sectionnum);
664
 
665
        // Get the new element html content.
666
        $resp->fullcontent = $renderer->course_section_updated_cm_item($format, $section, $mod);
667
 
668
        echo $OUTPUT->header();
669
        echo json_encode($resp);
670
        die();
671
    }
672
}