[ Index ] |
PHP Cross Reference of moodle-2.8 |
[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 * This plugin is used to access files on server file system 19 * 20 * @since Moodle 2.0 21 * @package repository_filesystem 22 * @copyright 2010 Dongsheng Cai {@link http://dongsheng.org} 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 require_once($CFG->dirroot . '/repository/lib.php'); 26 require_once($CFG->libdir . '/filelib.php'); 27 28 /** 29 * repository_filesystem class 30 * 31 * Create a repository from your local filesystem 32 * *NOTE* for security issue, we use a fixed repository path 33 * which is %moodledata%/repository 34 * 35 * @package repository 36 * @copyright 2009 Dongsheng Cai {@link http://dongsheng.org} 37 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 38 */ 39 class repository_filesystem extends repository { 40 41 /** 42 * The subdirectory of the instance. 43 * 44 * @var string 45 */ 46 protected $subdir; 47 48 /** 49 * Constructor 50 * 51 * @param int $repositoryid repository ID 52 * @param int $context context ID 53 * @param array $options 54 */ 55 public function __construct($repositoryid, $context = SYSCONTEXTID, $options = array()) { 56 parent::__construct($repositoryid, $context, $options); 57 $this->subdir = $this->get_option('fs_path'); 58 } 59 60 /** 61 * Get the list of files and directories in that repository. 62 * 63 * @param string $path to browse. 64 * @param string $page page number. 65 * @return array list of files and folders. 66 */ 67 public function get_listing($path = '', $page = '') { 68 global $OUTPUT; 69 70 $list = array(); 71 $list['list'] = array(); 72 $list['manage'] = false; 73 $list['dynload'] = true; 74 $list['nologin'] = true; 75 $list['nosearch'] = true; 76 $list['path'] = array( 77 array('name' => get_string('root', 'repository_filesystem'), 'path' => '') 78 ); 79 80 $path = trim($path, '/'); 81 if (!$this->is_in_repository($path)) { 82 // In case of doubt on the path, reset to default. 83 $path = ''; 84 } 85 $abspath = rtrim($this->get_rootpath() . $path, '/') . '/'; 86 87 // Construct the breadcrumb. 88 $trail = ''; 89 if ($path !== '') { 90 $parts = explode('/', $path); 91 if (count($parts) > 1) { 92 foreach ($parts as $part) { 93 if (!empty($part)) { 94 $trail .= '/' . $part; 95 $list['path'][] = array('name' => $part, 'path' => $trail); 96 } 97 } 98 } else { 99 $list['path'][] = array('name' => $path, 'path' => $path); 100 } 101 } 102 103 // Retrieve list of files and directories and sort them. 104 $fileslist = array(); 105 $dirslist = array(); 106 if ($dh = opendir($abspath)) { 107 while (($file = readdir($dh)) != false) { 108 if ($file != '.' and $file != '..') { 109 if (is_file($abspath . $file)) { 110 $fileslist[] = $file; 111 } else { 112 $dirslist[] = $file; 113 } 114 } 115 } 116 } 117 core_collator::asort($fileslist, core_collator::SORT_NATURAL); 118 core_collator::asort($dirslist, core_collator::SORT_NATURAL); 119 120 // Fill the $list['list']. 121 foreach ($dirslist as $file) { 122 $list['list'][] = array( 123 'title' => $file, 124 'children' => array(), 125 'datecreated' => filectime($abspath . $file), 126 'datemodified' => filemtime($abspath . $file), 127 'thumbnail' => $OUTPUT->pix_url(file_folder_icon(90))->out(false), 128 'path' => $path . '/' . $file 129 ); 130 } 131 foreach ($fileslist as $file) { 132 $node = array( 133 'title' => $file, 134 'source' => $path . '/' . $file, 135 'size' => filesize($abspath . $file), 136 'datecreated' => filectime($abspath . $file), 137 'datemodified' => filemtime($abspath . $file), 138 'thumbnail' => $OUTPUT->pix_url(file_extension_icon($file, 90))->out(false), 139 'icon' => $OUTPUT->pix_url(file_extension_icon($file, 24))->out(false) 140 ); 141 if (file_extension_in_typegroup($file, 'image') && ($imageinfo = @getimagesize($abspath . $file))) { 142 // This means it is an image and we can return dimensions and try to generate thumbnail/icon. 143 $token = $node['datemodified'] . $node['size']; // To prevent caching by browser. 144 $node['realthumbnail'] = $this->get_thumbnail_url($path . '/' . $file, 'thumb', $token)->out(false); 145 $node['realicon'] = $this->get_thumbnail_url($path . '/' . $file, 'icon', $token)->out(false); 146 $node['image_width'] = $imageinfo[0]; 147 $node['image_height'] = $imageinfo[1]; 148 } 149 $list['list'][] = $node; 150 } 151 $list['list'] = array_filter($list['list'], array($this, 'filter')); 152 return $list; 153 } 154 155 156 /** 157 * To check whether the user is logged in. 158 * 159 * @return bool 160 */ 161 public function check_login() { 162 return true; 163 } 164 165 /** 166 * Show the login screen, if required. 167 * 168 * @return string 169 */ 170 public function print_login() { 171 return true; 172 } 173 174 /** 175 * Is it possible to do a global search? 176 * 177 * @return bool 178 */ 179 public function global_search() { 180 return false; 181 } 182 183 /** 184 * Return file path. 185 * @return array 186 */ 187 public function get_file($file, $title = '') { 188 global $CFG; 189 $file = ltrim($file, '/'); 190 if (!$this->is_in_repository($file)) { 191 throw new repository_exception('Invalid file requested.'); 192 } 193 $file = $this->get_rootpath() . $file; 194 195 // This is a hack to prevent move_to_file deleting files in local repository. 196 $CFG->repository_no_delete = true; 197 return array('path' => $file, 'url' => ''); 198 } 199 200 /** 201 * Return the source information 202 * 203 * @param stdClass $filepath 204 * @return string|null 205 */ 206 public function get_file_source_info($filepath) { 207 return $filepath; 208 } 209 210 /** 211 * Logout from repository instance 212 * 213 * @return string 214 */ 215 public function logout() { 216 return true; 217 } 218 219 /** 220 * Return names of the instance options. 221 * 222 * @return array 223 */ 224 public static function get_instance_option_names() { 225 return array('fs_path', 'relativefiles'); 226 } 227 228 /** 229 * Save settings for repository instance 230 * 231 * @param array $options settings 232 * @return bool 233 */ 234 public function set_option($options = array()) { 235 $options['fs_path'] = clean_param($options['fs_path'], PARAM_PATH); 236 $options['relativefiles'] = clean_param($options['relativefiles'], PARAM_INT); 237 $ret = parent::set_option($options); 238 return $ret; 239 } 240 241 /** 242 * Edit/Create Instance Settings Moodle form 243 * 244 * @param moodleform $mform Moodle form (passed by reference) 245 */ 246 public static function instance_config_form($mform) { 247 global $CFG; 248 if (has_capability('moodle/site:config', context_system::instance())) { 249 $path = $CFG->dataroot . '/repository/'; 250 if (!is_dir($path)) { 251 mkdir($path, $CFG->directorypermissions, true); 252 } 253 if ($handle = opendir($path)) { 254 $fieldname = get_string('path', 'repository_filesystem'); 255 $choices = array(); 256 while (false !== ($file = readdir($handle))) { 257 if (is_dir($path . $file) && $file != '.' && $file != '..') { 258 $choices[$file] = $file; 259 $fieldname = ''; 260 } 261 } 262 if (empty($choices)) { 263 $mform->addElement('static', '', '', get_string('nosubdir', 'repository_filesystem', $path)); 264 $mform->addElement('hidden', 'fs_path', ''); 265 $mform->setType('fs_path', PARAM_PATH); 266 } else { 267 $mform->addElement('select', 'fs_path', $fieldname, $choices); 268 $mform->addElement('static', null, '', get_string('information', 'repository_filesystem', $path)); 269 } 270 closedir($handle); 271 } 272 $mform->addElement('checkbox', 'relativefiles', get_string('relativefiles', 'repository_filesystem'), 273 get_string('relativefiles_desc', 'repository_filesystem')); 274 $mform->setType('relativefiles', PARAM_INT); 275 276 } else { 277 $mform->addElement('static', null, '', get_string('nopermissions', 'error', get_string('configplugin', 278 'repository_filesystem'))); 279 return false; 280 } 281 } 282 283 /** 284 * Create an instance for this plug-in 285 * 286 * @static 287 * @param string $type the type of the repository 288 * @param int $userid the user id 289 * @param stdClass $context the context 290 * @param array $params the options for this instance 291 * @param int $readonly whether to create it readonly or not (defaults to not) 292 * @return mixed 293 */ 294 public static function create($type, $userid, $context, $params, $readonly=0) { 295 if (has_capability('moodle/site:config', context_system::instance())) { 296 return parent::create($type, $userid, $context, $params, $readonly); 297 } else { 298 require_capability('moodle/site:config', context_system::instance()); 299 return false; 300 } 301 } 302 303 /** 304 * Validate repository plugin instance form 305 * 306 * @param moodleform $mform moodle form 307 * @param array $data form data 308 * @param array $errors errors 309 * @return array errors 310 */ 311 public static function instance_form_validation($mform, $data, $errors) { 312 $fspath = clean_param(trim($data['fs_path'], '/'), PARAM_PATH); 313 if (empty($fspath) && !is_numeric($fspath)) { 314 $errors['fs_path'] = get_string('invalidadminsettingname', 'error', 'fs_path'); 315 } 316 return $errors; 317 } 318 319 /** 320 * User cannot use the external link to dropbox 321 * 322 * @return int 323 */ 324 public function supported_returntypes() { 325 return FILE_INTERNAL | FILE_REFERENCE; 326 } 327 328 /** 329 * Return human readable reference information 330 * 331 * @param string $reference value of DB field files_reference.reference 332 * @param int $filestatus status of the file, 0 - ok, 666 - source missing 333 * @return string 334 */ 335 public function get_reference_details($reference, $filestatus = 0) { 336 $details = $this->get_name().': '.$reference; 337 if ($filestatus) { 338 return get_string('lostsource', 'repository', $details); 339 } else { 340 return $details; 341 } 342 } 343 344 public function sync_reference(stored_file $file) { 345 if ($file->get_referencelastsync() + 60 > time()) { 346 // Does not cost us much to synchronise within our own filesystem, check every 1 minute. 347 return false; 348 } 349 static $issyncing = false; 350 if ($issyncing) { 351 // Avoid infinite recursion when calling $file->get_filesize() and get_contenthash(). 352 return; 353 } 354 $filepath = $this->get_rootpath() . ltrim($file->get_reference(), '/'); 355 if ($this->is_in_repository($file->get_reference()) && file_exists($filepath) && is_readable($filepath)) { 356 $fs = get_file_storage(); 357 $issyncing = true; 358 if (file_extension_in_typegroup($filepath, 'web_image')) { 359 $contenthash = sha1_file($filepath); 360 if ($file->get_contenthash() == $contenthash) { 361 // File did not change since the last synchronisation. 362 $filesize = filesize($filepath); 363 } else { 364 // Copy file into moodle filepool (used to generate an image thumbnail). 365 list($contenthash, $filesize, $newfile) = $fs->add_file_to_pool($filepath); 366 } 367 } else { 368 // Update only file size so file will NOT be copied into moodle filepool. 369 $contenthash = null; 370 $filesize = filesize($filepath); 371 } 372 $issyncing = false; 373 $file->set_synchronized($contenthash, $filesize); 374 } else { 375 $file->set_missingsource(); 376 } 377 return true; 378 } 379 380 /** 381 * Repository method to serve the referenced file 382 * 383 * @see send_stored_file 384 * 385 * @param stored_file $storedfile the file that contains the reference 386 * @param int $lifetime Number of seconds before the file should expire from caches (null means $CFG->filelifetime) 387 * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only 388 * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin 389 * @param array $options additional options affecting the file serving 390 */ 391 public function send_file($storedfile, $lifetime=null , $filter=0, $forcedownload=false, array $options = null) { 392 $reference = $storedfile->get_reference(); 393 $file = $this->get_rootpath() . ltrim($reference, '/'); 394 if ($this->is_in_repository($reference) && is_readable($file)) { 395 $filename = $storedfile->get_filename(); 396 if ($options && isset($options['filename'])) { 397 $filename = $options['filename']; 398 } 399 $dontdie = ($options && isset($options['dontdie'])); 400 send_file($file, $filename, $lifetime , $filter, false, $forcedownload, '', $dontdie); 401 } else { 402 send_file_not_found(); 403 } 404 } 405 406 /** 407 * Is this repository accessing private data? 408 * 409 * @return bool 410 */ 411 public function contains_private_data() { 412 return false; 413 } 414 415 /** 416 * Return the rootpath of this repository instance. 417 * 418 * Trim() is a necessary step to ensure that the subdirectory is not '/'. 419 * 420 * @return string path 421 * @throws repository_exception If the subdir is unsafe, or invalid. 422 */ 423 public function get_rootpath() { 424 global $CFG; 425 $subdir = clean_param(trim($this->subdir, '/'), PARAM_PATH); 426 $path = $CFG->dataroot . '/repository/' . $this->subdir . '/'; 427 if ((empty($this->subdir) && !is_numeric($this->subdir)) || $subdir != $this->subdir || !is_dir($path)) { 428 throw new repository_exception('The instance is not properly configured, invalid path.'); 429 } 430 return $path; 431 } 432 433 /** 434 * Checks if $path is part of this repository. 435 * 436 * Try to prevent $path hacks such as ../ . 437 * 438 * We do not use clean_param(, PARAM_PATH) here because it also trims down some 439 * characters that are allowed, like < > ' . But we do ensure that the directory 440 * is safe by checking that it starts with $rootpath. 441 * 442 * @param string $path relative path to a file or directory in the repo. 443 * @return boolean false when not. 444 */ 445 protected function is_in_repository($path) { 446 $rootpath = $this->get_rootpath(); 447 if (strpos(realpath($rootpath . $path), realpath($rootpath)) !== 0) { 448 return false; 449 } 450 return true; 451 } 452 453 /** 454 * Returns url of thumbnail file. 455 * 456 * @param string $filepath current path in repository (dir and filename) 457 * @param string $thumbsize 'thumb' or 'icon' 458 * @param string $token identifier of the file contents - to prevent browser from caching changed file 459 * @return moodle_url 460 */ 461 protected function get_thumbnail_url($filepath, $thumbsize, $token) { 462 return moodle_url::make_pluginfile_url($this->context->id, 'repository_filesystem', $thumbsize, $this->id, 463 '/' . trim($filepath, '/') . '/', $token); 464 } 465 466 /** 467 * Returns the stored thumbnail file, generates it if not present. 468 * 469 * @param string $filepath current path in repository (dir and filename) 470 * @param string $thumbsize 'thumb' or 'icon' 471 * @return null|stored_file 472 */ 473 public function get_thumbnail($filepath, $thumbsize) { 474 global $CFG; 475 476 $filepath = trim($filepath, '/'); 477 $origfile = $this->get_rootpath() . $filepath; 478 // As thumbnail filename we use original file content hash. 479 if (!$this->is_in_repository($filepath) || !($filecontents = @file_get_contents($origfile))) { 480 // File is not found or is not readable. 481 return null; 482 } 483 $filename = sha1($filecontents); 484 unset($filecontents); 485 486 // Try to get generated thumbnail for this file. 487 $fs = get_file_storage(); 488 if (!($file = $fs->get_file(SYSCONTEXTID, 'repository_filesystem', $thumbsize, $this->id, '/' . $filepath . '/', 489 $filename))) { 490 // Thumbnail not found . Generate and store thumbnail. 491 require_once($CFG->libdir . '/gdlib.php'); 492 if ($thumbsize === 'thumb') { 493 $size = 90; 494 } else { 495 $size = 24; 496 } 497 if (!$data = @generate_image_thumbnail($origfile, $size, $size)) { 498 // Generation failed. 499 return null; 500 } 501 $record = array( 502 'contextid' => SYSCONTEXTID, 503 'component' => 'repository_filesystem', 504 'filearea' => $thumbsize, 505 'itemid' => $this->id, 506 'filepath' => '/' . $filepath . '/', 507 'filename' => $filename, 508 ); 509 $file = $fs->create_file_from_string($record, $data); 510 } 511 return $file; 512 } 513 514 /** 515 * Run in cron for particular repository instance. Removes thumbnails for deleted/modified files. 516 * 517 * @param stored_file[] $storedfiles 518 */ 519 public function remove_obsolete_thumbnails($storedfiles) { 520 // Group found files by filepath ('filepath' in Moodle file storage is dir+name in filesystem repository). 521 $files = array(); 522 foreach ($storedfiles as $file) { 523 if (!isset($files[$file->get_filepath()])) { 524 $files[$file->get_filepath()] = array(); 525 } 526 $files[$file->get_filepath()][] = $file; 527 } 528 529 // Loop through all files and make sure the original exists and has the same contenthash. 530 $deletedcount = 0; 531 foreach ($files as $filepath => $filesinpath) { 532 if ($filecontents = @file_get_contents($this->get_rootpath() . trim($filepath, '/'))) { 533 // The 'filename' in Moodle file storage is contenthash of the file in filesystem repository. 534 $filename = sha1($filecontents); 535 foreach ($filesinpath as $file) { 536 if ($file->get_filename() !== $filename && $file->get_filename() !== '.') { 537 // Contenthash does not match, this is an old thumbnail. 538 $deletedcount++; 539 $file->delete(); 540 } 541 } 542 } else { 543 // Thumbnail exist but file not. 544 foreach ($filesinpath as $file) { 545 if ($file->get_filename() !== '.') { 546 $deletedcount++; 547 } 548 $file->delete(); 549 } 550 } 551 } 552 if ($deletedcount) { 553 mtrace(" instance {$this->id}: deleted $deletedcount thumbnails"); 554 } 555 } 556 557 /** 558 * Gets a file relative to this file in the repository and sends it to the browser. 559 * 560 * @param stored_file $mainfile The main file we are trying to access relative files for. 561 * @param string $relativepath the relative path to the file we are trying to access. 562 */ 563 public function send_relative_file(stored_file $mainfile, $relativepath) { 564 global $CFG; 565 // Check if this repository is allowed to use relative linking. 566 $allowlinks = $this->supports_relative_file(); 567 if (!empty($allowlinks)) { 568 // Get path to the mainfile. 569 $mainfilepath = $mainfile->get_source(); 570 571 // Strip out filename from the path. 572 $filename = $mainfile->get_filename(); 573 $basepath = strstr($mainfilepath, $filename, true); 574 575 $fullrelativefilepath = realpath($this->get_rootpath().$basepath.$relativepath); 576 577 // Sanity check to make sure this path is inside this repository and the file exists. 578 if (strpos($fullrelativefilepath, $this->get_rootpath()) === 0 && file_exists($fullrelativefilepath)) { 579 send_file($fullrelativefilepath, basename($relativepath), null, 0); 580 } 581 } 582 send_file_not_found(); 583 } 584 585 /** 586 * helper function to check if the repository supports send_relative_file. 587 * 588 * @return true|false 589 */ 590 public function supports_relative_file() { 591 return $this->get_option('relativefiles'); 592 } 593 } 594 595 /** 596 * Generates and sends the thumbnail for an image in filesystem. 597 * 598 * @param stdClass $course course object 599 * @param stdClass $cm course module object 600 * @param stdClass $context context object 601 * @param string $filearea file area 602 * @param array $args extra arguments 603 * @param bool $forcedownload whether or not force download 604 * @param array $options additional options affecting the file serving 605 * @return bool 606 */ 607 function repository_filesystem_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) { 608 global $OUTPUT, $CFG; 609 // Allowed filearea is either thumb or icon - size of the thumbnail. 610 if ($filearea !== 'thumb' && $filearea !== 'icon') { 611 return false; 612 } 613 614 // As itemid we pass repository instance id. 615 $itemid = array_shift($args); 616 // Filename is some token that we can ignore (used only to make sure browser does not serve cached copy when file is changed). 617 array_pop($args); 618 // As filepath we use full filepath (dir+name) of the file in this instance of filesystem repository. 619 $filepath = implode('/', $args); 620 621 // Make sure file exists in the repository and is accessible. 622 $repo = repository::get_repository_by_id($itemid, $context); 623 $repo->check_capability(); 624 // Find stored or generated thumbnail. 625 if (!($file = $repo->get_thumbnail($filepath, $filearea))) { 626 // Generation failed, redirect to default icon for file extension. 627 redirect($OUTPUT->pix_url(file_extension_icon($file, 90))); 628 } 629 // The thumbnails should not be changing much, but maybe the default lifetime is too long. 630 $lifetime = $CFG->filelifetime; 631 if ($lifetime > 60*10) { 632 $lifetime = 60*10; 633 } 634 send_stored_file($file, $lifetime, 0, $forcedownload, $options); 635 } 636 637 /** 638 * Cron callback for repository_filesystem. Deletes the thumbnails for deleted or changed files. 639 */ 640 function repository_filesystem_cron() { 641 $fs = get_file_storage(); 642 // Find all generated thumbnails and group them in array by itemid (itemid == repository instance id). 643 $allfiles = array_merge( 644 $fs->get_area_files(SYSCONTEXTID, 'repository_filesystem', 'thumb'), 645 $fs->get_area_files(SYSCONTEXTID, 'repository_filesystem', 'icon') 646 ); 647 $filesbyitem = array(); 648 foreach ($allfiles as $file) { 649 if (!isset($filesbyitem[$file->get_itemid()])) { 650 $filesbyitem[$file->get_itemid()] = array(); 651 } 652 $filesbyitem[$file->get_itemid()][] = $file; 653 } 654 // Find all instances of repository_filesystem. 655 $instances = repository::get_instances(array('type' => 'filesystem')); 656 // Loop through all itemids of generated thumbnails. 657 foreach ($filesbyitem as $itemid => $files) { 658 if (!isset($instances[$itemid]) || !($instances[$itemid] instanceof repository_filesystem)) { 659 // Instance was deleted. 660 $fs->delete_area_files(SYSCONTEXTID, 'repository_filesystem', 'thumb', $itemid); 661 $fs->delete_area_files(SYSCONTEXTID, 'repository_filesystem', 'icon', $itemid); 662 mtrace(" instance $itemid does not exist: deleted all thumbnails"); 663 } else { 664 // Instance has some generated thumbnails, check that they are not outdated. 665 $instances[$itemid]->remove_obsolete_thumbnails($files); 666 } 667 } 668 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 20:29:05 2014 | Cross-referenced by PHPXref 0.7.1 |