Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403]

   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          $details = $repo->process_upload(null, $maxbytes, $types, '/', $draftitemid);
 499          if (empty($this->displayname)) {
 500              $this->displayname = $this->display_name_from_file($details['file']);
 501          }
 502  
 503          // Create a course module to hold the new instance.
 504          $this->create_course_module();
 505  
 506          // Ask the module to set itself up.
 507          $moduledata = $this->prepare_module_data($draftitemid);
 508          $instanceid = plugin_callback('mod', $this->module->name, 'dndupload', 'handle', array($moduledata), 'invalidfunction');
 509          if ($instanceid === 'invalidfunction') {
 510              throw new coding_exception("{$this->module->name} does not support drag and drop upload (missing {$this->module->name}_dndupload_handle function");
 511          }
 512  
 513          // Finish setting up the course module.
 514          $this->finish_setup_course_module($instanceid);
 515      }
 516  
 517      /**
 518       * Handle uploads not containing file - create the course module, ask the mod to
 519       * set itself up, then return the result to the browser
 520       *
 521       * @param string $content the content uploaded to the browser
 522       */
 523      protected function handle_other_upload($content) {
 524          // Check this plugin is registered to handle this type of upload
 525          if (!$this->dnduploadhandler->has_type_handler($this->module->name, $this->type)) {
 526              $info = (object)array('modname' => $this->module->name, 'type' => $this->type);
 527              throw new moodle_exception('moddoesnotsupporttype', 'moodle', $info);
 528          }
 529  
 530          // Create a course module to hold the new instance.
 531          $this->create_course_module();
 532  
 533          // Ask the module to set itself up.
 534          $moduledata = $this->prepare_module_data(null, $content);
 535          $instanceid = plugin_callback('mod', $this->module->name, 'dndupload', 'handle', array($moduledata), 'invalidfunction');
 536          if ($instanceid === 'invalidfunction') {
 537              throw new coding_exception("{$this->module->name} does not support drag and drop upload (missing {$this->module->name}_dndupload_handle function");
 538          }
 539  
 540          // Finish setting up the course module.
 541          $this->finish_setup_course_module($instanceid);
 542      }
 543  
 544      /**
 545       * Generate the name of the mod instance from the name of the file
 546       * (remove the extension and convert underscore => space
 547       *
 548       * @param string $filename the filename of the uploaded file
 549       * @return string the display name to use
 550       */
 551      protected function display_name_from_file($filename) {
 552          $pos = core_text::strrpos($filename, '.');
 553          if ($pos) { // Want to skip if $pos === 0 OR $pos === false.
 554              $filename = core_text::substr($filename, 0, $pos);
 555          }
 556          return str_replace('_', ' ', $filename);
 557      }
 558  
 559      /**
 560       * Create the coursemodule to hold the file/content that has been uploaded
 561       */
 562      protected function create_course_module() {
 563          global $CFG;
 564          require_once($CFG->dirroot.'/course/modlib.php');
 565          list($module, $context, $cw, $cm, $data) = prepare_new_moduleinfo_data($this->course, $this->module->name, $this->section);
 566  
 567          $data->coursemodule = $data->id = add_course_module($data);
 568          $this->cm = $data;
 569      }
 570  
 571      /**
 572       * Gather together all the details to pass on to the mod, so that it can initialise it's
 573       * own database tables
 574       *
 575       * @param int $draftitemid optional the id of the draft area containing the file (for file uploads)
 576       * @param string $content optional the content dropped onto the course (for non-file uploads)
 577       * @return object data to pass on to the mod, containing:
 578       *              string $type the 'type' as registered with dndupload_handler (or 'Files')
 579       *              object $course the course the upload was for
 580       *              int $draftitemid optional the id of the draft area containing the files
 581       *              int $coursemodule id of the course module that has already been created
 582       *              string $displayname the name to use for this activity (can be overriden by the mod)
 583       */
 584      protected function prepare_module_data($draftitemid = null, $content = null) {
 585          $data = new stdClass();
 586          $data->type = $this->type;
 587          $data->course = $this->course;
 588          if ($draftitemid) {
 589              $data->draftitemid = $draftitemid;
 590          } else if ($content) {
 591              $data->content = $content;
 592          }
 593          $data->coursemodule = $this->cm->id;
 594          $data->displayname = $this->displayname;
 595          return $data;
 596      }
 597  
 598      /**
 599       * Called after the mod has set itself up, to finish off any course module settings
 600       * (set instance id, add to correct section, set visibility, etc.) and send the response
 601       *
 602       * @param int $instanceid id returned by the mod when it was created
 603       */
 604      protected function finish_setup_course_module($instanceid) {
 605          global $DB, $USER;
 606  
 607          if (!$instanceid) {
 608              // Something has gone wrong - undo everything we can.
 609              course_delete_module($this->cm->id);
 610              throw new moodle_exception('errorcreatingactivity', 'moodle', '', $this->module->name);
 611          }
 612  
 613          // Note the section visibility
 614          $visible = get_fast_modinfo($this->course)->get_section_info($this->section)->visible;
 615  
 616          $DB->set_field('course_modules', 'instance', $instanceid, array('id' => $this->cm->id));
 617          // Rebuild the course cache after update action
 618          rebuild_course_cache($this->course->id, true);
 619  
 620          $sectionid = course_add_cm_to_section($this->course, $this->cm->id, $this->section);
 621  
 622          set_coursemodule_visible($this->cm->id, $visible);
 623          if (!$visible) {
 624              $DB->set_field('course_modules', 'visibleold', 1, array('id' => $this->cm->id));
 625          }
 626  
 627          // retrieve the final info about this module.
 628          $info = get_fast_modinfo($this->course);
 629          if (!isset($info->cms[$this->cm->id])) {
 630              // The course module has not been properly created in the course - undo everything.
 631              course_delete_module($this->cm->id);
 632              throw new moodle_exception('errorcreatingactivity', 'moodle', '', $this->module->name);
 633          }
 634          $mod = $info->get_cm($this->cm->id);
 635  
 636          // Trigger course module created event.
 637          $event = \core\event\course_module_created::create_from_cm($mod);
 638          $event->trigger();
 639  
 640          $this->send_response($mod);
 641      }
 642  
 643      /**
 644       * Send the details of the newly created activity back to the client browser
 645       *
 646       * @param cm_info $mod details of the mod just created
 647       */
 648      protected function send_response($mod) {
 649          global $OUTPUT, $PAGE;
 650  
 651          $resp = new stdClass();
 652          $resp->error = self::ERROR_OK;
 653          $resp->elementid = 'module-' . $mod->id;
 654  
 655          $courserenderer = $PAGE->get_renderer('core', 'course');
 656          $completioninfo = new completion_info($this->course);
 657          $info = get_fast_modinfo($this->course);
 658          $sr = null;
 659          $modulehtml = $courserenderer->course_section_cm($this->course, $completioninfo,
 660                  $mod, null, array());
 661          $resp->fullcontent = $courserenderer->course_section_cm_list_item($this->course, $completioninfo, $mod, $sr);
 662  
 663          echo $OUTPUT->header();
 664          echo json_encode($resp);
 665          die();
 666      }
 667  }