[ 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 * 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 $fullpath Path to explore. This is assembled by {@link self::build_node_path()}. 64 * @param string $page Page number. 65 * @return array List of files and folders. 66 */ 67 public function get_listing($fullpath = '', $page = '') { 68 global $OUTPUT; 69 70 $list = array( 71 'list' => array(), 72 'manage' => false, 73 'dynload' => true, 74 'nologin' => true, 75 'path' => array() 76 ); 77 78 // We analyse the path to extract what to browse. 79 $fullpath = empty($fullpath) ? $this->build_node_path('root') : $fullpath; 80 $trail = explode('|', $fullpath); 81 $trail = array_pop($trail); 82 list($mode, $path, $unused) = $this->explode_node_path($trail); 83 84 // Is that a search? 85 if ($mode === 'search') { 86 return $this->search($path, $page); 87 } 88 89 // Cleaning up the requested path. 90 $path = trim($path, '/'); 91 if (!$this->is_in_repository($path)) { 92 // In case of doubt on the path, reset to default. 93 $path = ''; 94 } 95 $rootpath = $this->get_rootpath(); 96 $abspath = rtrim($rootpath . $path, '/') . '/'; 97 98 // Retrieve list of files and directories and sort them. 99 $fileslist = array(); 100 $dirslist = array(); 101 if ($dh = opendir($abspath)) { 102 while (($file = readdir($dh)) != false) { 103 if ($file != '.' and $file != '..') { 104 if (is_file($abspath . $file)) { 105 $fileslist[] = $file; 106 } else { 107 $dirslist[] = $file; 108 } 109 } 110 } 111 } 112 core_collator::asort($fileslist, core_collator::SORT_NATURAL); 113 core_collator::asort($dirslist, core_collator::SORT_NATURAL); 114 115 // Fill the results. 116 foreach ($dirslist as $file) { 117 $list['list'][] = $this->build_node($rootpath, $path, $file, true, $fullpath); 118 } 119 foreach ($fileslist as $file) { 120 $list['list'][] = $this->build_node($rootpath, $path, $file, false, $fullpath); 121 } 122 123 $list['path'] = $this->build_breadcrumb($fullpath); 124 $list['list'] = array_filter($list['list'], array($this, 'filter')); 125 126 return $list; 127 } 128 129 /** 130 * Search files in repository. 131 * 132 * This search works by walking through the directories returning the files that match. Once 133 * the limit of files is reached the walk stops. Whenever more files are requested, the walk 134 * starts from the beginning until it reaches an additional set of files to return. 135 * 136 * @param string $query The query string. 137 * @param int $page The page number. 138 * @return mixed 139 */ 140 public function search($query, $page = 1) { 141 global $OUTPUT, $SESSION; 142 143 $query = core_text::strtolower($query); 144 $remainingdirs = 1000; 145 $remainingobjects = 5000; 146 $perpage = 50; 147 148 // Because the repository API is weird, the first page is 0, but it should be 1. 149 if (!$page) { 150 $page = 1; 151 } 152 153 // Initialise the session variable in which we store the search related things. 154 if (!isset($SESSION->repository_filesystem_search)) { 155 $SESSION->repository_filesystem_search = array(); 156 } 157 158 // Restore, or initialise the session search variables. 159 if ($page <= 1) { 160 $SESSION->repository_filesystem_search['query'] = $query; 161 $SESSION->repository_filesystem_search['from'] = 0; 162 $from = 0; 163 } else { 164 // Yes, the repository does not send the query again... 165 $query = $SESSION->repository_filesystem_search['query']; 166 $from = (int) $SESSION->repository_filesystem_search['from']; 167 } 168 $limit = $from + $perpage; 169 $searchpath = $this->build_node_path('search', $query); 170 171 // Pre-search initialisation. 172 $rootpath = $this->get_rootpath(); 173 $found = 0; 174 $toexplore = array(''); 175 176 // Retrieve list of matching files and directories. 177 $matches = array(); 178 while (($path = array_shift($toexplore)) !== null) { 179 $remainingdirs--; 180 181 if ($objects = scandir($rootpath . $path)) { 182 foreach ($objects as $object) { 183 $objectabspath = $rootpath . $path . $object; 184 if ($object == '.' || $object == '..') { 185 continue; 186 } 187 188 $remainingobjects--; 189 $isdir = is_dir($objectabspath); 190 191 // It is a match! 192 if (strpos(core_text::strtolower($object), $query) !== false) { 193 $found++; 194 $matches[] = array($path, $object, $isdir); 195 196 // That's enough, no need to find more. 197 if ($found >= $limit) { 198 break 2; 199 } 200 } 201 202 // I've seen enough files, I give up! 203 if ($remainingobjects <= 0) { 204 break 2; 205 } 206 207 // Add the directory to things to explore later. 208 if ($isdir) { 209 $toexplore[] = $path . trim($object, '/') . '/'; 210 } 211 } 212 } 213 214 if ($remainingdirs <= 0) { 215 break; 216 } 217 } 218 219 // Extract the results from all the matches. 220 $matches = array_slice($matches, $from, $perpage); 221 222 // If we didn't reach our limits of browsing, and we appear to still have files to find. 223 if ($remainingdirs > 0 && $remainingobjects > 0 && count($matches) >= $perpage) { 224 $SESSION->repository_filesystem_search['from'] = $limit; 225 $pages = -1; 226 227 // We reached the end of the repository, or our limits. 228 } else { 229 $SESSION->repository_filesystem_search['from'] = 0; 230 $pages = 0; 231 } 232 233 // Organise the nodes. 234 $results = array(); 235 foreach ($matches as $match) { 236 list($path, $name, $isdir) = $match; 237 $results[] = $this->build_node($rootpath, $path, $name, $isdir, $searchpath); 238 } 239 240 $list = array(); 241 $list['list'] = array_filter($results, array($this, 'filter')); 242 $list['dynload'] = true; 243 $list['nologin'] = true; 244 $list['page'] = $page; 245 $list['pages'] = $pages; 246 $list['path'] = $this->build_breadcrumb($searchpath); 247 248 return $list; 249 } 250 251 /** 252 * Build the breadcrumb from a full path. 253 * 254 * @param string $path A path generated by {@link self::build_node_path()}. 255 * @return array 256 */ 257 protected function build_breadcrumb($path) { 258 $breadcrumb = array(array( 259 'name' => get_string('root', 'repository_filesystem'), 260 'path' => $this->build_node_path('root') 261 )); 262 263 $crumbs = explode('|', $path); 264 $trail = ''; 265 266 foreach ($crumbs as $crumb) { 267 list($mode, $nodepath, $display) = $this->explode_node_path($crumb); 268 switch ($mode) { 269 case 'search': 270 $breadcrumb[] = array( 271 'name' => get_string('searchresults', 'repository_filesystem'), 272 'path' => $this->build_node_path($mode, $nodepath, $display, $trail), 273 ); 274 break; 275 276 case 'browse': 277 $breadcrumb[] = array( 278 'name' => $display, 279 'path' => $this->build_node_path($mode, $nodepath, $display, $trail), 280 ); 281 break; 282 } 283 284 $lastcrumb = end($breadcrumb); 285 $trail = $lastcrumb['path']; 286 } 287 288 return $breadcrumb; 289 } 290 291 /** 292 * Build a file or directory node. 293 * 294 * @param string $rootpath The absolute path to the repository. 295 * @param string $path The relative path of the object 296 * @param string $name The name of the object 297 * @param string $isdir Is the object a directory? 298 * @param string $rootnodepath The node leading to this node (for breadcrumb). 299 * @return array 300 */ 301 protected function build_node($rootpath, $path, $name, $isdir, $rootnodepath) { 302 global $OUTPUT; 303 304 $relpath = trim($path, '/') . '/' . $name; 305 $abspath = $rootpath . $relpath; 306 $node = array( 307 'title' => $name, 308 'datecreated' => filectime($abspath), 309 'datemodified' => filemtime($abspath), 310 ); 311 312 if ($isdir) { 313 $node['children'] = array(); 314 $node['thumbnail'] = $OUTPUT->pix_url(file_folder_icon(90))->out(false); 315 $node['path'] = $this->build_node_path('browse', $relpath, $name, $rootnodepath); 316 317 } else { 318 $node['source'] = $relpath; 319 $node['size'] = filesize($abspath); 320 $node['thumbnail'] = $OUTPUT->pix_url(file_extension_icon($name, 90))->out(false); 321 $node['icon'] = $OUTPUT->pix_url(file_extension_icon($name, 24))->out(false); 322 $node['path'] = $relpath; 323 324 if (file_extension_in_typegroup($name, 'image') && ($imageinfo = @getimagesize($abspath))) { 325 // This means it is an image and we can return dimensions and try to generate thumbnail/icon. 326 $token = $node['datemodified'] . $node['size']; // To prevent caching by browser. 327 $node['realthumbnail'] = $this->get_thumbnail_url($relpath, 'thumb', $token)->out(false); 328 $node['realicon'] = $this->get_thumbnail_url($relpath, 'icon', $token)->out(false); 329 $node['image_width'] = $imageinfo[0]; 330 $node['image_height'] = $imageinfo[1]; 331 } 332 } 333 334 return $node; 335 } 336 337 /** 338 * Build the path to a browsable node. 339 * 340 * @param string $mode The type of browse mode. 341 * @param string $realpath The path, or similar. 342 * @param string $display The way to display the node. 343 * @param string $root The path preceding this node. 344 * @return string 345 */ 346 protected function build_node_path($mode, $realpath = '', $display = '', $root = '') { 347 $path = $mode . ':' . base64_encode($realpath) . ':' . base64_encode($display); 348 if (!empty($root)) { 349 $path = $root . '|' . $path; 350 } 351 return $path; 352 } 353 354 /** 355 * Extract information from a node path. 356 * 357 * Note, this should not include preceding paths. 358 * 359 * @param string $path The path of the node. 360 * @return array Contains the mode, the relative path, and the display text. 361 */ 362 protected function explode_node_path($path) { 363 list($mode, $realpath, $display) = explode(':', $path); 364 return array( 365 $mode, 366 base64_decode($realpath), 367 base64_decode($display) 368 ); 369 } 370 371 /** 372 * To check whether the user is logged in. 373 * 374 * @return bool 375 */ 376 public function check_login() { 377 return true; 378 } 379 380 /** 381 * Show the login screen, if required. 382 * 383 * @return string 384 */ 385 public function print_login() { 386 return true; 387 } 388 389 /** 390 * Is it possible to do a global search? 391 * 392 * @return bool 393 */ 394 public function global_search() { 395 return false; 396 } 397 398 /** 399 * Return file path. 400 * @return array 401 */ 402 public function get_file($file, $title = '') { 403 global $CFG; 404 $file = ltrim($file, '/'); 405 if (!$this->is_in_repository($file)) { 406 throw new repository_exception('Invalid file requested.'); 407 } 408 $file = $this->get_rootpath() . $file; 409 410 // This is a hack to prevent move_to_file deleting files in local repository. 411 $CFG->repository_no_delete = true; 412 return array('path' => $file, 'url' => ''); 413 } 414 415 /** 416 * Return the source information 417 * 418 * @param stdClass $filepath 419 * @return string|null 420 */ 421 public function get_file_source_info($filepath) { 422 return $filepath; 423 } 424 425 /** 426 * Logout from repository instance 427 * 428 * @return string 429 */ 430 public function logout() { 431 return true; 432 } 433 434 /** 435 * Return names of the instance options. 436 * 437 * @return array 438 */ 439 public static function get_instance_option_names() { 440 return array('fs_path', 'relativefiles'); 441 } 442 443 /** 444 * Save settings for repository instance 445 * 446 * @param array $options settings 447 * @return bool 448 */ 449 public function set_option($options = array()) { 450 $options['fs_path'] = clean_param($options['fs_path'], PARAM_PATH); 451 $options['relativefiles'] = clean_param($options['relativefiles'], PARAM_INT); 452 $ret = parent::set_option($options); 453 return $ret; 454 } 455 456 /** 457 * Edit/Create Instance Settings Moodle form 458 * 459 * @param moodleform $mform Moodle form (passed by reference) 460 */ 461 public static function instance_config_form($mform) { 462 global $CFG; 463 if (has_capability('moodle/site:config', context_system::instance())) { 464 $path = $CFG->dataroot . '/repository/'; 465 if (!is_dir($path)) { 466 mkdir($path, $CFG->directorypermissions, true); 467 } 468 if ($handle = opendir($path)) { 469 $fieldname = get_string('path', 'repository_filesystem'); 470 $choices = array(); 471 while (false !== ($file = readdir($handle))) { 472 if (is_dir($path . $file) && $file != '.' && $file != '..') { 473 $choices[$file] = $file; 474 $fieldname = ''; 475 } 476 } 477 if (empty($choices)) { 478 $mform->addElement('static', '', '', get_string('nosubdir', 'repository_filesystem', $path)); 479 $mform->addElement('hidden', 'fs_path', ''); 480 $mform->setType('fs_path', PARAM_PATH); 481 } else { 482 $mform->addElement('select', 'fs_path', $fieldname, $choices); 483 $mform->addElement('static', null, '', get_string('information', 'repository_filesystem', $path)); 484 } 485 closedir($handle); 486 } 487 $mform->addElement('checkbox', 'relativefiles', get_string('relativefiles', 'repository_filesystem'), 488 get_string('relativefiles_desc', 'repository_filesystem')); 489 $mform->setType('relativefiles', PARAM_INT); 490 491 } else { 492 $mform->addElement('static', null, '', get_string('nopermissions', 'error', get_string('configplugin', 493 'repository_filesystem'))); 494 return false; 495 } 496 } 497 498 /** 499 * Create an instance for this plug-in 500 * 501 * @static 502 * @param string $type the type of the repository 503 * @param int $userid the user id 504 * @param stdClass $context the context 505 * @param array $params the options for this instance 506 * @param int $readonly whether to create it readonly or not (defaults to not) 507 * @return mixed 508 */ 509 public static function create($type, $userid, $context, $params, $readonly=0) { 510 if (has_capability('moodle/site:config', context_system::instance())) { 511 return parent::create($type, $userid, $context, $params, $readonly); 512 } else { 513 require_capability('moodle/site:config', context_system::instance()); 514 return false; 515 } 516 } 517 518 /** 519 * Validate repository plugin instance form 520 * 521 * @param moodleform $mform moodle form 522 * @param array $data form data 523 * @param array $errors errors 524 * @return array errors 525 */ 526 public static function instance_form_validation($mform, $data, $errors) { 527 $fspath = clean_param(trim($data['fs_path'], '/'), PARAM_PATH); 528 if (empty($fspath) && !is_numeric($fspath)) { 529 $errors['fs_path'] = get_string('invalidadminsettingname', 'error', 'fs_path'); 530 } 531 return $errors; 532 } 533 534 /** 535 * User cannot use the external link to dropbox 536 * 537 * @return int 538 */ 539 public function supported_returntypes() { 540 return FILE_INTERNAL | FILE_REFERENCE; 541 } 542 543 /** 544 * Return human readable reference information 545 * 546 * @param string $reference value of DB field files_reference.reference 547 * @param int $filestatus status of the file, 0 - ok, 666 - source missing 548 * @return string 549 */ 550 public function get_reference_details($reference, $filestatus = 0) { 551 $details = $this->get_name().': '.$reference; 552 if ($filestatus) { 553 return get_string('lostsource', 'repository', $details); 554 } else { 555 return $details; 556 } 557 } 558 559 public function sync_reference(stored_file $file) { 560 if ($file->get_referencelastsync() + 60 > time()) { 561 // Does not cost us much to synchronise within our own filesystem, check every 1 minute. 562 return false; 563 } 564 static $issyncing = false; 565 if ($issyncing) { 566 // Avoid infinite recursion when calling $file->get_filesize() and get_contenthash(). 567 return false; 568 } 569 $filepath = $this->get_rootpath() . ltrim($file->get_reference(), '/'); 570 if ($this->is_in_repository($file->get_reference()) && file_exists($filepath) && is_readable($filepath)) { 571 $fs = get_file_storage(); 572 $issyncing = true; 573 if (file_extension_in_typegroup($filepath, 'web_image')) { 574 $contenthash = sha1_file($filepath); 575 if ($file->get_contenthash() == $contenthash) { 576 // File did not change since the last synchronisation. 577 $filesize = filesize($filepath); 578 } else { 579 // Copy file into moodle filepool (used to generate an image thumbnail). 580 list($contenthash, $filesize, $newfile) = $fs->add_file_to_pool($filepath); 581 } 582 } else { 583 // Update only file size so file will NOT be copied into moodle filepool. 584 $emptyfile = $contenthash = sha1(''); 585 $currentcontenthash = $file->get_contenthash(); 586 if ($currentcontenthash !== $emptyfile && $currentcontenthash === sha1_file($filepath)) { 587 // File content was synchronised and has not changed since then, leave it. 588 $contenthash = null; 589 } 590 $filesize = filesize($filepath); 591 } 592 $issyncing = false; 593 $modified = filemtime($filepath); 594 $file->set_synchronized($contenthash, $filesize, 0, $modified); 595 } else { 596 $file->set_missingsource(); 597 } 598 return true; 599 } 600 601 /** 602 * Repository method to serve the referenced file 603 * 604 * @see send_stored_file 605 * 606 * @param stored_file $storedfile the file that contains the reference 607 * @param int $lifetime Number of seconds before the file should expire from caches (null means $CFG->filelifetime) 608 * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only 609 * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin 610 * @param array $options additional options affecting the file serving 611 */ 612 public function send_file($storedfile, $lifetime=null , $filter=0, $forcedownload=false, array $options = null) { 613 $reference = $storedfile->get_reference(); 614 $file = $this->get_rootpath() . ltrim($reference, '/'); 615 if ($this->is_in_repository($reference) && is_readable($file)) { 616 $filename = $storedfile->get_filename(); 617 if ($options && isset($options['filename'])) { 618 $filename = $options['filename']; 619 } 620 $dontdie = ($options && isset($options['dontdie'])); 621 send_file($file, $filename, $lifetime , $filter, false, $forcedownload, '', $dontdie); 622 } else { 623 send_file_not_found(); 624 } 625 } 626 627 /** 628 * Is this repository accessing private data? 629 * 630 * @return bool 631 */ 632 public function contains_private_data() { 633 return false; 634 } 635 636 /** 637 * Return the rootpath of this repository instance. 638 * 639 * Trim() is a necessary step to ensure that the subdirectory is not '/'. 640 * 641 * @return string path 642 * @throws repository_exception If the subdir is unsafe, or invalid. 643 */ 644 public function get_rootpath() { 645 global $CFG; 646 $subdir = clean_param(trim($this->subdir, '/'), PARAM_PATH); 647 $path = $CFG->dataroot . '/repository/' . $this->subdir . '/'; 648 if ((empty($this->subdir) && !is_numeric($this->subdir)) || $subdir != $this->subdir || !is_dir($path)) { 649 throw new repository_exception('The instance is not properly configured, invalid path.'); 650 } 651 return $path; 652 } 653 654 /** 655 * Checks if $path is part of this repository. 656 * 657 * Try to prevent $path hacks such as ../ . 658 * 659 * We do not use clean_param(, PARAM_PATH) here because it also trims down some 660 * characters that are allowed, like < > ' . But we do ensure that the directory 661 * is safe by checking that it starts with $rootpath. 662 * 663 * @param string $path relative path to a file or directory in the repo. 664 * @return boolean false when not. 665 */ 666 protected function is_in_repository($path) { 667 $rootpath = $this->get_rootpath(); 668 if (strpos(realpath($rootpath . $path), realpath($rootpath)) !== 0) { 669 return false; 670 } 671 return true; 672 } 673 674 /** 675 * Returns url of thumbnail file. 676 * 677 * @param string $filepath current path in repository (dir and filename) 678 * @param string $thumbsize 'thumb' or 'icon' 679 * @param string $token identifier of the file contents - to prevent browser from caching changed file 680 * @return moodle_url 681 */ 682 protected function get_thumbnail_url($filepath, $thumbsize, $token) { 683 return moodle_url::make_pluginfile_url($this->context->id, 'repository_filesystem', $thumbsize, $this->id, 684 '/' . trim($filepath, '/') . '/', $token); 685 } 686 687 /** 688 * Returns the stored thumbnail file, generates it if not present. 689 * 690 * @param string $filepath current path in repository (dir and filename) 691 * @param string $thumbsize 'thumb' or 'icon' 692 * @return null|stored_file 693 */ 694 public function get_thumbnail($filepath, $thumbsize) { 695 global $CFG; 696 697 $filepath = trim($filepath, '/'); 698 $origfile = $this->get_rootpath() . $filepath; 699 // As thumbnail filename we use original file content hash. 700 if (!$this->is_in_repository($filepath) || !($filecontents = @file_get_contents($origfile))) { 701 // File is not found or is not readable. 702 return null; 703 } 704 $filename = sha1($filecontents); 705 706 // Try to get generated thumbnail for this file. 707 $fs = get_file_storage(); 708 if (!($file = $fs->get_file(SYSCONTEXTID, 'repository_filesystem', $thumbsize, $this->id, '/' . $filepath . '/', 709 $filename))) { 710 // Thumbnail not found . Generate and store thumbnail. 711 require_once($CFG->libdir . '/gdlib.php'); 712 if ($thumbsize === 'thumb') { 713 $size = 90; 714 } else { 715 $size = 24; 716 } 717 if (!$data = generate_image_thumbnail_from_string($filecontents, $size, $size)) { 718 // Generation failed. 719 return null; 720 } 721 $record = array( 722 'contextid' => SYSCONTEXTID, 723 'component' => 'repository_filesystem', 724 'filearea' => $thumbsize, 725 'itemid' => $this->id, 726 'filepath' => '/' . $filepath . '/', 727 'filename' => $filename, 728 ); 729 $file = $fs->create_file_from_string($record, $data); 730 } 731 return $file; 732 } 733 734 /** 735 * Run in cron for particular repository instance. Removes thumbnails for deleted/modified files. 736 * 737 * @param stored_file[] $storedfiles 738 */ 739 public function remove_obsolete_thumbnails($storedfiles) { 740 // Group found files by filepath ('filepath' in Moodle file storage is dir+name in filesystem repository). 741 $files = array(); 742 foreach ($storedfiles as $file) { 743 if (!isset($files[$file->get_filepath()])) { 744 $files[$file->get_filepath()] = array(); 745 } 746 $files[$file->get_filepath()][] = $file; 747 } 748 749 // Loop through all files and make sure the original exists and has the same contenthash. 750 $deletedcount = 0; 751 foreach ($files as $filepath => $filesinpath) { 752 if ($filecontents = @file_get_contents($this->get_rootpath() . trim($filepath, '/'))) { 753 // The 'filename' in Moodle file storage is contenthash of the file in filesystem repository. 754 $filename = sha1($filecontents); 755 foreach ($filesinpath as $file) { 756 if ($file->get_filename() !== $filename && $file->get_filename() !== '.') { 757 // Contenthash does not match, this is an old thumbnail. 758 $deletedcount++; 759 $file->delete(); 760 } 761 } 762 } else { 763 // Thumbnail exist but file not. 764 foreach ($filesinpath as $file) { 765 if ($file->get_filename() !== '.') { 766 $deletedcount++; 767 } 768 $file->delete(); 769 } 770 } 771 } 772 if ($deletedcount) { 773 mtrace(" instance {$this->id}: deleted $deletedcount thumbnails"); 774 } 775 } 776 777 /** 778 * Gets a file relative to this file in the repository and sends it to the browser. 779 * 780 * @param stored_file $mainfile The main file we are trying to access relative files for. 781 * @param string $relativepath the relative path to the file we are trying to access. 782 */ 783 public function send_relative_file(stored_file $mainfile, $relativepath) { 784 global $CFG; 785 // Check if this repository is allowed to use relative linking. 786 $allowlinks = $this->supports_relative_file(); 787 if (!empty($allowlinks)) { 788 // Get path to the mainfile. 789 $mainfilepath = $mainfile->get_source(); 790 791 // Strip out filename from the path. 792 $filename = $mainfile->get_filename(); 793 $basepath = strstr($mainfilepath, $filename, true); 794 795 $fullrelativefilepath = realpath($this->get_rootpath().$basepath.$relativepath); 796 797 // Sanity check to make sure this path is inside this repository and the file exists. 798 if (strpos($fullrelativefilepath, realpath($this->get_rootpath())) === 0 && file_exists($fullrelativefilepath)) { 799 send_file($fullrelativefilepath, basename($relativepath), null, 0); 800 } 801 } 802 send_file_not_found(); 803 } 804 805 /** 806 * helper function to check if the repository supports send_relative_file. 807 * 808 * @return true|false 809 */ 810 public function supports_relative_file() { 811 return $this->get_option('relativefiles'); 812 } 813 } 814 815 /** 816 * Generates and sends the thumbnail for an image in filesystem. 817 * 818 * @param stdClass $course course object 819 * @param stdClass $cm course module object 820 * @param stdClass $context context object 821 * @param string $filearea file area 822 * @param array $args extra arguments 823 * @param bool $forcedownload whether or not force download 824 * @param array $options additional options affecting the file serving 825 * @return bool 826 */ 827 function repository_filesystem_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) { 828 global $OUTPUT, $CFG; 829 // Allowed filearea is either thumb or icon - size of the thumbnail. 830 if ($filearea !== 'thumb' && $filearea !== 'icon') { 831 return false; 832 } 833 834 // As itemid we pass repository instance id. 835 $itemid = array_shift($args); 836 // Filename is some token that we can ignore (used only to make sure browser does not serve cached copy when file is changed). 837 array_pop($args); 838 // As filepath we use full filepath (dir+name) of the file in this instance of filesystem repository. 839 $filepath = implode('/', $args); 840 841 // Make sure file exists in the repository and is accessible. 842 $repo = repository::get_repository_by_id($itemid, $context); 843 $repo->check_capability(); 844 // Find stored or generated thumbnail. 845 if (!($file = $repo->get_thumbnail($filepath, $filearea))) { 846 // Generation failed, redirect to default icon for file extension. 847 redirect($OUTPUT->pix_url(file_extension_icon($file, 90))); 848 } 849 // The thumbnails should not be changing much, but maybe the default lifetime is too long. 850 $lifetime = $CFG->filelifetime; 851 if ($lifetime > 60*10) { 852 $lifetime = 60*10; 853 } 854 send_stored_file($file, $lifetime, 0, $forcedownload, $options); 855 } 856 857 /** 858 * Cron callback for repository_filesystem. Deletes the thumbnails for deleted or changed files. 859 */ 860 function repository_filesystem_cron() { 861 $fs = get_file_storage(); 862 // Find all generated thumbnails and group them in array by itemid (itemid == repository instance id). 863 $allfiles = array_merge( 864 $fs->get_area_files(SYSCONTEXTID, 'repository_filesystem', 'thumb'), 865 $fs->get_area_files(SYSCONTEXTID, 'repository_filesystem', 'icon') 866 ); 867 $filesbyitem = array(); 868 foreach ($allfiles as $file) { 869 if (!isset($filesbyitem[$file->get_itemid()])) { 870 $filesbyitem[$file->get_itemid()] = array(); 871 } 872 $filesbyitem[$file->get_itemid()][] = $file; 873 } 874 // Find all instances of repository_filesystem. 875 $instances = repository::get_instances(array('type' => 'filesystem')); 876 // Loop through all itemids of generated thumbnails. 877 foreach ($filesbyitem as $itemid => $files) { 878 if (!isset($instances[$itemid]) || !($instances[$itemid] instanceof repository_filesystem)) { 879 // Instance was deleted. 880 $fs->delete_area_files(SYSCONTEXTID, 'repository_filesystem', 'thumb', $itemid); 881 $fs->delete_area_files(SYSCONTEXTID, 'repository_filesystem', 'icon', $itemid); 882 mtrace(" instance $itemid does not exist: deleted all thumbnails"); 883 } else { 884 // Instance has some generated thumbnails, check that they are not outdated. 885 $instances[$itemid]->remove_obsolete_thumbnails($files); 886 } 887 } 888 }
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 |