[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/repository/filesystem/ -> lib.php (source)

   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  }


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1