[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

/repository/ -> 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 file contains classes used to manage the repository plugins in Moodle
  19   *
  20   * @since Moodle 2.0
  21   * @package   core_repository
  22   * @copyright 2009 Dongsheng Cai {@link http://dongsheng.org}
  23   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  defined('MOODLE_INTERNAL') || die();
  27  require_once($CFG->libdir . '/filelib.php');
  28  require_once($CFG->libdir . '/formslib.php');
  29  
  30  define('FILE_EXTERNAL',  1);
  31  define('FILE_INTERNAL',  2);
  32  define('FILE_REFERENCE', 4);
  33  define('RENAME_SUFFIX', '_2');
  34  
  35  /**
  36   * This class is used to manage repository plugins
  37   *
  38   * A repository_type is a repository plug-in. It can be Box.net, Flick-r, ...
  39   * A repository type can be edited, sorted and hidden. It is mandatory for an
  40   * administrator to create a repository type in order to be able to create
  41   * some instances of this type.
  42   * Coding note:
  43   * - a repository_type object is mapped to the "repository" database table
  44   * - "typename" attibut maps the "type" database field. It is unique.
  45   * - general "options" for a repository type are saved in the config_plugin table
  46   * - when you delete a repository, all instances are deleted, and general
  47   *   options are also deleted from database
  48   * - When you create a type for a plugin that can't have multiple instances, a
  49   *   instance is automatically created.
  50   *
  51   * @package   core_repository
  52   * @copyright 2009 Jerome Mouneyrac
  53   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  54   */
  55  class repository_type implements cacheable_object {
  56  
  57  
  58      /**
  59       * Type name (no whitespace) - A type name is unique
  60       * Note: for a user-friendly type name see get_readablename()
  61       * @var String
  62       */
  63      private $_typename;
  64  
  65  
  66      /**
  67       * Options of this type
  68       * They are general options that any instance of this type would share
  69       * e.g. API key
  70       * These options are saved in config_plugin table
  71       * @var array
  72       */
  73      private $_options;
  74  
  75  
  76      /**
  77       * Is the repository type visible or hidden
  78       * If false (hidden): no instances can be created, edited, deleted, showned , used...
  79       * @var boolean
  80       */
  81      private $_visible;
  82  
  83  
  84      /**
  85       * 0 => not ordered, 1 => first position, 2 => second position...
  86       * A not order type would appear in first position (should never happened)
  87       * @var integer
  88       */
  89      private $_sortorder;
  90  
  91      /**
  92       * Return if the instance is visible in a context
  93       *
  94       * @todo check if the context visibility has been overwritten by the plugin creator
  95       *       (need to create special functions to be overvwritten in repository class)
  96       * @param stdClass $context context
  97       * @return bool
  98       */
  99      public function get_contextvisibility($context) {
 100          global $USER;
 101  
 102          if ($context->contextlevel == CONTEXT_COURSE) {
 103              return $this->_options['enablecourseinstances'];
 104          }
 105  
 106          if ($context->contextlevel == CONTEXT_USER) {
 107              return $this->_options['enableuserinstances'];
 108          }
 109  
 110          //the context is SITE
 111          return true;
 112      }
 113  
 114  
 115  
 116      /**
 117       * repository_type constructor
 118       *
 119       * @param int $typename
 120       * @param array $typeoptions
 121       * @param bool $visible
 122       * @param int $sortorder (don't really need set, it will be during create() call)
 123       */
 124      public function __construct($typename = '', $typeoptions = array(), $visible = true, $sortorder = 0) {
 125          global $CFG;
 126  
 127          //set type attributs
 128          $this->_typename = $typename;
 129          $this->_visible = $visible;
 130          $this->_sortorder = $sortorder;
 131  
 132          //set options attribut
 133          $this->_options = array();
 134          $options = repository::static_function($typename, 'get_type_option_names');
 135          //check that the type can be setup
 136          if (!empty($options)) {
 137              //set the type options
 138              foreach ($options as $config) {
 139                  if (array_key_exists($config, $typeoptions)) {
 140                      $this->_options[$config] = $typeoptions[$config];
 141                  }
 142              }
 143          }
 144  
 145          //retrieve visibility from option
 146          if (array_key_exists('enablecourseinstances',$typeoptions)) {
 147              $this->_options['enablecourseinstances'] = $typeoptions['enablecourseinstances'];
 148          } else {
 149               $this->_options['enablecourseinstances'] = 0;
 150          }
 151  
 152          if (array_key_exists('enableuserinstances',$typeoptions)) {
 153              $this->_options['enableuserinstances'] = $typeoptions['enableuserinstances'];
 154          } else {
 155               $this->_options['enableuserinstances'] = 0;
 156          }
 157  
 158      }
 159  
 160      /**
 161       * Get the type name (no whitespace)
 162       * For a human readable name, use get_readablename()
 163       *
 164       * @return string the type name
 165       */
 166      public function get_typename() {
 167          return $this->_typename;
 168      }
 169  
 170      /**
 171       * Return a human readable and user-friendly type name
 172       *
 173       * @return string user-friendly type name
 174       */
 175      public function get_readablename() {
 176          return get_string('pluginname','repository_'.$this->_typename);
 177      }
 178  
 179      /**
 180       * Return general options
 181       *
 182       * @return array the general options
 183       */
 184      public function get_options() {
 185          return $this->_options;
 186      }
 187  
 188      /**
 189       * Return visibility
 190       *
 191       * @return bool
 192       */
 193      public function get_visible() {
 194          return $this->_visible;
 195      }
 196  
 197      /**
 198       * Return order / position of display in the file picker
 199       *
 200       * @return int
 201       */
 202      public function get_sortorder() {
 203          return $this->_sortorder;
 204      }
 205  
 206      /**
 207       * Create a repository type (the type name must not already exist)
 208       * @param bool $silent throw exception?
 209       * @return mixed return int if create successfully, return false if
 210       */
 211      public function create($silent = false) {
 212          global $DB;
 213  
 214          //check that $type has been set
 215          $timmedtype = trim($this->_typename);
 216          if (empty($timmedtype)) {
 217              throw new repository_exception('emptytype', 'repository');
 218          }
 219  
 220          //set sortorder as the last position in the list
 221          if (!isset($this->_sortorder) || $this->_sortorder == 0 ) {
 222              $sql = "SELECT MAX(sortorder) FROM {repository}";
 223              $this->_sortorder = 1 + $DB->get_field_sql($sql);
 224          }
 225  
 226          //only create a new type if it doesn't already exist
 227          $existingtype = $DB->get_record('repository', array('type'=>$this->_typename));
 228          if (!$existingtype) {
 229              //create the type
 230              $newtype = new stdClass();
 231              $newtype->type = $this->_typename;
 232              $newtype->visible = $this->_visible;
 233              $newtype->sortorder = $this->_sortorder;
 234              $plugin_id = $DB->insert_record('repository', $newtype);
 235              //save the options in DB
 236              $this->update_options();
 237  
 238              $instanceoptionnames = repository::static_function($this->_typename, 'get_instance_option_names');
 239  
 240              //if the plugin type has no multiple instance (e.g. has no instance option name) so it wont
 241              //be possible for the administrator to create a instance
 242              //in this case we need to create an instance
 243              if (empty($instanceoptionnames)) {
 244                  $instanceoptions = array();
 245                  if (empty($this->_options['pluginname'])) {
 246                      // when moodle trying to install some repo plugin automatically
 247                      // this option will be empty, get it from language string when display
 248                      $instanceoptions['name'] = '';
 249                  } else {
 250                      // when admin trying to add a plugin manually, he will type a name
 251                      // for it
 252                      $instanceoptions['name'] = $this->_options['pluginname'];
 253                  }
 254                  repository::static_function($this->_typename, 'create', $this->_typename, 0, context_system::instance(), $instanceoptions);
 255              }
 256              //run plugin_init function
 257              if (!repository::static_function($this->_typename, 'plugin_init')) {
 258                  $this->update_visibility(false);
 259                  if (!$silent) {
 260                      throw new repository_exception('cannotinitplugin', 'repository');
 261                  }
 262              }
 263  
 264              cache::make('core', 'repositories')->purge();
 265              if(!empty($plugin_id)) {
 266                  // return plugin_id if create successfully
 267                  return $plugin_id;
 268              } else {
 269                  return false;
 270              }
 271  
 272          } else {
 273              if (!$silent) {
 274                  throw new repository_exception('existingrepository', 'repository');
 275              }
 276              // If plugin existed, return false, tell caller no new plugins were created.
 277              return false;
 278          }
 279      }
 280  
 281  
 282      /**
 283       * Update plugin options into the config_plugin table
 284       *
 285       * @param array $options
 286       * @return bool
 287       */
 288      public function update_options($options = null) {
 289          global $DB;
 290          $classname = 'repository_' . $this->_typename;
 291          $instanceoptions = repository::static_function($this->_typename, 'get_instance_option_names');
 292          if (empty($instanceoptions)) {
 293              // update repository instance name if this plugin type doesn't have muliti instances
 294              $params = array();
 295              $params['type'] = $this->_typename;
 296              $instances = repository::get_instances($params);
 297              $instance = array_pop($instances);
 298              if ($instance) {
 299                  $DB->set_field('repository_instances', 'name', $options['pluginname'], array('id'=>$instance->id));
 300              }
 301              unset($options['pluginname']);
 302          }
 303  
 304          if (!empty($options)) {
 305              $this->_options = $options;
 306          }
 307  
 308          foreach ($this->_options as $name => $value) {
 309              set_config($name, $value, $this->_typename);
 310          }
 311  
 312          cache::make('core', 'repositories')->purge();
 313          return true;
 314      }
 315  
 316      /**
 317       * Update visible database field with the value given as parameter
 318       * or with the visible value of this object
 319       * This function is private.
 320       * For public access, have a look to switch_and_update_visibility()
 321       *
 322       * @param bool $visible
 323       * @return bool
 324       */
 325      private function update_visible($visible = null) {
 326          global $DB;
 327  
 328          if (!empty($visible)) {
 329              $this->_visible = $visible;
 330          }
 331          else if (!isset($this->_visible)) {
 332              throw new repository_exception('updateemptyvisible', 'repository');
 333          }
 334  
 335          cache::make('core', 'repositories')->purge();
 336          return $DB->set_field('repository', 'visible', $this->_visible, array('type'=>$this->_typename));
 337      }
 338  
 339      /**
 340       * Update database sortorder field with the value given as parameter
 341       * or with the sortorder value of this object
 342       * This function is private.
 343       * For public access, have a look to move_order()
 344       *
 345       * @param int $sortorder
 346       * @return bool
 347       */
 348      private function update_sortorder($sortorder = null) {
 349          global $DB;
 350  
 351          if (!empty($sortorder) && $sortorder!=0) {
 352              $this->_sortorder = $sortorder;
 353          }
 354          //if sortorder is not set, we set it as the ;ast position in the list
 355          else if (!isset($this->_sortorder) || $this->_sortorder == 0 ) {
 356              $sql = "SELECT MAX(sortorder) FROM {repository}";
 357              $this->_sortorder = 1 + $DB->get_field_sql($sql);
 358          }
 359  
 360          cache::make('core', 'repositories')->purge();
 361          return $DB->set_field('repository', 'sortorder', $this->_sortorder, array('type'=>$this->_typename));
 362      }
 363  
 364      /**
 365       * Change order of the type with its adjacent upper or downer type
 366       * (database fields are updated)
 367       * Algorithm details:
 368       * 1. retrieve all types in an array. This array is sorted by sortorder,
 369       * and the array keys start from 0 to X (incremented by 1)
 370       * 2. switch sortorder values of this type and its adjacent type
 371       *
 372       * @param string $move "up" or "down"
 373       */
 374      public function move_order($move) {
 375          global $DB;
 376  
 377          $types = repository::get_types();    // retrieve all types
 378  
 379          // retrieve this type into the returned array
 380          $i = 0;
 381          while (!isset($indice) && $i<count($types)) {
 382              if ($types[$i]->get_typename() == $this->_typename) {
 383                  $indice = $i;
 384              }
 385              $i++;
 386          }
 387  
 388          // retrieve adjacent indice
 389          switch ($move) {
 390              case "up":
 391                  $adjacentindice = $indice - 1;
 392              break;
 393              case "down":
 394                  $adjacentindice = $indice + 1;
 395              break;
 396              default:
 397              throw new repository_exception('movenotdefined', 'repository');
 398          }
 399  
 400          //switch sortorder of this type and the adjacent type
 401          //TODO: we could reset sortorder for all types. This is not as good in performance term, but
 402          //that prevent from wrong behaviour on a screwed database. As performance are not important in this particular case
 403          //it worth to change the algo.
 404          if ($adjacentindice>=0 && !empty($types[$adjacentindice])) {
 405              $DB->set_field('repository', 'sortorder', $this->_sortorder, array('type'=>$types[$adjacentindice]->get_typename()));
 406              $this->update_sortorder($types[$adjacentindice]->get_sortorder());
 407          }
 408      }
 409  
 410      /**
 411       * 1. Change visibility to the value chosen
 412       * 2. Update the type
 413       *
 414       * @param bool $visible
 415       * @return bool
 416       */
 417      public function update_visibility($visible = null) {
 418          if (is_bool($visible)) {
 419              $this->_visible = $visible;
 420          } else {
 421              $this->_visible = !$this->_visible;
 422          }
 423          return $this->update_visible();
 424      }
 425  
 426  
 427      /**
 428       * Delete a repository_type (general options are removed from config_plugin
 429       * table, and all instances are deleted)
 430       *
 431       * @param bool $downloadcontents download external contents if exist
 432       * @return bool
 433       */
 434      public function delete($downloadcontents = false) {
 435          global $DB;
 436  
 437          //delete all instances of this type
 438          $params = array();
 439          $params['context'] = array();
 440          $params['onlyvisible'] = false;
 441          $params['type'] = $this->_typename;
 442          $instances = repository::get_instances($params);
 443          foreach ($instances as $instance) {
 444              $instance->delete($downloadcontents);
 445          }
 446  
 447          //delete all general options
 448          foreach ($this->_options as $name => $value) {
 449              set_config($name, null, $this->_typename);
 450          }
 451  
 452          cache::make('core', 'repositories')->purge();
 453          try {
 454              $DB->delete_records('repository', array('type' => $this->_typename));
 455          } catch (dml_exception $ex) {
 456              return false;
 457          }
 458          return true;
 459      }
 460  
 461      /**
 462       * Prepares the repository type to be cached. Implements method from cacheable_object interface.
 463       *
 464       * @return array
 465       */
 466      public function prepare_to_cache() {
 467          return array(
 468              'typename' => $this->_typename,
 469              'typeoptions' => $this->_options,
 470              'visible' => $this->_visible,
 471              'sortorder' => $this->_sortorder
 472          );
 473      }
 474  
 475      /**
 476       * Restores repository type from cache. Implements method from cacheable_object interface.
 477       *
 478       * @return array
 479       */
 480      public static function wake_from_cache($data) {
 481          return new repository_type($data['typename'], $data['typeoptions'], $data['visible'], $data['sortorder']);
 482      }
 483  }
 484  
 485  /**
 486   * This is the base class of the repository class.
 487   *
 488   * To create repository plugin, see: {@link http://docs.moodle.org/dev/Repository_plugins}
 489   * See an example: {@link repository_boxnet}
 490   *
 491   * @package   core_repository
 492   * @copyright 2009 Dongsheng Cai {@link http://dongsheng.org}
 493   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 494   */
 495  abstract class repository implements cacheable_object {
 496      /**
 497       * Timeout in seconds for downloading the external file into moodle
 498       * @deprecated since Moodle 2.7, please use $CFG->repositorygetfiletimeout instead
 499       */
 500      const GETFILE_TIMEOUT = 30;
 501  
 502      /**
 503       * Timeout in seconds for syncronising the external file size
 504       * @deprecated since Moodle 2.7, please use $CFG->repositorysyncfiletimeout instead
 505       */
 506      const SYNCFILE_TIMEOUT = 1;
 507  
 508      /**
 509       * Timeout in seconds for downloading an image file from external repository during syncronisation
 510       * @deprecated since Moodle 2.7, please use $CFG->repositorysyncimagetimeout instead
 511       */
 512      const SYNCIMAGE_TIMEOUT = 3;
 513  
 514      // $disabled can be set to true to disable a plugin by force
 515      // example: self::$disabled = true
 516      /** @var bool force disable repository instance */
 517      public $disabled = false;
 518      /** @var int repository instance id */
 519      public $id;
 520      /** @var stdClass current context */
 521      public $context;
 522      /** @var array repository options */
 523      public $options;
 524      /** @var bool Whether or not the repository instance is editable */
 525      public $readonly;
 526      /** @var int return types */
 527      public $returntypes;
 528      /** @var stdClass repository instance database record */
 529      public $instance;
 530      /** @var string Type of repository (webdav, google_docs, dropbox, ...). Read from $this->get_typename(). */
 531      protected $typename;
 532  
 533      /**
 534       * Constructor
 535       *
 536       * @param int $repositoryid repository instance id
 537       * @param int|stdClass $context a context id or context object
 538       * @param array $options repository options
 539       * @param int $readonly indicate this repo is readonly or not
 540       */
 541      public function __construct($repositoryid, $context = SYSCONTEXTID, $options = array(), $readonly = 0) {
 542          global $DB;
 543          $this->id = $repositoryid;
 544          if (is_object($context)) {
 545              $this->context = $context;
 546          } else {
 547              $this->context = context::instance_by_id($context);
 548          }
 549          $cache = cache::make('core', 'repositories');
 550          if (($this->instance = $cache->get('i:'. $this->id)) === false) {
 551              $this->instance = $DB->get_record_sql("SELECT i.*, r.type AS repositorytype, r.sortorder, r.visible
 552                        FROM {repository} r, {repository_instances} i
 553                       WHERE i.typeid = r.id and i.id = ?", array('id' => $this->id));
 554              $cache->set('i:'. $this->id, $this->instance);
 555          }
 556          $this->readonly = $readonly;
 557          $this->options = array();
 558  
 559          if (is_array($options)) {
 560              // The get_option() method will get stored options in database.
 561              $options = array_merge($this->get_option(), $options);
 562          } else {
 563              $options = $this->get_option();
 564          }
 565          foreach ($options as $n => $v) {
 566              $this->options[$n] = $v;
 567          }
 568          $this->name = $this->get_name();
 569          $this->returntypes = $this->supported_returntypes();
 570          $this->super_called = true;
 571      }
 572  
 573      /**
 574       * Magic method for non-existing (usually deprecated) class methods.
 575       *
 576       * @param string $name
 577       * @param array $arguments
 578       * @return mixed
 579       * @throws coding_exception
 580       */
 581      public function __call($name, $arguments) {
 582          if ($name === 'sync_individual_file') {
 583              // Method repository::sync_individual_file() was deprecated in Moodle 2.6.
 584              // See repository::sync_reference().
 585              debugging('Function repository::sync_individual_file() is deprecated.', DEBUG_DEVELOPER);
 586              return true;
 587          } else if ($name === 'get_file_by_reference') {
 588              // Method repository::get_file_by_reference() was deprecated in Moodle 2.6.
 589              // See repository::sync_reference().
 590              debugging('Function repository::get_file_by_reference() is deprecated.', DEBUG_DEVELOPER);
 591              return null;
 592          } else if ($name === 'get_reference_file_lifetime') {
 593              // Method repository::get_file_by_reference() was deprecated in Moodle 2.6.
 594              // See repository::sync_reference().
 595              debugging('Function repository::get_reference_file_lifetime() is deprecated.', DEBUG_DEVELOPER);
 596              return 24 * 60 * 60;
 597          } else {
 598              throw new coding_exception('Tried to call unknown method '.get_class($this).'::'.$name);
 599          }
 600      }
 601  
 602      /**
 603       * Get repository instance using repository id
 604       *
 605       * Note that this function does not check permission to access repository contents
 606       *
 607       * @throws repository_exception
 608       *
 609       * @param int $repositoryid repository instance ID
 610       * @param context|int $context context instance or context ID where this repository will be used
 611       * @param array $options additional repository options
 612       * @return repository
 613       */
 614      public static function get_repository_by_id($repositoryid, $context, $options = array()) {
 615          global $CFG, $DB;
 616          $cache = cache::make('core', 'repositories');
 617          if (!is_object($context)) {
 618              $context = context::instance_by_id($context);
 619          }
 620          $cachekey = 'rep:'. $repositoryid. ':'. $context->id. ':'. serialize($options);
 621          if ($repository = $cache->get($cachekey)) {
 622              return $repository;
 623          }
 624  
 625          if (!$record = $cache->get('i:'. $repositoryid)) {
 626              $sql = "SELECT i.*, r.type AS repositorytype, r.visible, r.sortorder
 627                        FROM {repository_instances} i
 628                        JOIN {repository} r ON r.id = i.typeid
 629                       WHERE i.id = ?";
 630              if (!$record = $DB->get_record_sql($sql, array($repositoryid))) {
 631                  throw new repository_exception('invalidrepositoryid', 'repository');
 632              }
 633              $cache->set('i:'. $record->id, $record);
 634          }
 635  
 636          $type = $record->repositorytype;
 637          if (file_exists($CFG->dirroot . "/repository/$type/lib.php")) {
 638              require_once($CFG->dirroot . "/repository/$type/lib.php");
 639              $classname = 'repository_' . $type;
 640              $options['type'] = $type;
 641              $options['typeid'] = $record->typeid;
 642              $options['visible'] = $record->visible;
 643              if (empty($options['name'])) {
 644                  $options['name'] = $record->name;
 645              }
 646              $repository = new $classname($repositoryid, $context, $options, $record->readonly);
 647              if (empty($repository->super_called)) {
 648                  // to make sure the super construct is called
 649                  debugging('parent::__construct must be called by '.$type.' plugin.');
 650              }
 651              $cache->set($cachekey, $repository);
 652              return $repository;
 653          } else {
 654              throw new repository_exception('invalidplugin', 'repository');
 655          }
 656      }
 657  
 658      /**
 659       * Returns the type name of the repository.
 660       *
 661       * @return string type name of the repository.
 662       * @since  Moodle 2.5
 663       */
 664      public function get_typename() {
 665          if (empty($this->typename)) {
 666              $matches = array();
 667              if (!preg_match("/^repository_(.*)$/", get_class($this), $matches)) {
 668                  throw new coding_exception('The class name of a repository should be repository_<typeofrepository>, '.
 669                          'e.g. repository_dropbox');
 670              }
 671              $this->typename = $matches[1];
 672          }
 673          return $this->typename;
 674      }
 675  
 676      /**
 677       * Get a repository type object by a given type name.
 678       *
 679       * @static
 680       * @param string $typename the repository type name
 681       * @return repository_type|bool
 682       */
 683      public static function get_type_by_typename($typename) {
 684          global $DB;
 685          $cache = cache::make('core', 'repositories');
 686          if (($repositorytype = $cache->get('typename:'. $typename)) === false) {
 687              $repositorytype = null;
 688              if ($record = $DB->get_record('repository', array('type' => $typename))) {
 689                  $repositorytype = new repository_type($record->type, (array)get_config($record->type), $record->visible, $record->sortorder);
 690                  $cache->set('typeid:'. $record->id, $repositorytype);
 691              }
 692              $cache->set('typename:'. $typename, $repositorytype);
 693          }
 694          return $repositorytype;
 695      }
 696  
 697      /**
 698       * Get the repository type by a given repository type id.
 699       *
 700       * @static
 701       * @param int $id the type id
 702       * @return object
 703       */
 704      public static function get_type_by_id($id) {
 705          global $DB;
 706          $cache = cache::make('core', 'repositories');
 707          if (($repositorytype = $cache->get('typeid:'. $id)) === false) {
 708              $repositorytype = null;
 709              if ($record = $DB->get_record('repository', array('id' => $id))) {
 710                  $repositorytype = new repository_type($record->type, (array)get_config($record->type), $record->visible, $record->sortorder);
 711                  $cache->set('typename:'. $record->type, $repositorytype);
 712              }
 713              $cache->set('typeid:'. $id, $repositorytype);
 714          }
 715          return $repositorytype;
 716      }
 717  
 718      /**
 719       * Return all repository types ordered by sortorder field
 720       * first repository type in returnedarray[0], second repository type in returnedarray[1], ...
 721       *
 722       * @static
 723       * @param bool $visible can return types by visiblity, return all types if null
 724       * @return array Repository types
 725       */
 726      public static function get_types($visible=null) {
 727          global $DB, $CFG;
 728          $cache = cache::make('core', 'repositories');
 729          if (!$visible) {
 730              $typesnames = $cache->get('types');
 731          } else {
 732              $typesnames = $cache->get('typesvis');
 733          }
 734          $types = array();
 735          if ($typesnames === false) {
 736              $typesnames = array();
 737              $vistypesnames = array();
 738              if ($records = $DB->get_records('repository', null ,'sortorder')) {
 739                  foreach($records as $type) {
 740                      if (($repositorytype = $cache->get('typename:'. $type->type)) === false) {
 741                          // Create new instance of repository_type.
 742                          if (file_exists($CFG->dirroot . '/repository/'. $type->type .'/lib.php')) {
 743                              $repositorytype = new repository_type($type->type, (array)get_config($type->type), $type->visible, $type->sortorder);
 744                              $cache->set('typeid:'. $type->id, $repositorytype);
 745                              $cache->set('typename:'. $type->type, $repositorytype);
 746                          }
 747                      }
 748                      if ($repositorytype) {
 749                          if (empty($visible) || $repositorytype->get_visible()) {
 750                              $types[] = $repositorytype;
 751                              $vistypesnames[] = $repositorytype->get_typename();
 752                          }
 753                          $typesnames[] = $repositorytype->get_typename();
 754                      }
 755                  }
 756              }
 757              $cache->set('types', $typesnames);
 758              $cache->set('typesvis', $vistypesnames);
 759          } else {
 760              foreach ($typesnames as $typename) {
 761                  $types[] = self::get_type_by_typename($typename);
 762              }
 763          }
 764          return $types;
 765      }
 766  
 767      /**
 768       * Checks if user has a capability to view the current repository.
 769       *
 770       * @return bool true when the user can, otherwise throws an exception.
 771       * @throws repository_exception when the user does not meet the requirements.
 772       */
 773      public final function check_capability() {
 774          global $USER;
 775  
 776          // The context we are on.
 777          $currentcontext = $this->context;
 778  
 779          // Ensure that the user can view the repository in the current context.
 780          $can = has_capability('repository/'.$this->get_typename().':view', $currentcontext);
 781  
 782          // Context in which the repository has been created.
 783          $repocontext = context::instance_by_id($this->instance->contextid);
 784  
 785          // Prevent access to private repositories when logged in as.
 786          if ($can && \core\session\manager::is_loggedinas()) {
 787              if ($this->contains_private_data() || $repocontext->contextlevel == CONTEXT_USER) {
 788                  $can = false;
 789              }
 790          }
 791  
 792          // We are going to ensure that the current context was legit, and reliable to check
 793          // the capability against. (No need to do that if we already cannot).
 794          if ($can) {
 795              if ($repocontext->contextlevel == CONTEXT_USER) {
 796                  // The repository is a user instance, ensure we're the right user to access it!
 797                  if ($repocontext->instanceid != $USER->id) {
 798                      $can = false;
 799                  }
 800              } else if ($repocontext->contextlevel == CONTEXT_COURSE) {
 801                  // The repository is a course one. Let's check that we are on the right course.
 802                  if (in_array($currentcontext->contextlevel, array(CONTEXT_COURSE, CONTEXT_MODULE, CONTEXT_BLOCK))) {
 803                      $coursecontext = $currentcontext->get_course_context();
 804                      if ($coursecontext->instanceid != $repocontext->instanceid) {
 805                          $can = false;
 806                      }
 807                  } else {
 808                      // We are on a parent context, therefore it's legit to check the permissions
 809                      // in the current context.
 810                  }
 811              } else {
 812                  // Nothing to check here, system instances can have different permissions on different
 813                  // levels. We do not want to prevent URL hack here, because it does not make sense to
 814                  // prevent a user to access a repository in a context if it's accessible in another one.
 815              }
 816          }
 817  
 818          if ($can) {
 819              return true;
 820          }
 821  
 822          throw new repository_exception('nopermissiontoaccess', 'repository');
 823      }
 824  
 825      /**
 826       * Check if file already exists in draft area.
 827       *
 828       * @static
 829       * @param int $itemid of the draft area.
 830       * @param string $filepath path to the file.
 831       * @param string $filename file name.
 832       * @return bool
 833       */
 834      public static function draftfile_exists($itemid, $filepath, $filename) {
 835          global $USER;
 836          $fs = get_file_storage();
 837          $usercontext = context_user::instance($USER->id);
 838          return $fs->file_exists($usercontext->id, 'user', 'draft', $itemid, $filepath, $filename);
 839      }
 840  
 841      /**
 842       * Parses the moodle file reference and returns an instance of stored_file
 843       *
 844       * @param string $reference reference to the moodle internal file as retruned by
 845       *        {@link repository::get_file_reference()} or {@link file_storage::pack_reference()}
 846       * @return stored_file|null
 847       */
 848      public static function get_moodle_file($reference) {
 849          $params = file_storage::unpack_reference($reference, true);
 850          $fs = get_file_storage();
 851          return $fs->get_file($params['contextid'], $params['component'], $params['filearea'],
 852                      $params['itemid'], $params['filepath'], $params['filename']);
 853      }
 854  
 855      /**
 856       * Repository method to make sure that user can access particular file.
 857       *
 858       * This is checked when user tries to pick the file from repository to deal with
 859       * potential parameter substitutions is request
 860       *
 861       * @param string $source source of the file, returned by repository as 'source' and received back from user (not cleaned)
 862       * @return bool whether the file is accessible by current user
 863       */
 864      public function file_is_accessible($source) {
 865          if ($this->has_moodle_files()) {
 866              $reference = $this->get_file_reference($source);
 867              try {
 868                  $params = file_storage::unpack_reference($reference, true);
 869              } catch (file_reference_exception $e) {
 870                  return false;
 871              }
 872              $browser = get_file_browser();
 873              $context = context::instance_by_id($params['contextid']);
 874              $file_info = $browser->get_file_info($context, $params['component'], $params['filearea'],
 875                      $params['itemid'], $params['filepath'], $params['filename']);
 876              return !empty($file_info);
 877          }
 878          return true;
 879      }
 880  
 881      /**
 882       * This function is used to copy a moodle file to draft area.
 883       *
 884       * It DOES NOT check if the user is allowed to access this file because the actual file
 885       * can be located in the area where user does not have access to but there is an alias
 886       * to this file in the area where user CAN access it.
 887       * {@link file_is_accessible} should be called for alias location before calling this function.
 888       *
 889       * @param string $source The metainfo of file, it is base64 encoded php serialized data
 890       * @param stdClass|array $filerecord contains itemid, filepath, filename and optionally other
 891       *      attributes of the new file
 892       * @param int $maxbytes maximum allowed size of file, -1 if unlimited. If size of file exceeds
 893       *      the limit, the file_exception is thrown.
 894       * @param int $areamaxbytes the maximum size of the area. A file_exception is thrown if the
 895       *      new file will reach the limit.
 896       * @return array The information about the created file
 897       */
 898      public function copy_to_area($source, $filerecord, $maxbytes = -1, $areamaxbytes = FILE_AREA_MAX_BYTES_UNLIMITED) {
 899          global $USER;
 900          $fs = get_file_storage();
 901  
 902          if ($this->has_moodle_files() == false) {
 903              throw new coding_exception('Only repository used to browse moodle files can use repository::copy_to_area()');
 904          }
 905  
 906          $user_context = context_user::instance($USER->id);
 907  
 908          $filerecord = (array)$filerecord;
 909          // make sure the new file will be created in user draft area
 910          $filerecord['component'] = 'user';
 911          $filerecord['filearea'] = 'draft';
 912          $filerecord['contextid'] = $user_context->id;
 913          $draftitemid = $filerecord['itemid'];
 914          $new_filepath = $filerecord['filepath'];
 915          $new_filename = $filerecord['filename'];
 916  
 917          // the file needs to copied to draft area
 918          $stored_file = self::get_moodle_file($source);
 919          if ($maxbytes != -1 && $stored_file->get_filesize() > $maxbytes) {
 920              throw new file_exception('maxbytes');
 921          }
 922          // Validate the size of the draft area.
 923          if (file_is_draft_area_limit_reached($draftitemid, $areamaxbytes, $stored_file->get_filesize())) {
 924              throw new file_exception('maxareabytes');
 925          }
 926  
 927          if (repository::draftfile_exists($draftitemid, $new_filepath, $new_filename)) {
 928              // create new file
 929              $unused_filename = repository::get_unused_filename($draftitemid, $new_filepath, $new_filename);
 930              $filerecord['filename'] = $unused_filename;
 931              $fs->create_file_from_storedfile($filerecord, $stored_file);
 932              $event = array();
 933              $event['event'] = 'fileexists';
 934              $event['newfile'] = new stdClass;
 935              $event['newfile']->filepath = $new_filepath;
 936              $event['newfile']->filename = $unused_filename;
 937              $event['newfile']->url = moodle_url::make_draftfile_url($draftitemid, $new_filepath, $unused_filename)->out();
 938              $event['existingfile'] = new stdClass;
 939              $event['existingfile']->filepath = $new_filepath;
 940              $event['existingfile']->filename = $new_filename;
 941              $event['existingfile']->url = moodle_url::make_draftfile_url($draftitemid, $new_filepath, $new_filename)->out();
 942              return $event;
 943          } else {
 944              $fs->create_file_from_storedfile($filerecord, $stored_file);
 945              $info = array();
 946              $info['itemid'] = $draftitemid;
 947              $info['file'] = $new_filename;
 948              $info['title'] = $new_filename;
 949              $info['contextid'] = $user_context->id;
 950              $info['url'] = moodle_url::make_draftfile_url($draftitemid, $new_filepath, $new_filename)->out();
 951              $info['filesize'] = $stored_file->get_filesize();
 952              return $info;
 953          }
 954      }
 955  
 956      /**
 957       * Get an unused filename from the current draft area.
 958       *
 959       * Will check if the file ends with ([0-9]) and increase the number.
 960       *
 961       * @static
 962       * @param int $itemid draft item ID.
 963       * @param string $filepath path to the file.
 964       * @param string $filename name of the file.
 965       * @return string an unused file name.
 966       */
 967      public static function get_unused_filename($itemid, $filepath, $filename) {
 968          global $USER;
 969          $contextid = context_user::instance($USER->id)->id;
 970          $fs = get_file_storage();
 971          return $fs->get_unused_filename($contextid, 'user', 'draft', $itemid, $filepath, $filename);
 972      }
 973  
 974      /**
 975       * Append a suffix to filename.
 976       *
 977       * @static
 978       * @param string $filename
 979       * @return string
 980       * @deprecated since 2.5
 981       */
 982      public static function append_suffix($filename) {
 983          debugging('The function repository::append_suffix() has been deprecated. Use repository::get_unused_filename() instead.',
 984              DEBUG_DEVELOPER);
 985          $pathinfo = pathinfo($filename);
 986          if (empty($pathinfo['extension'])) {
 987              return $filename . RENAME_SUFFIX;
 988          } else {
 989              return $pathinfo['filename'] . RENAME_SUFFIX . '.' . $pathinfo['extension'];
 990          }
 991      }
 992  
 993      /**
 994       * Return all types that you a user can create/edit and which are also visible
 995       * Note: Mostly used in order to know if at least one editable type can be set
 996       *
 997       * @static
 998       * @param stdClass $context the context for which we want the editable types
 999       * @return array types
1000       */
1001      public static function get_editable_types($context = null) {
1002  
1003          if (empty($context)) {
1004              $context = context_system::instance();
1005          }
1006  
1007          $types= repository::get_types(true);
1008          $editabletypes = array();
1009          foreach ($types as $type) {
1010              $instanceoptionnames = repository::static_function($type->get_typename(), 'get_instance_option_names');
1011              if (!empty($instanceoptionnames)) {
1012                  if ($type->get_contextvisibility($context)) {
1013                      $editabletypes[]=$type;
1014                  }
1015               }
1016          }
1017          return $editabletypes;
1018      }
1019  
1020      /**
1021       * Return repository instances
1022       *
1023       * @static
1024       * @param array $args Array containing the following keys:
1025       *           currentcontext : instance of context (default system context)
1026       *           context : array of instances of context (default empty array)
1027       *           onlyvisible : bool (default true)
1028       *           type : string return instances of this type only
1029       *           accepted_types : string|array return instances that contain files of those types (*, web_image, .pdf, ...)
1030       *           return_types : int combination of FILE_INTERNAL & FILE_EXTERNAL & FILE_REFERENCE.
1031       *                          0 means every type. The default is FILE_INTERNAL | FILE_EXTERNAL.
1032       *           userid : int if specified, instances belonging to other users will not be returned
1033       *
1034       * @return array repository instances
1035       */
1036      public static function get_instances($args = array()) {
1037          global $DB, $CFG, $USER;
1038  
1039          // Fill $args attributes with default values unless specified
1040          if (!isset($args['currentcontext']) || !($args['currentcontext'] instanceof context)) {
1041              $current_context = context_system::instance();
1042          } else {
1043              $current_context = $args['currentcontext'];
1044          }
1045          $args['currentcontext'] = $current_context->id;
1046          $contextids = array();
1047          if (!empty($args['context'])) {
1048              foreach ($args['context'] as $context) {
1049                  $contextids[] = $context->id;
1050              }
1051          }
1052          $args['context'] = $contextids;
1053          if (!isset($args['onlyvisible'])) {
1054              $args['onlyvisible'] = true;
1055          }
1056          if (!isset($args['return_types'])) {
1057              $args['return_types'] = FILE_INTERNAL | FILE_EXTERNAL;
1058          }
1059          if (!isset($args['type'])) {
1060              $args['type'] = null;
1061          }
1062          if (empty($args['disable_types']) || !is_array($args['disable_types'])) {
1063              $args['disable_types'] = null;
1064          }
1065          if (empty($args['userid']) || !is_numeric($args['userid'])) {
1066              $args['userid'] = null;
1067          }
1068          if (!isset($args['accepted_types']) || (is_array($args['accepted_types']) && in_array('*', $args['accepted_types']))) {
1069              $args['accepted_types'] = '*';
1070          }
1071          ksort($args);
1072          $cachekey = 'all:'. serialize($args);
1073  
1074          // Check if we have cached list of repositories with the same query
1075          $cache = cache::make('core', 'repositories');
1076          if (($cachedrepositories = $cache->get($cachekey)) !== false) {
1077              // convert from cacheable_object_array to array
1078              $repositories = array();
1079              foreach ($cachedrepositories as $repository) {
1080                  $repositories[$repository->id] = $repository;
1081              }
1082              return $repositories;
1083          }
1084  
1085          // Prepare DB SQL query to retrieve repositories
1086          $params = array();
1087          $sql = "SELECT i.*, r.type AS repositorytype, r.sortorder, r.visible
1088                    FROM {repository} r, {repository_instances} i
1089                   WHERE i.typeid = r.id ";
1090  
1091          if ($args['disable_types']) {
1092              list($types, $p) = $DB->get_in_or_equal($args['disable_types'], SQL_PARAMS_NAMED, 'distype', false);
1093              $sql .= " AND r.type $types";
1094              $params = array_merge($params, $p);
1095          }
1096  
1097          if ($args['userid']) {
1098              $sql .= " AND (i.userid = 0 or i.userid = :userid)";
1099              $params['userid'] = $args['userid'];
1100          }
1101  
1102          if ($args['context']) {
1103              list($ctxsql, $p2) = $DB->get_in_or_equal($args['context'], SQL_PARAMS_NAMED, 'ctx');
1104              $sql .= " AND i.contextid $ctxsql";
1105              $params = array_merge($params, $p2);
1106          }
1107  
1108          if ($args['onlyvisible'] == true) {
1109              $sql .= " AND r.visible = 1";
1110          }
1111  
1112          if ($args['type'] !== null) {
1113              $sql .= " AND r.type = :type";
1114              $params['type'] = $args['type'];
1115          }
1116          $sql .= " ORDER BY r.sortorder, i.name";
1117  
1118          if (!$records = $DB->get_records_sql($sql, $params)) {
1119              $records = array();
1120          }
1121  
1122          $repositories = array();
1123          // Sortorder should be unique, which is not true if we use $record->sortorder
1124          // and there are multiple instances of any repository type
1125          $sortorder = 1;
1126          foreach ($records as $record) {
1127              $cache->set('i:'. $record->id, $record);
1128              if (!file_exists($CFG->dirroot . '/repository/'. $record->repositorytype.'/lib.php')) {
1129                  continue;
1130              }
1131              $repository = self::get_repository_by_id($record->id, $current_context);
1132              $repository->options['sortorder'] = $sortorder++;
1133  
1134              $is_supported = true;
1135  
1136              // check mimetypes
1137              if ($args['accepted_types'] !== '*' and $repository->supported_filetypes() !== '*') {
1138                  $accepted_ext = file_get_typegroup('extension', $args['accepted_types']);
1139                  $supported_ext = file_get_typegroup('extension', $repository->supported_filetypes());
1140                  $valid_ext = array_intersect($accepted_ext, $supported_ext);
1141                  $is_supported = !empty($valid_ext);
1142              }
1143              // Check return values.
1144              if (!empty($args['return_types']) && !($repository->supported_returntypes() & $args['return_types'])) {
1145                  $is_supported = false;
1146              }
1147  
1148              if (!$args['onlyvisible'] || ($repository->is_visible() && !$repository->disabled)) {
1149                  // check capability in current context
1150                  $capability = has_capability('repository/'.$record->repositorytype.':view', $current_context);
1151                  if ($record->repositorytype == 'coursefiles') {
1152                      // coursefiles plugin needs managefiles permission
1153                      $capability = $capability && has_capability('moodle/course:managefiles', $current_context);
1154                  }
1155                  if ($is_supported && $capability) {
1156                      $repositories[$repository->id] = $repository;
1157                  }
1158              }
1159          }
1160          $cache->set($cachekey, new cacheable_object_array($repositories));
1161          return $repositories;
1162      }
1163  
1164      /**
1165       * Get single repository instance for administrative actions
1166       *
1167       * Do not use this function to access repository contents, because it
1168       * does not set the current context
1169       *
1170       * @see repository::get_repository_by_id()
1171       *
1172       * @static
1173       * @param integer $id repository instance id
1174       * @return repository
1175       */
1176      public static function get_instance($id) {
1177          return self::get_repository_by_id($id, context_system::instance());
1178      }
1179  
1180      /**
1181       * Call a static function. Any additional arguments than plugin and function will be passed through.
1182       *
1183       * @static
1184       * @param string $plugin repository plugin name
1185       * @param string $function function name
1186       * @return mixed
1187       */
1188      public static function static_function($plugin, $function) {
1189          global $CFG;
1190  
1191          //check that the plugin exists
1192          $typedirectory = $CFG->dirroot . '/repository/'. $plugin . '/lib.php';
1193          if (!file_exists($typedirectory)) {
1194              //throw new repository_exception('invalidplugin', 'repository');
1195              return false;
1196          }
1197  
1198          $args = func_get_args();
1199          if (count($args) <= 2) {
1200              $args = array();
1201          } else {
1202              array_shift($args);
1203              array_shift($args);
1204          }
1205  
1206          require_once($typedirectory);
1207          return call_user_func_array(array('repository_' . $plugin, $function), $args);
1208      }
1209  
1210      /**
1211       * Scan file, throws exception in case of infected file.
1212       *
1213       * Please note that the scanning engine must be able to access the file,
1214       * permissions of the file are not modified here!
1215       *
1216       * @static
1217       * @param string $thefile
1218       * @param string $filename name of the file
1219       * @param bool $deleteinfected
1220       */
1221      public static function antivir_scan_file($thefile, $filename, $deleteinfected) {
1222          global $CFG;
1223  
1224          if (!is_readable($thefile)) {
1225              // this should not happen
1226              return;
1227          }
1228  
1229          if (empty($CFG->runclamonupload) or empty($CFG->pathtoclam)) {
1230              // clam not enabled
1231              return;
1232          }
1233  
1234          $CFG->pathtoclam = trim($CFG->pathtoclam);
1235  
1236          if (!file_exists($CFG->pathtoclam) or !is_executable($CFG->pathtoclam)) {
1237              // misconfigured clam - use the old notification for now
1238              require("$CFG->libdir/uploadlib.php");
1239              $notice = get_string('clamlost', 'moodle', $CFG->pathtoclam);
1240              clam_message_admins($notice);
1241              return;
1242          }
1243  
1244          $clamparam = ' --stdout ';
1245          // If we are dealing with clamdscan, clamd is likely run as a different user
1246          // that might not have permissions to access your file.
1247          // To make clamdscan work, we use --fdpass parameter that passes the file
1248          // descriptor permissions to clamd, which allows it to scan given file
1249          // irrespective of directory and file permissions.
1250          if (basename($CFG->pathtoclam) == 'clamdscan') {
1251              $clamparam .= '--fdpass ';
1252          }
1253          // execute test
1254          $cmd = escapeshellcmd($CFG->pathtoclam).$clamparam.escapeshellarg($thefile);
1255          exec($cmd, $output, $return);
1256  
1257          if ($return == 0) {
1258              // perfect, no problem found
1259              return;
1260  
1261          } else if ($return == 1) {
1262              // infection found
1263              if ($deleteinfected) {
1264                  unlink($thefile);
1265              }
1266              throw new moodle_exception('virusfounduser', 'moodle', '', array('filename'=>$filename));
1267  
1268          } else {
1269              //unknown problem
1270              require("$CFG->libdir/uploadlib.php");
1271              $notice = get_string('clamfailed', 'moodle', get_clam_error_code($return));
1272              $notice .= "\n\n". implode("\n", $output);
1273              clam_message_admins($notice);
1274              if ($CFG->clamfailureonupload === 'actlikevirus') {
1275                  if ($deleteinfected) {
1276                      unlink($thefile);
1277                  }
1278                  throw new moodle_exception('virusfounduser', 'moodle', '', array('filename'=>$filename));
1279              } else {
1280                  return;
1281              }
1282          }
1283      }
1284  
1285      /**
1286       * Repository method to serve the referenced file
1287       *
1288       * @see send_stored_file
1289       *
1290       * @param stored_file $storedfile the file that contains the reference
1291       * @param int $lifetime Number of seconds before the file should expire from caches (null means $CFG->filelifetime)
1292       * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only
1293       * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin
1294       * @param array $options additional options affecting the file serving
1295       */
1296      public function send_file($storedfile, $lifetime=null , $filter=0, $forcedownload=false, array $options = null) {
1297          if ($this->has_moodle_files()) {
1298              $fs = get_file_storage();
1299              $params = file_storage::unpack_reference($storedfile->get_reference(), true);
1300              $srcfile = null;
1301              if (is_array($params)) {
1302                  $srcfile = $fs->get_file($params['contextid'], $params['component'], $params['filearea'],
1303                          $params['itemid'], $params['filepath'], $params['filename']);
1304              }
1305              if (empty($options)) {
1306                  $options = array();
1307              }
1308              if (!isset($options['filename'])) {
1309                  $options['filename'] = $storedfile->get_filename();
1310              }
1311              if (!$srcfile) {
1312                  send_file_not_found();
1313              } else {
1314                  send_stored_file($srcfile, $lifetime, $filter, $forcedownload, $options);
1315              }
1316          } else {
1317              throw new coding_exception("Repository plugin must implement send_file() method.");
1318          }
1319      }
1320  
1321      /**
1322       * Return human readable reference information
1323       *
1324       * @param string $reference value of DB field files_reference.reference
1325       * @param int $filestatus status of the file, 0 - ok, 666 - source missing
1326       * @return string
1327       */
1328      public function get_reference_details($reference, $filestatus = 0) {
1329          if ($this->has_moodle_files()) {
1330              $fileinfo = null;
1331              $params = file_storage::unpack_reference($reference, true);
1332              if (is_array($params)) {
1333                  $context = context::instance_by_id($params['contextid'], IGNORE_MISSING);
1334                  if ($context) {
1335                      $browser = get_file_browser();
1336                      $fileinfo = $browser->get_file_info($context, $params['component'], $params['filearea'], $params['itemid'], $params['filepath'], $params['filename']);
1337                  }
1338              }
1339              if (empty($fileinfo)) {
1340                  if ($filestatus == 666) {
1341                      if (is_siteadmin() || ($context && has_capability('moodle/course:managefiles', $context))) {
1342                          return get_string('lostsource', 'repository',
1343                                  $params['contextid']. '/'. $params['component']. '/'. $params['filearea']. '/'. $params['itemid']. $params['filepath']. $params['filename']);
1344                      } else {
1345                          return get_string('lostsource', 'repository', '');
1346                      }
1347                  }
1348                  return get_string('undisclosedsource', 'repository');
1349              } else {
1350                  return $fileinfo->get_readable_fullname();
1351              }
1352          }
1353          return '';
1354      }
1355  
1356      /**
1357       * Cache file from external repository by reference
1358       * {@link repository::get_file_reference()}
1359       * {@link repository::get_file()}
1360       * Invoked at MOODLE/repository/repository_ajax.php
1361       *
1362       * @param string $reference this reference is generated by
1363       *                          repository::get_file_reference()
1364       * @param stored_file $storedfile created file reference
1365       */
1366      public function cache_file_by_reference($reference, $storedfile) {
1367      }
1368  
1369      /**
1370       * Return the source information
1371       *
1372       * The result of the function is stored in files.source field. It may be analysed
1373       * when the source file is lost or repository may use it to display human-readable
1374       * location of reference original.
1375       *
1376       * This method is called when file is picked for the first time only. When file
1377       * (either copy or a reference) is already in moodle and it is being picked
1378       * again to another file area (also as a copy or as a reference), the value of
1379       * files.source is copied.
1380       *
1381       * @param string $source source of the file, returned by repository as 'source' and received back from user (not cleaned)
1382       * @return string|null
1383       */
1384      public function get_file_source_info($source) {
1385          if ($this->has_moodle_files()) {
1386              $reference = $this->get_file_reference($source);
1387              return $this->get_reference_details($reference, 0);
1388          }
1389          return $source;
1390      }
1391  
1392      /**
1393       * Move file from download folder to file pool using FILE API
1394       *
1395       * @todo MDL-28637
1396       * @static
1397       * @param string $thefile file path in download folder
1398       * @param stdClass $record
1399       * @return array containing the following keys:
1400       *           icon
1401       *           file
1402       *           id
1403       *           url
1404       */
1405      public static function move_to_filepool($thefile, $record) {
1406          global $DB, $CFG, $USER, $OUTPUT;
1407  
1408          // scan for viruses if possible, throws exception if problem found
1409          self::antivir_scan_file($thefile, $record->filename, empty($CFG->repository_no_delete)); //TODO: MDL-28637 this repository_no_delete is a bloody hack!
1410  
1411          $fs = get_file_storage();
1412          // If file name being used.
1413          if (repository::draftfile_exists($record->itemid, $record->filepath, $record->filename)) {
1414              $draftitemid = $record->itemid;
1415              $new_filename = repository::get_unused_filename($draftitemid, $record->filepath, $record->filename);
1416              $old_filename = $record->filename;
1417              // Create a tmp file.
1418              $record->filename = $new_filename;
1419              $newfile = $fs->create_file_from_pathname($record, $thefile);
1420              $event = array();
1421              $event['event'] = 'fileexists';
1422              $event['newfile'] = new stdClass;
1423              $event['newfile']->filepath = $record->filepath;
1424              $event['newfile']->filename = $new_filename;
1425              $event['newfile']->url = moodle_url::make_draftfile_url($draftitemid, $record->filepath, $new_filename)->out();
1426  
1427              $event['existingfile'] = new stdClass;
1428              $event['existingfile']->filepath = $record->filepath;
1429              $event['existingfile']->filename = $old_filename;
1430              $event['existingfile']->url      = moodle_url::make_draftfile_url($draftitemid, $record->filepath, $old_filename)->out();
1431              return $event;
1432          }
1433          if ($file = $fs->create_file_from_pathname($record, $thefile)) {
1434              if (empty($CFG->repository_no_delete)) {
1435                  $delete = unlink($thefile);
1436                  unset($CFG->repository_no_delete);
1437              }
1438              return array(
1439                  'url'=>moodle_url::make_draftfile_url($file->get_itemid(), $file->get_filepath(), $file->get_filename())->out(),
1440                  'id'=>$file->get_itemid(),
1441                  'file'=>$file->get_filename(),
1442                  'icon' => $OUTPUT->pix_url(file_extension_icon($thefile, 32))->out(),
1443              );
1444          } else {
1445              return null;
1446          }
1447      }
1448  
1449      /**
1450       * Builds a tree of files This function is then called recursively.
1451       *
1452       * @static
1453       * @todo take $search into account, and respect a threshold for dynamic loading
1454       * @param file_info $fileinfo an object returned by file_browser::get_file_info()
1455       * @param string $search searched string
1456       * @param bool $dynamicmode no recursive call is done when in dynamic mode
1457       * @param array $list the array containing the files under the passed $fileinfo
1458       * @return int the number of files found
1459       */
1460      public static function build_tree($fileinfo, $search, $dynamicmode, &$list) {
1461          global $CFG, $OUTPUT;
1462  
1463          $filecount = 0;
1464          $children = $fileinfo->get_children();
1465  
1466          foreach ($children as $child) {
1467              $filename = $child->get_visible_name();
1468              $filesize = $child->get_filesize();
1469              $filesize = $filesize ? display_size($filesize) : '';
1470              $filedate = $child->get_timemodified();
1471              $filedate = $filedate ? userdate($filedate) : '';
1472              $filetype = $child->get_mimetype();
1473  
1474              if ($child->is_directory()) {
1475                  $path = array();
1476                  $level = $child->get_parent();
1477                  while ($level) {
1478                      $params = $level->get_params();
1479                      $path[] = array($params['filepath'], $level->get_visible_name());
1480                      $level = $level->get_parent();
1481                  }
1482  
1483                  $tmp = array(
1484                      'title' => $child->get_visible_name(),
1485                      'size' => 0,
1486                      'date' => $filedate,
1487                      'path' => array_reverse($path),
1488                      'thumbnail' => $OUTPUT->pix_url(file_folder_icon(90))->out(false)
1489                  );
1490  
1491                  //if ($dynamicmode && $child->is_writable()) {
1492                  //    $tmp['children'] = array();
1493                  //} else {
1494                      // if folder name matches search, we send back all files contained.
1495                  $_search = $search;
1496                  if ($search && stristr($tmp['title'], $search) !== false) {
1497                      $_search = false;
1498                  }
1499                  $tmp['children'] = array();
1500                  $_filecount = repository::build_tree($child, $_search, $dynamicmode, $tmp['children']);
1501                  if ($search && $_filecount) {
1502                      $tmp['expanded'] = 1;
1503                  }
1504  
1505                  //}
1506  
1507                  if (!$search || $_filecount || (stristr($tmp['title'], $search) !== false)) {
1508                      $filecount += $_filecount;
1509                      $list[] = $tmp;
1510                  }
1511  
1512              } else { // not a directory
1513                  // skip the file, if we're in search mode and it's not a match
1514                  if ($search && (stristr($filename, $search) === false)) {
1515                      continue;
1516                  }
1517                  $params = $child->get_params();
1518                  $source = serialize(array($params['contextid'], $params['component'], $params['filearea'], $params['itemid'], $params['filepath'], $params['filename']));
1519                  $list[] = array(
1520                      'title' => $filename,
1521                      'size' => $filesize,
1522                      'date' => $filedate,
1523                      //'source' => $child->get_url(),
1524                      'source' => base64_encode($source),
1525                      'icon'=>$OUTPUT->pix_url(file_file_icon($child, 24))->out(false),
1526                      'thumbnail'=>$OUTPUT->pix_url(file_file_icon($child, 90))->out(false),
1527                  );
1528                  $filecount++;
1529              }
1530          }
1531  
1532          return $filecount;
1533      }
1534  
1535      /**
1536       * Display a repository instance list (with edit/delete/create links)
1537       *
1538       * @static
1539       * @param stdClass $context the context for which we display the instance
1540       * @param string $typename if set, we display only one type of instance
1541       */
1542      public static function display_instances_list($context, $typename = null) {
1543          global $CFG, $USER, $OUTPUT;
1544  
1545          $output = $OUTPUT->box_start('generalbox');
1546          //if the context is SYSTEM, so we call it from administration page
1547          $admin = ($context->id == SYSCONTEXTID) ? true : false;
1548          if ($admin) {
1549              $baseurl = new moodle_url('/'.$CFG->admin.'/repositoryinstance.php', array('sesskey'=>sesskey()));
1550              $output .= $OUTPUT->heading(get_string('siteinstances', 'repository'));
1551          } else {
1552              $baseurl = new moodle_url('/repository/manage_instances.php', array('contextid'=>$context->id, 'sesskey'=>sesskey()));
1553          }
1554  
1555          $namestr = get_string('name');
1556          $pluginstr = get_string('plugin', 'repository');
1557          $settingsstr = get_string('settings');
1558          $deletestr = get_string('delete');
1559          // Retrieve list of instances. In administration context we want to display all
1560          // instances of a type, even if this type is not visible. In course/user context we
1561          // want to display only visible instances, but for every type types. The repository::get_instances()
1562          // third parameter displays only visible type.
1563          $params = array();
1564          $params['context'] = array($context);
1565          $params['currentcontext'] = $context;
1566          $params['return_types'] = 0;
1567          $params['onlyvisible'] = !$admin;
1568          $params['type']        = $typename;
1569          $instances = repository::get_instances($params);
1570          $instancesnumber = count($instances);
1571          $alreadyplugins = array();
1572  
1573          $table = new html_table();
1574          $table->head = array($namestr, $pluginstr, $settingsstr, $deletestr);
1575          $table->align = array('left', 'left', 'center','center');
1576          $table->data = array();
1577  
1578          $updowncount = 1;
1579  
1580          foreach ($instances as $i) {
1581              $settings = '';
1582              $delete = '';
1583  
1584              $type = repository::get_type_by_id($i->options['typeid']);
1585  
1586              if ($type->get_contextvisibility($context)) {
1587                  if (!$i->readonly) {
1588  
1589                      $settingurl = new moodle_url($baseurl);
1590                      $settingurl->param('type', $i->options['type']);
1591                      $settingurl->param('edit', $i->id);
1592                      $settings .= html_writer::link($settingurl, $settingsstr);
1593  
1594                      $deleteurl = new moodle_url($baseurl);
1595                      $deleteurl->param('delete', $i->id);
1596                      $deleteurl->param('type', $i->options['type']);
1597                      $delete .= html_writer::link($deleteurl, $deletestr);
1598                  }
1599              }
1600  
1601              $type = repository::get_type_by_id($i->options['typeid']);
1602              $table->data[] = array(format_string($i->name), $type->get_readablename(), $settings, $delete);
1603  
1604              //display a grey row if the type is defined as not visible
1605              if (isset($type) && !$type->get_visible()) {
1606                  $table->rowclasses[] = 'dimmed_text';
1607              } else {
1608                  $table->rowclasses[] = '';
1609              }
1610  
1611              if (!in_array($i->name, $alreadyplugins)) {
1612                  $alreadyplugins[] = $i->name;
1613              }
1614          }
1615          $output .= html_writer::table($table);
1616          $instancehtml = '<div>';
1617          $addable = 0;
1618  
1619          //if no type is set, we can create all type of instance
1620          if (!$typename) {
1621              $instancehtml .= '<h3>';
1622              $instancehtml .= get_string('createrepository', 'repository');
1623              $instancehtml .= '</h3><ul>';
1624              $types = repository::get_editable_types($context);
1625              foreach ($types as $type) {
1626                  if (!empty($type) && $type->get_visible()) {
1627                      // If the user does not have the permission to view the repository, it won't be displayed in
1628                      // the list of instances. Hiding the link to create new instances will prevent the
1629                      // user from creating them without being able to find them afterwards, which looks like a bug.
1630                      if (!has_capability('repository/'.$type->get_typename().':view', $context)) {
1631                          continue;
1632                      }
1633                      $instanceoptionnames = repository::static_function($type->get_typename(), 'get_instance_option_names');
1634                      if (!empty($instanceoptionnames)) {
1635                          $baseurl->param('new', $type->get_typename());
1636                          $instancehtml .= '<li><a href="'.$baseurl->out().'">'.get_string('createxxinstance', 'repository', get_string('pluginname', 'repository_'.$type->get_typename())).  '</a></li>';
1637                          $baseurl->remove_params('new');
1638                          $addable++;
1639                      }
1640                  }
1641              }
1642              $instancehtml .= '</ul>';
1643  
1644          } else {
1645              $instanceoptionnames = repository::static_function($typename, 'get_instance_option_names');
1646              if (!empty($instanceoptionnames)) {   //create a unique type of instance
1647                  $addable = 1;
1648                  $baseurl->param('new', $typename);
1649                  $output .= $OUTPUT->single_button($baseurl, get_string('createinstance', 'repository'), 'get');
1650                  $baseurl->remove_params('new');
1651              }
1652          }
1653  
1654          if ($addable) {
1655              $instancehtml .= '</div>';
1656              $output .= $instancehtml;
1657          }
1658  
1659          $output .= $OUTPUT->box_end();
1660  
1661          //print the list + creation links
1662          print($output);
1663      }
1664  
1665      /**
1666       * Prepare file reference information
1667       *
1668       * @param string $source source of the file, returned by repository as 'source' and received back from user (not cleaned)
1669       * @return string file reference, ready to be stored
1670       */
1671      public function get_file_reference($source) {
1672          if ($source && $this->has_moodle_files()) {
1673              $params = @json_decode(base64_decode($source), true);
1674              if (!is_array($params) || empty($params['contextid'])) {
1675                  throw new repository_exception('invalidparams', 'repository');
1676              }
1677              $params = array(
1678                  'component' => empty($params['component']) ? ''   : clean_param($params['component'], PARAM_COMPONENT),
1679                  'filearea'  => empty($params['filearea'])  ? ''   : clean_param($params['filearea'], PARAM_AREA),
1680                  'itemid'    => empty($params['itemid'])    ? 0    : clean_param($params['itemid'], PARAM_INT),
1681                  'filename'  => empty($params['filename'])  ? null : clean_param($params['filename'], PARAM_FILE),
1682                  'filepath'  => empty($params['filepath'])  ? null : clean_param($params['filepath'], PARAM_PATH),
1683                  'contextid' => clean_param($params['contextid'], PARAM_INT)
1684              );
1685              // Check if context exists.
1686              if (!context::instance_by_id($params['contextid'], IGNORE_MISSING)) {
1687                  throw new repository_exception('invalidparams', 'repository');
1688              }
1689              return file_storage::pack_reference($params);
1690          }
1691          return $source;
1692      }
1693  
1694      /**
1695       * Decide where to save the file, can be overwriten by subclass
1696       *
1697       * @param string $filename file name
1698       * @return file path
1699       */
1700      public function prepare_file($filename) {
1701          global $CFG;
1702          $dir = make_temp_directory('download/'.get_class($this).'/');
1703          while (empty($filename) || file_exists($dir.$filename)) {
1704              $filename = uniqid('', true).'_'.time().'.tmp';
1705          }
1706          return $dir.$filename;
1707      }
1708  
1709      /**
1710       * Does this repository used to browse moodle files?
1711       *
1712       * @return bool
1713       */
1714      public function has_moodle_files() {
1715          return false;
1716      }
1717  
1718      /**
1719       * Return file URL, for most plugins, the parameter is the original
1720       * url, but some plugins use a file id, so we need this function to
1721       * convert file id to original url.
1722       *
1723       * @param string $url the url of file
1724       * @return string
1725       */
1726      public function get_link($url) {
1727          return $url;
1728      }
1729  
1730      /**
1731       * Downloads a file from external repository and saves it in temp dir
1732       *
1733       * Function get_file() must be implemented by repositories that support returntypes
1734       * FILE_INTERNAL or FILE_REFERENCE. It is invoked to pick up the file and copy it
1735       * to moodle. This function is not called for moodle repositories, the function
1736       * {@link repository::copy_to_area()} is used instead.
1737       *
1738       * This function can be overridden by subclass if the files.reference field contains
1739       * not just URL or if request should be done differently.
1740       *
1741       * @see curl
1742       * @throws file_exception when error occured
1743       *
1744       * @param string $url the content of files.reference field, in this implementaion
1745       * it is asssumed that it contains the string with URL of the file
1746       * @param string $filename filename (without path) to save the downloaded file in the
1747       * temporary directory, if omitted or file already exists the new filename will be generated
1748       * @return array with elements:
1749       *   path: internal location of the file
1750       *   url: URL to the source (from parameters)
1751       */
1752      public function get_file($url, $filename = '') {
1753          global $CFG;
1754  
1755          $path = $this->prepare_file($filename);
1756          $c = new curl;
1757  
1758          $result = $c->download_one($url, null, array('filepath' => $path, 'timeout' => $CFG->repositorygetfiletimeout));
1759          if ($result !== true) {
1760              throw new moodle_exception('errorwhiledownload', 'repository', '', $result);
1761          }
1762          return array('path'=>$path, 'url'=>$url);
1763      }
1764  
1765      /**
1766       * Downloads the file from external repository and saves it in moodle filepool.
1767       * This function is different from {@link repository::sync_reference()} because it has
1768       * bigger request timeout and always downloads the content.
1769       *
1770       * This function is invoked when we try to unlink the file from the source and convert
1771       * a reference into a true copy.
1772       *
1773       * @throws exception when file could not be imported
1774       *
1775       * @param stored_file $file
1776       * @param int $maxbytes throw an exception if file size is bigger than $maxbytes (0 means no limit)
1777       */
1778      public function import_external_file_contents(stored_file $file, $maxbytes = 0) {
1779          if (!$file->is_external_file()) {
1780              // nothing to import if the file is not a reference
1781              return;
1782          } else if ($file->get_repository_id() != $this->id) {
1783              // error
1784              debugging('Repository instance id does not match');
1785              return;
1786          } else if ($this->has_moodle_files()) {
1787              // files that are references to local files are already in moodle filepool
1788              // just validate the size
1789              if ($maxbytes > 0 && $file->get_filesize() > $maxbytes) {
1790                  throw new file_exception('maxbytes');
1791              }
1792              return;
1793          } else {
1794              if ($maxbytes > 0 && $file->get_filesize() > $maxbytes) {
1795                  // note that stored_file::get_filesize() also calls synchronisation
1796                  throw new file_exception('maxbytes');
1797              }
1798              $fs = get_file_storage();
1799              $contentexists = $fs->content_exists($file->get_contenthash());
1800              if ($contentexists && $file->get_filesize() && $file->get_contenthash() === sha1('')) {
1801                  // even when 'file_storage::content_exists()' returns true this may be an empty
1802                  // content for the file that was not actually downloaded
1803                  $contentexists = false;
1804              }
1805              if (!$file->get_status() && $contentexists) {
1806                  // we already have the content in moodle filepool and it was synchronised recently.
1807                  // Repositories may overwrite it if they want to force synchronisation anyway!
1808                  return;
1809              } else {
1810                  // attempt to get a file
1811                  try {
1812                      $fileinfo = $this->get_file($file->get_reference());
1813                      if (isset($fileinfo['path'])) {
1814                          list($contenthash, $filesize, $newfile) = $fs->add_file_to_pool($fileinfo['path']);
1815                          // set this file and other similar aliases synchronised
1816                          $file->set_synchronized($contenthash, $filesize);
1817                      } else {
1818                          throw new moodle_exception('errorwhiledownload', 'repository', '', '');
1819                      }
1820                  } catch (Exception $e) {
1821                      if ($contentexists) {
1822                          // better something than nothing. We have a copy of file. It's sync time
1823                          // has expired but it is still very likely that it is the last version
1824                      } else {
1825                          throw($e);
1826                      }
1827                  }
1828              }
1829          }
1830      }
1831  
1832      /**
1833       * Return size of a file in bytes.
1834       *
1835       * @param string $source encoded and serialized data of file
1836       * @return int file size in bytes
1837       */
1838      public function get_file_size($source) {
1839          // TODO MDL-33297 remove this function completely?
1840          $browser    = get_file_browser();
1841          $params     = unserialize(base64_decode($source));
1842          $contextid  = clean_param($params['contextid'], PARAM_INT);
1843          $fileitemid = clean_param($params['itemid'], PARAM_INT);
1844          $filename   = clean_param($params['filename'], PARAM_FILE);
1845          $filepath   = clean_param($params['filepath'], PARAM_PATH);
1846          $filearea   = clean_param($params['filearea'], PARAM_AREA);
1847          $component  = clean_param($params['component'], PARAM_COMPONENT);
1848          $context    = context::instance_by_id($contextid);
1849          $file_info  = $browser->get_file_info($context, $component, $filearea, $fileitemid, $filepath, $filename);
1850          if (!empty($file_info)) {
1851              $filesize = $file_info->get_filesize();
1852          } else {
1853              $filesize = null;
1854          }
1855          return $filesize;
1856      }
1857  
1858      /**
1859       * Return is the instance is visible
1860       * (is the type visible ? is the context enable ?)
1861       *
1862       * @return bool
1863       */
1864      public function is_visible() {
1865          $type = repository::get_type_by_id($this->options['typeid']);
1866          $instanceoptions = repository::static_function($type->get_typename(), 'get_instance_option_names');
1867  
1868          if ($type->get_visible()) {
1869              //if the instance is unique so it's visible, otherwise check if the instance has a enabled context
1870              if (empty($instanceoptions) || $type->get_contextvisibility(context::instance_by_id($this->instance->contextid))) {
1871                  return true;
1872              }
1873          }
1874  
1875          return false;
1876      }
1877  
1878      /**
1879       * Can the instance be edited by the current user?
1880       *
1881       * The property $readonly must not be used within this method because
1882       * it only controls if the options from self::get_instance_option_names()
1883       * can be edited.
1884       *
1885       * @return bool true if the user can edit the instance.
1886       * @since Moodle 2.5
1887       */
1888      public final function can_be_edited_by_user() {
1889          global $USER;
1890  
1891          // We need to be able to explore the repository.
1892          try {
1893              $this->check_capability();
1894          } catch (repository_exception $e) {
1895              return false;
1896          }
1897  
1898          $repocontext = context::instance_by_id($this->instance->contextid);
1899          if ($repocontext->contextlevel == CONTEXT_USER && $repocontext->instanceid != $USER->id) {
1900              // If the context of this instance is a user context, we need to be this user.
1901              return false;
1902          } else if ($repocontext->contextlevel == CONTEXT_MODULE && !has_capability('moodle/course:update', $repocontext)) {
1903              // We need to have permissions on the course to edit the instance.
1904              return false;
1905          } else if ($repocontext->contextlevel == CONTEXT_SYSTEM && !has_capability('moodle/site:config', $repocontext)) {
1906              // Do not meet the requirements for the context system.
1907              return false;
1908          }
1909  
1910          return true;
1911      }
1912  
1913      /**
1914       * Return the name of this instance, can be overridden.
1915       *
1916       * @return string
1917       */
1918      public function get_name() {
1919          if ($name = $this->instance->name) {
1920              return $name;
1921          } else {
1922              return get_string('pluginname', 'repository_' . $this->get_typename());
1923          }
1924      }
1925  
1926      /**
1927       * Is this repository accessing private data?
1928       *
1929       * This function should return true for the repositories which access external private
1930       * data from a user. This is the case for repositories such as Dropbox, Google Docs or Box.net
1931       * which authenticate the user and then store the auth token.
1932       *
1933       * Of course, many repositories store 'private data', but we only want to set
1934       * contains_private_data() to repositories which are external to Moodle and shouldn't be accessed
1935       * to by the users having the capability to 'login as' someone else. For instance, the repository
1936       * 'Private files' is not considered as private because it's part of Moodle.
1937       *
1938       * You should not set contains_private_data() to true on repositories which allow different types
1939       * of instances as the levels other than 'user' are, by definition, not private. Also
1940       * the user instances will be protected when they need to.
1941       *
1942       * @return boolean True when the repository accesses private external data.
1943       * @since  Moodle 2.5
1944       */
1945      public function contains_private_data() {
1946          return true;
1947      }
1948  
1949      /**
1950       * What kind of files will be in this repository?
1951       *
1952       * @return array return '*' means this repository support any files, otherwise
1953       *               return mimetypes of files, it can be an array
1954       */
1955      public function supported_filetypes() {
1956          // return array('text/plain', 'image/gif');
1957          return '*';
1958      }
1959  
1960      /**
1961       * Tells how the file can be picked from this repository
1962       *
1963       * Maximum value is FILE_INTERNAL | FILE_EXTERNAL | FILE_REFERENCE
1964       *
1965       * @return int
1966       */
1967      public function supported_returntypes() {
1968          return (FILE_INTERNAL | FILE_EXTERNAL);
1969      }
1970  
1971      /**
1972       * Provide repository instance information for Ajax
1973       *
1974       * @return stdClass
1975       */
1976      final public function get_meta() {
1977          global $CFG, $OUTPUT;
1978          $meta = new stdClass();
1979          $meta->id   = $this->id;
1980          $meta->name = format_string($this->get_name());
1981          $meta->type = $this->get_typename();
1982          $meta->icon = $OUTPUT->pix_url('icon', 'repository_'.$meta->type)->out(false);
1983          $meta->supported_types = file_get_typegroup('extension', $this->supported_filetypes());
1984          $meta->return_types = $this->supported_returntypes();
1985          $meta->sortorder = $this->options['sortorder'];
1986          return $meta;
1987      }
1988  
1989      /**
1990       * Create an instance for this plug-in
1991       *
1992       * @static
1993       * @param string $type the type of the repository
1994       * @param int $userid the user id
1995       * @param stdClass $context the context
1996       * @param array $params the options for this instance
1997       * @param int $readonly whether to create it readonly or not (defaults to not)
1998       * @return mixed
1999       */
2000      public static function create($type, $userid, $context, $params, $readonly=0) {
2001          global $CFG, $DB;
2002          $params = (array)$params;
2003          require_once($CFG->dirroot . '/repository/'. $type . '/lib.php');
2004          $classname = 'repository_' . $type;
2005          if ($repo = $DB->get_record('repository', array('type'=>$type))) {
2006              $record = new stdClass();
2007              $record->name = $params['name'];
2008              $record->typeid = $repo->id;
2009              $record->timecreated  = time();
2010              $record->timemodified = time();
2011              $record->contextid = $context->id;
2012              $record->readonly = $readonly;
2013              $record->userid    = $userid;
2014              $id = $DB->insert_record('repository_instances', $record);
2015              cache::make('core', 'repositories')->purge();
2016              $options = array();
2017              $configs = call_user_func($classname . '::get_instance_option_names');
2018              if (!empty($configs)) {
2019                  foreach ($configs as $config) {
2020                      if (isset($params[$config])) {
2021                          $options[$config] = $params[$config];
2022                      } else {
2023                          $options[$config] = null;
2024                      }
2025                  }
2026              }
2027  
2028              if (!empty($id)) {
2029                  unset($options['name']);
2030                  $instance = repository::get_instance($id);
2031                  $instance->set_option($options);
2032                  return $id;
2033              } else {
2034                  return null;
2035              }
2036          } else {
2037              return null;
2038          }
2039      }
2040  
2041      /**
2042       * delete a repository instance
2043       *
2044       * @param bool $downloadcontents
2045       * @return bool
2046       */
2047      final public function delete($downloadcontents = false) {
2048          global $DB;
2049          if ($downloadcontents) {
2050              $this->convert_references_to_local();
2051          }
2052          cache::make('core', 'repositories')->purge();
2053          try {
2054              $DB->delete_records('repository_instances', array('id'=>$this->id));
2055              $DB->delete_records('repository_instance_config', array('instanceid'=>$this->id));
2056          } catch (dml_exception $ex) {
2057              return false;
2058          }
2059          return true;
2060      }
2061  
2062      /**
2063       * Delete all the instances associated to a context.
2064       *
2065       * This method is intended to be a callback when deleting
2066       * a course or a user to delete all the instances associated
2067       * to their context. The usual way to delete a single instance
2068       * is to use {@link self::delete()}.
2069       *
2070       * @param int $contextid context ID.
2071       * @param boolean $downloadcontents true to convert references to hard copies.
2072       * @return void
2073       */
2074      final public static function delete_all_for_context($contextid, $downloadcontents = true) {
2075          global $DB;
2076          $repoids = $DB->get_fieldset_select('repository_instances', 'id', 'contextid = :contextid', array('contextid' => $contextid));
2077          if ($downloadcontents) {
2078              foreach ($repoids as $repoid) {
2079                  $repo = repository::get_repository_by_id($repoid, $contextid);
2080                  $repo->convert_references_to_local();
2081              }
2082          }
2083          cache::make('core', 'repositories')->purge();
2084          $DB->delete_records_list('repository_instances', 'id', $repoids);
2085          $DB->delete_records_list('repository_instance_config', 'instanceid', $repoids);
2086      }
2087  
2088      /**
2089       * Hide/Show a repository
2090       *
2091       * @param string $hide
2092       * @return bool
2093       */
2094      final public function hide($hide = 'toggle') {
2095          global $DB;
2096          if ($entry = $DB->get_record('repository', array('id'=>$this->id))) {
2097              if ($hide === 'toggle' ) {
2098                  if (!empty($entry->visible)) {
2099                      $entry->visible = 0;
2100                  } else {
2101                      $entry->visible = 1;
2102                  }
2103              } else {
2104                  if (!empty($hide)) {
2105                      $entry->visible = 0;
2106                  } else {
2107                      $entry->visible = 1;
2108                  }
2109              }
2110              return $DB->update_record('repository', $entry);
2111          }
2112          return false;
2113      }
2114  
2115      /**
2116       * Save settings for repository instance
2117       * $repo->set_option(array('api_key'=>'f2188bde132', 'name'=>'dongsheng'));
2118       *
2119       * @param array $options settings
2120       * @return bool
2121       */
2122      public function set_option($options = array()) {
2123          global $DB;
2124  
2125          if (!empty($options['name'])) {
2126              $r = new stdClass();
2127              $r->id   = $this->id;
2128              $r->name = $options['name'];
2129              $DB->update_record('repository_instances', $r);
2130              unset($options['name']);
2131          }
2132          foreach ($options as $name=>$value) {
2133              if ($id = $DB->get_field('repository_instance_config', 'id', array('name'=>$name, 'instanceid'=>$this->id))) {
2134                  $DB->set_field('repository_instance_config', 'value', $value, array('id'=>$id));
2135              } else {
2136                  $config = new stdClass();
2137                  $config->instanceid = $this->id;
2138                  $config->name   = $name;
2139                  $config->value  = $value;
2140                  $DB->insert_record('repository_instance_config', $config);
2141              }
2142          }
2143          cache::make('core', 'repositories')->purge();
2144          return true;
2145      }
2146  
2147      /**
2148       * Get settings for repository instance.
2149       *
2150       * @param string $config a specific option to get.
2151       * @return mixed returns an array of options. If $config is not empty, then it returns that option,
2152       *               or null if the option does not exist.
2153       */
2154      public function get_option($config = '') {
2155          global $DB;
2156          $cache = cache::make('core', 'repositories');
2157          if (($entries = $cache->get('ops:'. $this->id)) === false) {
2158              $entries = $DB->get_records('repository_instance_config', array('instanceid' => $this->id));
2159              $cache->set('ops:'. $this->id, $entries);
2160          }
2161  
2162          $ret = array();
2163          foreach($entries as $entry) {
2164              $ret[$entry->name] = $entry->value;
2165          }
2166  
2167          if (!empty($config)) {
2168              if (isset($ret[$config])) {
2169                  return $ret[$config];
2170              } else {
2171                  return null;
2172              }
2173          } else {
2174              return $ret;
2175          }
2176      }
2177  
2178      /**
2179       * Filter file listing to display specific types
2180       *
2181       * @param array $value
2182       * @return bool
2183       */
2184      public function filter(&$value) {
2185          $accepted_types = optional_param_array('accepted_types', '', PARAM_RAW);
2186          if (isset($value['children'])) {
2187              if (!empty($value['children'])) {
2188                  $value['children'] = array_filter($value['children'], array($this, 'filter'));
2189              }
2190              return true; // always return directories
2191          } else {
2192              if ($accepted_types == '*' or empty($accepted_types)
2193                  or (is_array($accepted_types) and in_array('*', $accepted_types))) {
2194                  return true;
2195              } else {
2196                  foreach ($accepted_types as $ext) {
2197                      if (preg_match('#'.$ext.'$#i', $value['title'])) {
2198                          return true;
2199                      }
2200                  }
2201              }
2202          }
2203          return false;
2204      }
2205  
2206      /**
2207       * Given a path, and perhaps a search, get a list of files.
2208       *
2209       * See details on {@link http://docs.moodle.org/dev/Repository_plugins}
2210       *
2211       * @param string $path this parameter can a folder name, or a identification of folder
2212       * @param string $page the page number of file list
2213       * @return array the list of files, including meta infomation, containing the following keys
2214       *           manage, url to manage url
2215       *           client_id
2216       *           login, login form
2217       *           repo_id, active repository id
2218       *           login_btn_action, the login button action
2219       *           login_btn_label, the login button label
2220       *           total, number of results
2221       *           perpage, items per page
2222       *           page
2223       *           pages, total pages
2224       *           issearchresult, is it a search result?
2225       *           list, file list
2226       *           path, current path and parent path
2227       */
2228      public function get_listing($path = '', $page = '') {
2229      }
2230  
2231  
2232      /**
2233       * Prepare the breadcrumb.
2234       *
2235       * @param array $breadcrumb contains each element of the breadcrumb.
2236       * @return array of breadcrumb elements.
2237       * @since Moodle 2.3.3
2238       */
2239      protected static function prepare_breadcrumb($breadcrumb) {
2240          global $OUTPUT;
2241          $foldericon = $OUTPUT->pix_url(file_folder_icon(24))->out(false);
2242          $len = count($breadcrumb);
2243          for ($i = 0; $i < $len; $i++) {
2244              if (is_array($breadcrumb[$i]) && !isset($breadcrumb[$i]['icon'])) {
2245                  $breadcrumb[$i]['icon'] = $foldericon;
2246              } else if (is_object($breadcrumb[$i]) && !isset($breadcrumb[$i]->icon)) {
2247                  $breadcrumb[$i]->icon = $foldericon;
2248              }
2249          }
2250          return $breadcrumb;
2251      }
2252  
2253      /**
2254       * Prepare the file/folder listing.
2255       *
2256       * @param array $list of files and folders.
2257       * @return array of files and folders.
2258       * @since Moodle 2.3.3
2259       */
2260      protected static function prepare_list($list) {
2261          global $OUTPUT;
2262          $foldericon = $OUTPUT->pix_url(file_folder_icon(24))->out(false);
2263  
2264          // Reset the array keys because non-numeric keys will create an object when converted to JSON.
2265          $list = array_values($list);
2266  
2267          $len = count($list);
2268          for ($i = 0; $i < $len; $i++) {
2269              if (is_object($list[$i])) {
2270                  $file = (array)$list[$i];
2271                  $converttoobject = true;
2272              } else {
2273                  $file =& $list[$i];
2274                  $converttoobject = false;
2275              }
2276              if (isset($file['size'])) {
2277                  $file['size'] = (int)$file['size'];
2278                  $file['size_f'] = display_size($file['size']);
2279              }
2280              if (isset($file['license']) && get_string_manager()->string_exists($file['license'], 'license')) {
2281                  $file['license_f'] = get_string($file['license'], 'license');
2282              }
2283              if (isset($file['image_width']) && isset($file['image_height'])) {
2284                  $a = array('width' => $file['image_width'], 'height' => $file['image_height']);
2285                  $file['dimensions'] = get_string('imagesize', 'repository', (object)$a);
2286              }
2287              foreach (array('date', 'datemodified', 'datecreated') as $key) {
2288                  if (!isset($file[$key]) && isset($file['date'])) {
2289                      $file[$key] = $file['date'];
2290                  }
2291                  if (isset($file[$key])) {
2292                      // must be UNIX timestamp
2293                      $file[$key] = (int)$file[$key];
2294                      if (!$file[$key]) {
2295                          unset($file[$key]);
2296                      } else {
2297                          $file[$key.'_f'] = userdate($file[$key], get_string('strftimedatetime', 'langconfig'));
2298                          $file[$key.'_f_s'] = userdate($file[$key], get_string('strftimedatetimeshort', 'langconfig'));
2299                      }
2300                  }
2301              }
2302              $isfolder = (array_key_exists('children', $file) || (isset($file['type']) && $file['type'] == 'folder'));
2303              $filename = null;
2304              if (isset($file['title'])) {
2305                  $filename = $file['title'];
2306              }
2307              else if (isset($file['fullname'])) {
2308                  $filename = $file['fullname'];
2309              }
2310              if (!isset($file['mimetype']) && !$isfolder && $filename) {
2311                  $file['mimetype'] = get_mimetype_description(array('filename' => $filename));
2312              }
2313              if (!isset($file['icon'])) {
2314                  if ($isfolder) {
2315                      $file['icon'] = $foldericon;
2316                  } else if ($filename) {
2317                      $file['icon'] = $OUTPUT->pix_url(file_extension_icon($filename, 24))->out(false);
2318                  }
2319              }
2320  
2321              // Recursively loop over children.
2322              if (isset($file['children'])) {
2323                  $file['children'] = self::prepare_list($file['children']);
2324              }
2325  
2326              // Convert the array back to an object.
2327              if ($converttoobject) {
2328                  $list[$i] = (object)$file;
2329              }
2330          }
2331          return $list;
2332      }
2333  
2334      /**
2335       * Prepares list of files before passing it to AJAX, makes sure data is in the correct
2336       * format and stores formatted values.
2337       *
2338       * @param array|stdClass $listing result of get_listing() or search() or file_get_drafarea_files()
2339       * @return array
2340       */
2341      public static function prepare_listing($listing) {
2342          $wasobject = false;
2343          if (is_object($listing)) {
2344              $listing = (array) $listing;
2345              $wasobject = true;
2346          }
2347  
2348          // Prepare the breadcrumb, passed as 'path'.
2349          if (isset($listing['path']) && is_array($listing['path'])) {
2350              $listing['path'] = self::prepare_breadcrumb($listing['path']);
2351          }
2352  
2353          // Prepare the listing of objects.
2354          if (isset($listing['list']) && is_array($listing['list'])) {
2355              $listing['list'] = self::prepare_list($listing['list']);
2356          }
2357  
2358          // Convert back to an object.
2359          if ($wasobject) {
2360              $listing = (object) $listing;
2361          }
2362          return $listing;
2363      }
2364  
2365      /**
2366       * Search files in repository
2367       * When doing global search, $search_text will be used as
2368       * keyword.
2369       *
2370       * @param string $search_text search key word
2371       * @param int $page page
2372       * @return mixed see {@link repository::get_listing()}
2373       */
2374      public function search($search_text, $page = 0) {
2375          $list = array();
2376          $list['list'] = array();
2377          return false;
2378      }
2379  
2380      /**
2381       * Logout from repository instance
2382       * By default, this function will return a login form
2383       *
2384       * @return string
2385       */
2386      public function logout(){
2387          return $this->print_login();
2388      }
2389  
2390      /**
2391       * To check whether the user is logged in.
2392       *
2393       * @return bool
2394       */
2395      public function check_login(){
2396          return true;
2397      }
2398  
2399  
2400      /**
2401       * Show the login screen, if required
2402       *
2403       * @return string
2404       */
2405      public function print_login(){
2406          return $this->get_listing();
2407      }
2408  
2409      /**
2410       * Show the search screen, if required
2411       *
2412       * @return string
2413       */
2414      public function print_search() {
2415          global $PAGE;
2416          $renderer = $PAGE->get_renderer('core', 'files');
2417          return $renderer->repository_default_searchform();
2418      }
2419  
2420      /**
2421       * For oauth like external authentication, when external repository direct user back to moodle,
2422       * this function will be called to set up token and token_secret
2423       */
2424      public function callback() {
2425      }
2426  
2427      /**
2428       * is it possible to do glboal search?
2429       *
2430       * @return bool
2431       */
2432      public function global_search() {
2433          return false;
2434      }
2435  
2436      /**
2437       * Defines operations that happen occasionally on cron
2438       *
2439       * @return bool
2440       */
2441      public function cron() {
2442          return true;
2443      }
2444  
2445      /**
2446       * function which is run when the type is created (moodle administrator add the plugin)
2447       *
2448       * @return bool success or fail?
2449       */
2450      public static function plugin_init() {
2451          return true;
2452      }
2453  
2454      /**
2455       * Edit/Create Admin Settings Moodle form
2456       *
2457       * @param moodleform $mform Moodle form (passed by reference)
2458       * @param string $classname repository class name
2459       */
2460      public static function type_config_form($mform, $classname = 'repository') {
2461          $instnaceoptions = call_user_func(array($classname, 'get_instance_option_names'), $mform, $classname);
2462          if (empty($instnaceoptions)) {
2463              // this plugin has only one instance
2464              // so we need to give it a name
2465              // it can be empty, then moodle will look for instance name from language string
2466              $mform->addElement('text', 'pluginname', get_string('pluginname', 'repository'), array('size' => '40'));
2467              $mform->addElement('static', 'pluginnamehelp', '', get_string('pluginnamehelp', 'repository'));
2468              $mform->setType('pluginname', PARAM_TEXT);
2469          }
2470      }
2471  
2472      /**
2473       * Validate Admin Settings Moodle form
2474       *
2475       * @static
2476       * @param moodleform $mform Moodle form (passed by reference)
2477       * @param array $data array of ("fieldname"=>value) of submitted data
2478       * @param array $errors array of ("fieldname"=>errormessage) of errors
2479       * @return array array of errors
2480       */
2481      public static function type_form_validation($mform, $data, $errors) {
2482          return $errors;
2483      }
2484  
2485  
2486      /**
2487       * Edit/Create Instance Settings Moodle form
2488       *
2489       * @param moodleform $mform Moodle form (passed by reference)
2490       */
2491      public static function instance_config_form($mform) {
2492      }
2493  
2494      /**
2495       * Return names of the general options.
2496       * By default: no general option name
2497       *
2498       * @return array
2499       */
2500      public static function get_type_option_names() {
2501          return array('pluginname');
2502      }
2503  
2504      /**
2505       * Return names of the instance options.
2506       * By default: no instance option name
2507       *
2508       * @return array
2509       */
2510      public static function get_instance_option_names() {
2511          return array();
2512      }
2513  
2514      /**
2515       * Validate repository plugin instance form
2516       *
2517       * @param moodleform $mform moodle form
2518       * @param array $data form data
2519       * @param array $errors errors
2520       * @return array errors
2521       */
2522      public static function instance_form_validation($mform, $data, $errors) {
2523          return $errors;
2524      }
2525  
2526      /**
2527       * Create a shorten filename
2528       *
2529       * @param string $str filename
2530       * @param int $maxlength max file name length
2531       * @return string short filename
2532       */
2533      public function get_short_filename($str, $maxlength) {
2534          if (core_text::strlen($str) >= $maxlength) {
2535              return trim(core_text::substr($str, 0, $maxlength)).'...';
2536          } else {
2537              return $str;
2538          }
2539      }
2540  
2541      /**
2542       * Overwrite an existing file
2543       *
2544       * @param int $itemid
2545       * @param string $filepath
2546       * @param string $filename
2547       * @param string $newfilepath
2548       * @param string $newfilename
2549       * @return bool
2550       */
2551      public static function overwrite_existing_draftfile($itemid, $filepath, $filename, $newfilepath, $newfilename) {
2552          global $USER;
2553          $fs = get_file_storage();
2554          $user_context = context_user::instance($USER->id);
2555          if ($file = $fs->get_file($user_context->id, 'user', 'draft', $itemid, $filepath, $filename)) {
2556              if ($tempfile = $fs->get_file($user_context->id, 'user', 'draft', $itemid, $newfilepath, $newfilename)) {
2557                  // Remember original file source field.
2558                  $source = @unserialize($file->get_source());
2559                  // Remember the original sortorder.
2560                  $sortorder = $file->get_sortorder();
2561                  if ($tempfile->is_external_file()) {
2562                      // New file is a reference. Check that existing file does not have any other files referencing to it
2563                      if (isset($source->original) && $fs->search_references_count($source->original)) {
2564                          return (object)array('error' => get_string('errordoublereference', 'repository'));
2565                      }
2566                  }
2567                  // delete existing file to release filename
2568                  $file->delete();
2569                  // create new file
2570                  $newfile = $fs->create_file_from_storedfile(array('filepath'=>$filepath, 'filename'=>$filename), $tempfile);
2571                  // Preserve original file location (stored in source field) for handling references
2572                  if (isset($source->original)) {
2573                      if (!($newfilesource = @unserialize($newfile->get_source()))) {
2574                          $newfilesource = new stdClass();
2575                      }
2576                      $newfilesource->original = $source->original;
2577                      $newfile->set_source(serialize($newfilesource));
2578                  }
2579                  $newfile->set_sortorder($sortorder);
2580                  // remove temp file
2581                  $tempfile->delete();
2582                  return true;
2583              }
2584          }
2585          return false;
2586      }
2587  
2588      /**
2589       * Updates a file in draft filearea.
2590       *
2591       * This function can only update fields filepath, filename, author, license.
2592       * If anything (except filepath) is updated, timemodified is set to current time.
2593       * If filename or filepath is updated the file unconnects from it's origin
2594       * and therefore all references to it will be converted to copies when
2595       * filearea is saved.
2596       *
2597       * @param int $draftid
2598       * @param string $filepath path to the directory containing the file, or full path in case of directory
2599       * @param string $filename name of the file, or '.' in case of directory
2600       * @param array $updatedata array of fields to change (only filename, filepath, license and/or author can be updated)
2601       * @throws moodle_exception if for any reason file can not be updated (file does not exist, target already exists, etc.)
2602       */
2603      public static function update_draftfile($draftid, $filepath, $filename, $updatedata) {
2604          global $USER;
2605          $fs = get_file_storage();
2606          $usercontext = context_user::instance($USER->id);
2607          // make sure filename and filepath are present in $updatedata
2608          $updatedata = $updatedata + array('filepath' => $filepath, 'filename' => $filename);
2609          $filemodified = false;
2610          if (!$file = $fs->get_file($usercontext->id, 'user', 'draft', $draftid, $filepath, $filename)) {
2611              if ($filename === '.') {
2612                  throw new moodle_exception('foldernotfound', 'repository');
2613              } else {
2614                  throw new moodle_exception('filenotfound', 'error');
2615              }
2616          }
2617          if (!$file->is_directory()) {
2618              // This is a file
2619              if ($updatedata['filepath'] !== $filepath || $updatedata['filename'] !== $filename) {
2620                  // Rename/move file: check that target file name does not exist.
2621                  if ($fs->file_exists($usercontext->id, 'user', 'draft', $draftid, $updatedata['filepath'], $updatedata['filename'])) {
2622                      throw new moodle_exception('fileexists', 'repository');
2623                  }
2624                  if (($filesource = @unserialize($file->get_source())) && isset($filesource->original)) {
2625                      unset($filesource->original);
2626                      $file->set_source(serialize($filesource));
2627                  }
2628                  $file->rename($updatedata['filepath'], $updatedata['filename']);
2629                  // timemodified is updated only when file is renamed and not updated when file is moved.
2630                  $filemodified = $filemodified || ($updatedata['filename'] !== $filename);
2631              }
2632              if (array_key_exists('license', $updatedata) && $updatedata['license'] !== $file->get_license()) {
2633                  // Update license and timemodified.
2634                  $file->set_license($updatedata['license']);
2635                  $filemodified = true;
2636              }
2637              if (array_key_exists('author', $updatedata) && $updatedata['author'] !== $file->get_author()) {
2638                  // Update author and timemodified.
2639                  $file->set_author($updatedata['author']);
2640                  $filemodified = true;
2641              }
2642              // Update timemodified:
2643              if ($filemodified) {
2644                  $file->set_timemodified(time());
2645              }
2646          } else {
2647              // This is a directory - only filepath can be updated for a directory (it was moved).
2648              if ($updatedata['filepath'] === $filepath) {
2649                  // nothing to update
2650                  return;
2651              }
2652              if ($fs->file_exists($usercontext->id, 'user', 'draft', $draftid, $updatedata['filepath'], '.')) {
2653                  // bad luck, we can not rename if something already exists there
2654                  throw new moodle_exception('folderexists', 'repository');
2655              }
2656              $xfilepath = preg_quote($filepath, '|');
2657              if (preg_match("|^$xfilepath|", $updatedata['filepath'])) {
2658                  // we can not move folder to it's own subfolder
2659                  throw new moodle_exception('folderrecurse', 'repository');
2660              }
2661  
2662              // If directory changed the name, update timemodified.
2663              $filemodified = (basename(rtrim($file->get_filepath(), '/')) !== basename(rtrim($updatedata['filepath'], '/')));
2664  
2665              // Now update directory and all children.
2666              $files = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftid);
2667              foreach ($files as $f) {
2668                  if (preg_match("|^$xfilepath|", $f->get_filepath())) {
2669                      $path = preg_replace("|^$xfilepath|", $updatedata['filepath'], $f->get_filepath());
2670                      if (($filesource = @unserialize($f->get_source())) && isset($filesource->original)) {
2671                          // unset original so the references are not shown any more
2672                          unset($filesource->original);
2673                          $f->set_source(serialize($filesource));
2674                      }
2675                      $f->rename($path, $f->get_filename());
2676                      if ($filemodified && $f->get_filepath() === $updatedata['filepath'] && $f->get_filename() === $filename) {
2677                          $f->set_timemodified(time());
2678                      }
2679                  }
2680              }
2681          }
2682      }
2683  
2684      /**
2685       * Delete a temp file from draft area
2686       *
2687       * @param int $draftitemid
2688       * @param string $filepath
2689       * @param string $filename
2690       * @return bool
2691       */
2692      public static function delete_tempfile_from_draft($draftitemid, $filepath, $filename) {
2693          global $USER;
2694          $fs = get_file_storage();
2695          $user_context = context_user::instance($USER->id);
2696          if ($file = $fs->get_file($user_context->id, 'user', 'draft', $draftitemid, $filepath, $filename)) {
2697              $file->delete();
2698              return true;
2699          } else {
2700              return false;
2701          }
2702      }
2703  
2704      /**
2705       * Find all external files in this repo and import them
2706       */
2707      public function convert_references_to_local() {
2708          $fs = get_file_storage();
2709          $files = $fs->get_external_files($this->id);
2710          foreach ($files as $storedfile) {
2711              $fs->import_external_file($storedfile);
2712          }
2713      }
2714  
2715      /**
2716       * Method deprecated, cache is handled by MUC now.
2717       * @deprecated since 2.6
2718       */
2719      public static function reset_caches() {
2720          debugging('Function repository::reset_caches() is deprecated.', DEBUG_DEVELOPER);
2721      }
2722  
2723      /**
2724       * Method deprecated
2725       * @deprecated since 2.6
2726       * @see repository::sync_reference()
2727       */
2728      public static function sync_external_file($file, $resetsynchistory = false) {
2729          debugging('Function repository::sync_external_file() is deprecated.',
2730                  DEBUG_DEVELOPER);
2731          if ($resetsynchistory || !$file || !$file->get_repository_id() ||
2732                  !($repository = self::get_repository_by_id($file->get_repository_id(), SYSCONTEXTID))) {
2733              return false;
2734          }
2735          return $repository->sync_reference($file);
2736      }
2737  
2738      /**
2739       * Performs synchronisation of an external file if the previous one has expired.
2740       *
2741       * This function must be implemented for external repositories supporting
2742       * FILE_REFERENCE, it is called for existing aliases when their filesize,
2743       * contenthash or timemodified are requested. It is not called for internal
2744       * repositories (see {@link repository::has_moodle_files()}), references to
2745       * internal files are updated immediately when source is modified.
2746       *
2747       * Referenced files may optionally keep their content in Moodle filepool (for
2748       * thumbnail generation or to be able to serve cached copy). In this
2749       * case both contenthash and filesize need to be synchronized. Otherwise repositories
2750       * should use contenthash of empty file and correct filesize in bytes.
2751       *
2752       * Note that this function may be run for EACH file that needs to be synchronised at the
2753       * moment. If anything is being downloaded or requested from external sources there
2754       * should be a small timeout. The synchronisation is performed to update the size of
2755       * the file and/or to update image and re-generated image preview. There is nothing
2756       * fatal if syncronisation fails but it is fatal if syncronisation takes too long
2757       * and hangs the script generating a page.
2758       *
2759       * Note: If you wish to call $file->get_filesize(), $file->get_contenthash() or
2760       * $file->get_timemodified() make sure that recursion does not happen.
2761       *
2762       * Called from {@link stored_file::sync_external_file()}
2763       *
2764       * @uses stored_file::set_missingsource()
2765       * @uses stored_file::set_synchronized()
2766       * @param stored_file $file
2767       * @return bool false when file does not need synchronisation, true if it was synchronised
2768       */
2769      public function sync_reference(stored_file $file) {
2770          if ($file->get_repository_id() != $this->id) {
2771              // This should not really happen because the function can be called from stored_file only.
2772              return false;
2773          }
2774  
2775          if ($this->has_moodle_files()) {
2776              // References to local files need to be synchronised only once.
2777              // Later they will be synchronised automatically when the source is changed.
2778              if ($file->get_referencelastsync()) {
2779                  return false;
2780              }
2781              $fs = get_file_storage();
2782              $params = file_storage::unpack_reference($file->get_reference(), true);
2783              if (!is_array($params) || !($storedfile = $fs->get_file($params['contextid'],
2784                      $params['component'], $params['filearea'], $params['itemid'], $params['filepath'],
2785                      $params['filename']))) {
2786                  $file->set_missingsource();
2787              } else {
2788                  $file->set_synchronized($storedfile->get_contenthash(), $storedfile->get_filesize());
2789              }
2790              return true;
2791          }
2792  
2793          // Backward compatibility (Moodle 2.3-2.5) implementation that calls
2794          // methods repository::get_reference_file_lifetime(), repository::sync_individual_file()
2795          // and repository::get_file_by_reference(). These methods are removed from the
2796          // base repository class but may still be implemented by the child classes.
2797  
2798          // THIS IS NOT A GOOD EXAMPLE of implementation. For good examples see the overwriting methods.
2799  
2800          if (!method_exists($this, 'get_file_by_reference')) {
2801              // Function get_file_by_reference() is not implemented. No synchronisation.
2802              return false;
2803          }
2804  
2805          // Check if the previous sync result is still valid.
2806          if (method_exists($this, 'get_reference_file_lifetime')) {
2807              $lifetime = $this->get_reference_file_lifetime($file->get_reference());
2808          } else {
2809              // Default value that was hardcoded in Moodle 2.3 - 2.5.
2810              $lifetime =  60 * 60 * 24;
2811          }
2812          if (($lastsynced = $file->get_referencelastsync()) && $lastsynced + $lifetime >= time()) {
2813              return false;
2814          }
2815  
2816          $cache = cache::make('core', 'repositories');
2817          if (($lastsyncresult = $cache->get('sync:'.$file->get_referencefileid())) !== false) {
2818              if ($lastsyncresult === true) {
2819                  // We are in the process of synchronizing this reference.
2820                  // Avoid recursion when calling $file->get_filesize() and $file->get_contenthash().
2821                  return false;
2822              } else {
2823                  // We have synchronised the same reference inside this request already.
2824                  // It looks like the object $file was created before the synchronisation and contains old data.
2825                  if (!empty($lastsyncresult['missing'])) {
2826                      $file->set_missingsource();
2827                  } else {
2828                      $cache->set('sync:'.$file->get_referencefileid(), true);
2829                      if ($file->get_contenthash() != $lastsyncresult['contenthash'] ||
2830                              $file->get_filesize() != $lastsyncresult['filesize']) {
2831                          $file->set_synchronized($lastsyncresult['contenthash'], $lastsyncresult['filesize']);
2832                      }
2833                      $cache->set('sync:'.$file->get_referencefileid(), $lastsyncresult);
2834                  }
2835                  return true;
2836              }
2837          }
2838  
2839          // Weird function sync_individual_file() that was present in API in 2.3 - 2.5, default value was true.
2840          if (method_exists($this, 'sync_individual_file') && !$this->sync_individual_file($file)) {
2841              return false;
2842          }
2843  
2844          // Set 'true' into the cache to indicate that file is in the process of synchronisation.
2845          $cache->set('sync:'.$file->get_referencefileid(), true);
2846  
2847          // Create object with the structure that repository::get_file_by_reference() expects.
2848          $reference = new stdClass();
2849          $reference->id = $file->get_referencefileid();
2850          $reference->reference = $file->get_reference();
2851          $reference->referencehash = sha1($file->get_reference());
2852          $reference->lastsync = $file->get_referencelastsync();
2853          $reference->lifetime = $lifetime;
2854  
2855          $fileinfo = $this->get_file_by_reference($reference);
2856  
2857          $contenthash = null;
2858          $filesize = null;
2859          $fs = get_file_storage();
2860          if (!empty($fileinfo->filesize)) {
2861              // filesize returned
2862              if (!empty($fileinfo->contenthash) && $fs->content_exists($fileinfo->contenthash)) {
2863                  // contenthash is specified and valid
2864                  $contenthash = $fileinfo->contenthash;
2865              } else if ($fileinfo->filesize == $file->get_filesize()) {
2866                  // we don't know the new contenthash but the filesize did not change,
2867                  // assume the contenthash did not change either
2868                  $contenthash = $file->get_contenthash();
2869              } else {
2870                  // we can't save empty contenthash so generate contenthash from empty string
2871                  list($contenthash, $unused1, $unused2) = $fs->add_string_to_pool('');
2872              }
2873              $filesize = $fileinfo->filesize;
2874          } else if (!empty($fileinfo->filepath)) {
2875              // File path returned
2876              list($contenthash, $filesize, $newfile) = $fs->add_file_to_pool($fileinfo->filepath);
2877          } else if (!empty($fileinfo->handle) && is_resource($fileinfo->handle)) {
2878              // File handle returned
2879              $contents = '';
2880              while (!feof($fileinfo->handle)) {
2881                  $contents .= fread($fileinfo->handle, 8192);
2882              }
2883              fclose($fileinfo->handle);
2884              list($contenthash, $filesize, $newfile) = $fs->add_string_to_pool($contents);
2885          } else if (isset($fileinfo->content)) {
2886              // File content returned
2887              list($contenthash, $filesize, $newfile) = $fs->add_string_to_pool($fileinfo->content);
2888          }
2889  
2890          if (!isset($contenthash) or !isset($filesize)) {
2891              $file->set_missingsource(null);
2892              $cache->set('sync:'.$file->get_referencefileid(), array('missing' => true));
2893          } else {
2894              // update files table
2895              $file->set_synchronized($contenthash, $filesize);
2896              $cache->set('sync:'.$file->get_referencefileid(),
2897                      array('contenthash' => $contenthash, 'filesize' => $filesize));
2898          }
2899  
2900          return true;
2901      }
2902  
2903      /**
2904       * Build draft file's source field
2905       *
2906       * {@link file_restore_source_field_from_draft_file()}
2907       * XXX: This is a hack for file manager (MDL-28666)
2908       * For newly created  draft files we have to construct
2909       * source filed in php serialized data format.
2910       * File manager needs to know the original file information before copying
2911       * to draft area, so we append these information in mdl_files.source field
2912       *
2913       * @param string $source
2914       * @return string serialised source field
2915       */
2916      public static function build_source_field($source) {
2917          $sourcefield = new stdClass;
2918          $sourcefield->source = $source;
2919          return serialize($sourcefield);
2920      }
2921  
2922      /**
2923       * Prepares the repository to be cached. Implements method from cacheable_object interface.
2924       *
2925       * @return array
2926       */
2927      public function prepare_to_cache() {
2928          return array(
2929              'class' => get_class($this),
2930              'id' => $this->id,
2931              'ctxid' => $this->context->id,
2932              'options' => $this->options,
2933              'readonly' => $this->readonly
2934          );
2935      }
2936  
2937      /**
2938       * Restores the repository from cache. Implements method from cacheable_object interface.
2939       *
2940       * @return array
2941       */
2942      public static function wake_from_cache($data) {
2943          $classname = $data['class'];
2944          return new $classname($data['id'], $data['ctxid'], $data['options'], $data['readonly']);
2945      }
2946  
2947      /**
2948       * Gets a file relative to this file in the repository and sends it to the browser.
2949       * Used to allow relative file linking within a repository without creating file records
2950       * for linked files
2951       *
2952       * Repositories that overwrite this must be very careful - see filesystem repository for example.
2953       *
2954       * @param stored_file $mainfile The main file we are trying to access relative files for.
2955       * @param string $relativepath the relative path to the file we are trying to access.
2956       *
2957       */
2958      public function send_relative_file(stored_file $mainfile, $relativepath) {
2959          // This repository hasn't implemented this so send_file_not_found.
2960          send_file_not_found();
2961      }
2962  
2963      /**
2964       * helper function to check if the repository supports send_relative_file.
2965       *
2966       * @return true|false
2967       */
2968      public function supports_relative_file() {
2969          return false;
2970      }
2971  }
2972  
2973  /**
2974   * Exception class for repository api
2975   *
2976   * @since Moodle 2.0
2977   * @package   core_repository
2978   * @copyright 2009 Dongsheng Cai {@link http://dongsheng.org}
2979   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2980   */
2981  class repository_exception extends moodle_exception {
2982  }
2983  
2984  /**
2985   * This is a class used to define a repository instance form
2986   *
2987   * @since Moodle 2.0
2988   * @package   core_repository
2989   * @copyright 2009 Dongsheng Cai {@link http://dongsheng.org}
2990   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2991   */
2992  final class repository_instance_form extends moodleform {
2993      /** @var stdClass repository instance */
2994      protected $instance;
2995      /** @var string repository plugin type */
2996      protected $plugin;
2997  
2998      /**
2999       * Added defaults to moodle form
3000       */
3001      protected function add_defaults() {
3002          $mform =& $this->_form;
3003          $strrequired = get_string('required');
3004  
3005          $mform->addElement('hidden', 'edit',  ($this->instance) ? $this->instance->id : 0);
3006          $mform->setType('edit', PARAM_INT);
3007          $mform->addElement('hidden', 'new',   $this->plugin);
3008          $mform->setType('new', PARAM_ALPHANUMEXT);
3009          $mform->addElement('hidden', 'plugin', $this->plugin);
3010          $mform->setType('plugin', PARAM_PLUGIN);
3011          $mform->addElement('hidden', 'typeid', $this->typeid);
3012          $mform->setType('typeid', PARAM_INT);
3013          $mform->addElement('hidden', 'contextid', $this->contextid);
3014          $mform->setType('contextid', PARAM_INT);
3015  
3016          $mform->addElement('text', 'name', get_string('name'), 'maxlength="100" size="30"');
3017          $mform->addRule('name', $strrequired, 'required', null, 'client');
3018          $mform->setType('name', PARAM_TEXT);
3019      }
3020  
3021      /**
3022       * Define moodle form elements
3023       */
3024      public function definition() {
3025          global $CFG;
3026          // type of plugin, string
3027          $this->plugin = $this->_customdata['plugin'];
3028          $this->typeid = $this->_customdata['typeid'];
3029          $this->contextid = $this->_customdata['contextid'];
3030          $this->instance = (isset($this->_customdata['instance'])
3031                  && is_subclass_of($this->_customdata['instance'], 'repository'))
3032              ? $this->_customdata['instance'] : null;
3033  
3034          $mform =& $this->_form;
3035  
3036          $this->add_defaults();
3037  
3038          // Add instance config options.
3039          $result = repository::static_function($this->plugin, 'instance_config_form', $mform);
3040          if ($result === false) {
3041              // Remove the name element if no other config options.
3042              $mform->removeElement('name');
3043          }
3044          if ($this->instance) {
3045              $data = array();
3046              $data['name'] = $this->instance->name;
3047              if (!$this->instance->readonly) {
3048                  // and set the data if we have some.
3049                  foreach ($this->instance->get_instance_option_names() as $config) {
3050                      if (!empty($this->instance->options[$config])) {
3051                          $data[$config] = $this->instance->options[$config];
3052                       } else {
3053                          $data[$config] = '';
3054                       }
3055                  }
3056              }
3057              $this->set_data($data);
3058          }
3059  
3060          if ($result === false) {
3061              $mform->addElement('cancel');
3062          } else {
3063              $this->add_action_buttons(true, get_string('save','repository'));
3064          }
3065      }
3066  
3067      /**
3068       * Validate moodle form data
3069       *
3070       * @param array $data form data
3071       * @param array $files files in form
3072       * @return array errors
3073       */
3074      public function validation($data, $files) {
3075          global $DB;
3076          $errors = array();
3077          $plugin = $this->_customdata['plugin'];
3078          $instance = (isset($this->_customdata['instance'])
3079                  && is_subclass_of($this->_customdata['instance'], 'repository'))
3080              ? $this->_customdata['instance'] : null;
3081  
3082          if (!$instance) {
3083              $errors = repository::static_function($plugin, 'instance_form_validation', $this, $data, $errors);
3084          } else {
3085              $errors = $instance->instance_form_validation($this, $data, $errors);
3086          }
3087  
3088          $sql = "SELECT count('x')
3089                    FROM {repository_instances} i, {repository} r
3090                   WHERE r.type=:plugin AND r.id=i.typeid AND i.name=:name AND i.contextid=:contextid";
3091          $params = array('name' => $data['name'], 'plugin' => $this->plugin, 'contextid' => $this->contextid);
3092          if ($instance) {
3093              $sql .= ' AND i.id != :instanceid';
3094              $params['instanceid'] = $instance->id;
3095          }
3096          if ($DB->count_records_sql($sql, $params) > 0) {
3097              $errors['name'] = get_string('erroruniquename', 'repository');
3098          }
3099  
3100          return $errors;
3101      }
3102  }
3103  
3104  /**
3105   * This is a class used to define a repository type setting form
3106   *
3107   * @since Moodle 2.0
3108   * @package   core_repository
3109   * @copyright 2009 Dongsheng Cai {@link http://dongsheng.org}
3110   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3111   */
3112  final class repository_type_form extends moodleform {
3113      /** @var stdClass repository instance */
3114      protected $instance;
3115      /** @var string repository plugin name */
3116      protected $plugin;
3117      /** @var string action */
3118      protected $action;
3119  
3120      /**
3121       * Definition of the moodleform
3122       */
3123      public function definition() {
3124          global $CFG;
3125          // type of plugin, string
3126          $this->plugin = $this->_customdata['plugin'];
3127          $this->instance = (isset($this->_customdata['instance'])
3128                  && is_a($this->_customdata['instance'], 'repository_type'))
3129              ? $this->_customdata['instance'] : null;
3130  
3131          $this->action = $this->_customdata['action'];
3132          $this->pluginname = $this->_customdata['pluginname'];
3133          $mform =& $this->_form;
3134          $strrequired = get_string('required');
3135  
3136          $mform->addElement('hidden', 'action', $this->action);
3137          $mform->setType('action', PARAM_TEXT);
3138          $mform->addElement('hidden', 'repos', $this->plugin);
3139          $mform->setType('repos', PARAM_PLUGIN);
3140  
3141          // let the plugin add its specific fields
3142          $classname = 'repository_' . $this->plugin;
3143          require_once($CFG->dirroot . '/repository/' . $this->plugin . '/lib.php');
3144          //add "enable course/user instances" checkboxes if multiple instances are allowed
3145          $instanceoptionnames = repository::static_function($this->plugin, 'get_instance_option_names');
3146  
3147          $result = call_user_func(array($classname, 'type_config_form'), $mform, $classname);
3148  
3149          if (!empty($instanceoptionnames)) {
3150              $sm = get_string_manager();
3151              $component = 'repository';
3152              if ($sm->string_exists('enablecourseinstances', 'repository_' . $this->plugin)) {
3153                  $component .= ('_' . $this->plugin);
3154              }
3155              $mform->addElement('checkbox', 'enablecourseinstances', get_string('enablecourseinstances', $component));
3156              $mform->setType('enablecourseinstances', PARAM_BOOL);
3157  
3158              $component = 'repository';
3159              if ($sm->string_exists('enableuserinstances', 'repository_' . $this->plugin)) {
3160                  $component .= ('_' . $this->plugin);
3161              }
3162              $mform->addElement('checkbox', 'enableuserinstances', get_string('enableuserinstances', $component));
3163              $mform->setType('enableuserinstances', PARAM_BOOL);
3164          }
3165  
3166          // set the data if we have some.
3167          if ($this->instance) {
3168              $data = array();
3169              $option_names = call_user_func(array($classname,'get_type_option_names'));
3170              if (!empty($instanceoptionnames)){
3171                  $option_names[] = 'enablecourseinstances';
3172                  $option_names[] = 'enableuserinstances';
3173              }
3174  
3175              $instanceoptions = $this->instance->get_options();
3176              foreach ($option_names as $config) {
3177                  if (!empty($instanceoptions[$config])) {
3178                      $data[$config] = $instanceoptions[$config];
3179                  } else {
3180                      $data[$config] = '';
3181                  }
3182              }
3183              // XXX: set plugin name for plugins which doesn't have muliti instances
3184              if (empty($instanceoptionnames)){
3185                  $data['pluginname'] = $this->pluginname;
3186              }
3187              $this->set_data($data);
3188          }
3189  
3190          $this->add_action_buttons(true, get_string('save','repository'));
3191      }
3192  
3193      /**
3194       * Validate moodle form data
3195       *
3196       * @param array $data moodle form data
3197       * @param array $files
3198       * @return array errors
3199       */
3200      public function validation($data, $files) {
3201          $errors = array();
3202          $plugin = $this->_customdata['plugin'];
3203          $instance = (isset($this->_customdata['instance'])
3204                  && is_subclass_of($this->_customdata['instance'], 'repository'))
3205              ? $this->_customdata['instance'] : null;
3206          if (!$instance) {
3207              $errors = repository::static_function($plugin, 'type_form_validation', $this, $data, $errors);
3208          } else {
3209              $errors = $instance->type_form_validation($this, $data, $errors);
3210          }
3211  
3212          return $errors;
3213      }
3214  }
3215  
3216  /**
3217   * Generate all options needed by filepicker
3218   *
3219   * @param array $args including following keys
3220   *          context
3221   *          accepted_types
3222   *          return_types
3223   *
3224   * @return array the list of repository instances, including meta infomation, containing the following keys
3225   *          externallink
3226   *          repositories
3227   *          accepted_types
3228   */
3229  function initialise_filepicker($args) {
3230      global $CFG, $USER, $PAGE, $OUTPUT;
3231      static $templatesinitialized = array();
3232      require_once($CFG->libdir . '/licenselib.php');
3233  
3234      $return = new stdClass();
3235      $licenses = array();
3236      if (!empty($CFG->licenses)) {
3237          $array = explode(',', $CFG->licenses);
3238          foreach ($array as $license) {
3239              $l = new stdClass();
3240              $l->shortname = $license;
3241              $l->fullname = get_string($license, 'license');
3242              $licenses[] = $l;
3243          }
3244      }
3245      if (!empty($CFG->sitedefaultlicense)) {
3246          $return->defaultlicense = $CFG->sitedefaultlicense;
3247      }
3248  
3249      $return->licenses = $licenses;
3250  
3251      $return->author = fullname($USER);
3252  
3253      if (empty($args->context)) {
3254          $context = $PAGE->context;
3255      } else {
3256          $context = $args->context;
3257      }
3258      $disable_types = array();
3259      if (!empty($args->disable_types)) {
3260          $disable_types = $args->disable_types;
3261      }
3262  
3263      $user_context = context_user::instance($USER->id);
3264  
3265      list($context, $course, $cm) = get_context_info_array($context->id);
3266      $contexts = array($user_context, context_system::instance());
3267      if (!empty($course)) {
3268          // adding course context
3269          $contexts[] = context_course::instance($course->id);
3270      }
3271      $externallink = (int)get_config(null, 'repositoryallowexternallinks');
3272      $repositories = repository::get_instances(array(
3273          'context'=>$contexts,
3274          'currentcontext'=> $context,
3275          'accepted_types'=>$args->accepted_types,
3276          'return_types'=>$args->return_types,
3277          'disable_types'=>$disable_types
3278      ));
3279  
3280      $return->repositories = array();
3281  
3282      if (empty($externallink)) {
3283          $return->externallink = false;
3284      } else {
3285          $return->externallink = true;
3286      }
3287  
3288      $return->userprefs = array();
3289      $return->userprefs['recentrepository'] = get_user_preferences('filepicker_recentrepository', '');
3290      $return->userprefs['recentlicense'] = get_user_preferences('filepicker_recentlicense', '');
3291      $return->userprefs['recentviewmode'] = get_user_preferences('filepicker_recentviewmode', '');
3292  
3293      user_preference_allow_ajax_update('filepicker_recentrepository', PARAM_INT);
3294      user_preference_allow_ajax_update('filepicker_recentlicense', PARAM_SAFEDIR);
3295      user_preference_allow_ajax_update('filepicker_recentviewmode', PARAM_INT);
3296  
3297  
3298      // provided by form element
3299      $return->accepted_types = file_get_typegroup('extension', $args->accepted_types);
3300      $return->return_types = $args->return_types;
3301      $templates = array();
3302      foreach ($repositories as $repository) {
3303          $meta = $repository->get_meta();
3304          // Please note that the array keys for repositories are used within
3305          // JavaScript a lot, the key NEEDS to be the repository id.
3306          $return->repositories[$repository->id] = $meta;
3307          // Register custom repository template if it has one
3308          if(method_exists($repository, 'get_upload_template') && !array_key_exists('uploadform_' . $meta->type, $templatesinitialized)) {
3309              $templates['uploadform_' . $meta->type] = $repository->get_upload_template();
3310              $templatesinitialized['uploadform_' . $meta->type] = true;
3311          }
3312      }
3313      if (!array_key_exists('core', $templatesinitialized)) {
3314          // we need to send each filepicker template to the browser just once
3315          $fprenderer = $PAGE->get_renderer('core', 'files');
3316          $templates = array_merge($templates, $fprenderer->filepicker_js_templates());
3317          $templatesinitialized['core'] = true;
3318      }
3319      if (sizeof($templates)) {
3320          $PAGE->requires->js_init_call('M.core_filepicker.set_templates', array($templates), true);
3321      }
3322      return $return;
3323  }


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1