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