PageRenderTime 113ms CodeModel.GetById 72ms app.highlight 24ms RepoModel.GetById 1ms app.codeStats 1ms

/repository/lib.php

https://bitbucket.org/synergylearning/campusconnect
PHP | 3317 lines | 1888 code | 286 blank | 1143 comment | 455 complexity | 9eed1caff850c24826e808293ae2d74c MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   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 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
  26defined('MOODLE_INTERNAL') || die();
  27require_once($CFG->libdir . '/filelib.php');
  28require_once($CFG->libdir . '/formslib.php');
  29
  30define('FILE_EXTERNAL',  1);
  31define('FILE_INTERNAL',  2);
  32define('FILE_REFERENCE', 4);
  33define('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 */
  55class 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 */
 495abstract class repository implements cacheable_object {
 496    /** Timeout in seconds for downloading the external file into moodle */
 497    const GETFILE_TIMEOUT = 30;
 498    /** Timeout in seconds for syncronising the external file size */
 499    const SYNCFILE_TIMEOUT = 1;
 500    /** Timeout in seconds for downloading an image file from external repository during syncronisation */
 501    const SYNCIMAGE_TIMEOUT = 3;
 502
 503    // $disabled can be set to true to disable a plugin by force
 504    // example: self::$disabled = true
 505    /** @var bool force disable repository instance */
 506    public $disabled = false;
 507    /** @var int repository instance id */
 508    public $id;
 509    /** @var stdClass current context */
 510    public $context;
 511    /** @var array repository options */
 512    public $options;
 513    /** @var bool Whether or not the repository instance is editable */
 514    public $readonly;
 515    /** @var int return types */
 516    public $returntypes;
 517    /** @var stdClass repository instance database record */
 518    public $instance;
 519    /** @var string Type of repository (webdav, google_docs, dropbox, ...). Read from $this->get_typename(). */
 520    protected $typename;
 521
 522    /**
 523     * Constructor
 524     *
 525     * @param int $repositoryid repository instance id
 526     * @param int|stdClass $context a context id or context object
 527     * @param array $options repository options
 528     * @param int $readonly indicate this repo is readonly or not
 529     */
 530    public function __construct($repositoryid, $context = SYSCONTEXTID, $options = array(), $readonly = 0) {
 531        global $DB;
 532        $this->id = $repositoryid;
 533        if (is_object($context)) {
 534            $this->context = $context;
 535        } else {
 536            $this->context = context::instance_by_id($context);
 537        }
 538        $cache = cache::make('core', 'repositories');
 539        if (($this->instance = $cache->get('i:'. $this->id)) === false) {
 540            $this->instance = $DB->get_record_sql("SELECT i.*, r.type AS repositorytype, r.sortorder, r.visible
 541                      FROM {repository} r, {repository_instances} i
 542                     WHERE i.typeid = r.id and i.id = ?", array('id' => $this->id));
 543            $cache->set('i:'. $this->id, $this->instance);
 544        }
 545        $this->readonly = $readonly;
 546        $this->options = array();
 547
 548        if (is_array($options)) {
 549            // The get_option() method will get stored options in database.
 550            $options = array_merge($this->get_option(), $options);
 551        } else {
 552            $options = $this->get_option();
 553        }
 554        foreach ($options as $n => $v) {
 555            $this->options[$n] = $v;
 556        }
 557        $this->name = $this->get_name();
 558        $this->returntypes = $this->supported_returntypes();
 559        $this->super_called = true;
 560    }
 561
 562    /**
 563     * Magic method for non-existing (usually deprecated) class methods.
 564     *
 565     * @param string $name
 566     * @param array $arguments
 567     * @return mixed
 568     * @throws coding_exception
 569     */
 570    public function __call($name, $arguments) {
 571        if ($name === 'sync_individual_file') {
 572            // Method repository::sync_individual_file() was deprecated in Moodle 2.6.
 573            // See repository::sync_reference().
 574            debugging('Function repository::sync_individual_file() is deprecated.', DEBUG_DEVELOPER);
 575            return true;
 576        } else if ($name === 'get_file_by_reference') {
 577            // Method repository::get_file_by_reference() was deprecated in Moodle 2.6.
 578            // See repository::sync_reference().
 579            debugging('Function repository::get_file_by_reference() is deprecated.', DEBUG_DEVELOPER);
 580            return null;
 581        } else if ($name === 'get_reference_file_lifetime') {
 582            // Method repository::get_file_by_reference() was deprecated in Moodle 2.6.
 583            // See repository::sync_reference().
 584            debugging('Function repository::get_reference_file_lifetime() is deprecated.', DEBUG_DEVELOPER);
 585            return 24 * 60 * 60;
 586        } else {
 587            throw new coding_exception('Tried to call unknown method '.get_class($this).'::'.$name);
 588        }
 589    }
 590
 591    /**
 592     * Get repository instance using repository id
 593     *
 594     * Note that this function does not check permission to access repository contents
 595     *
 596     * @throws repository_exception
 597     *
 598     * @param int $repositoryid repository instance ID
 599     * @param context|int $context context instance or context ID where this repository will be used
 600     * @param array $options additional repository options
 601     * @return repository
 602     */
 603    public static function get_repository_by_id($repositoryid, $context, $options = array()) {
 604        global $CFG, $DB;
 605        $cache = cache::make('core', 'repositories');
 606        if (!is_object($context)) {
 607            $context = context::instance_by_id($context);
 608        }
 609        $cachekey = 'rep:'. $repositoryid. ':'. $context->id. ':'. serialize($options);
 610        if ($repository = $cache->get($cachekey)) {
 611            return $repository;
 612        }
 613
 614        if (!$record = $cache->get('i:'. $repositoryid)) {
 615            $sql = "SELECT i.*, r.type AS repositorytype, r.visible, r.sortorder
 616                      FROM {repository_instances} i
 617                      JOIN {repository} r ON r.id = i.typeid
 618                     WHERE i.id = ?";
 619            if (!$record = $DB->get_record_sql($sql, array($repositoryid))) {
 620                throw new repository_exception('invalidrepositoryid', 'repository');
 621            }
 622            $cache->set('i:'. $record->id, $record);
 623        }
 624
 625        $type = $record->repositorytype;
 626        if (file_exists($CFG->dirroot . "/repository/$type/lib.php")) {
 627            require_once($CFG->dirroot . "/repository/$type/lib.php");
 628            $classname = 'repository_' . $type;
 629            $options['type'] = $type;
 630            $options['typeid'] = $record->typeid;
 631            $options['visible'] = $record->visible;
 632            if (empty($options['name'])) {
 633                $options['name'] = $record->name;
 634            }
 635            $repository = new $classname($repositoryid, $context, $options, $record->readonly);
 636            if (empty($repository->super_called)) {
 637                // to make sure the super construct is called
 638                debugging('parent::__construct must be called by '.$type.' plugin.');
 639            }
 640            $cache->set($cachekey, $repository);
 641            return $repository;
 642        } else {
 643            throw new repository_exception('invalidplugin', 'repository');
 644        }
 645    }
 646
 647    /**
 648     * Returns the type name of the repository.
 649     *
 650     * @return string type name of the repository.
 651     * @since  2.5
 652     */
 653    public function get_typename() {
 654        if (empty($this->typename)) {
 655            $matches = array();
 656            if (!preg_match("/^repository_(.*)$/", get_class($this), $matches)) {
 657                throw new coding_exception('The class name of a repository should be repository_<typeofrepository>, '.
 658                        'e.g. repository_dropbox');
 659            }
 660            $this->typename = $matches[1];
 661        }
 662        return $this->typename;
 663    }
 664
 665    /**
 666     * Get a repository type object by a given type name.
 667     *
 668     * @static
 669     * @param string $typename the repository type name
 670     * @return repository_type|bool
 671     */
 672    public static function get_type_by_typename($typename) {
 673        global $DB;
 674        $cache = cache::make('core', 'repositories');
 675        if (($repositorytype = $cache->get('typename:'. $typename)) === false) {
 676            $repositorytype = null;
 677            if ($record = $DB->get_record('repository', array('type' => $typename))) {
 678                $repositorytype = new repository_type($record->type, (array)get_config($record->type), $record->visible, $record->sortorder);
 679                $cache->set('typeid:'. $record->id, $repositorytype);
 680            }
 681            $cache->set('typename:'. $typename, $repositorytype);
 682        }
 683        return $repositorytype;
 684    }
 685
 686    /**
 687     * Get the repository type by a given repository type id.
 688     *
 689     * @static
 690     * @param int $id the type id
 691     * @return object
 692     */
 693    public static function get_type_by_id($id) {
 694        global $DB;
 695        $cache = cache::make('core', 'repositories');
 696        if (($repositorytype = $cache->get('typeid:'. $id)) === false) {
 697            $repositorytype = null;
 698            if ($record = $DB->get_record('repository', array('id' => $id))) {
 699                $repositorytype = new repository_type($record->type, (array)get_config($record->type), $record->visible, $record->sortorder);
 700                $cache->set('typename:'. $record->type, $repositorytype);
 701            }
 702            $cache->set('typeid:'. $id, $repositorytype);
 703        }
 704        return $repositorytype;
 705    }
 706
 707    /**
 708     * Return all repository types ordered by sortorder field
 709     * first repository type in returnedarray[0], second repository type in returnedarray[1], ...
 710     *
 711     * @static
 712     * @param bool $visible can return types by visiblity, return all types if null
 713     * @return array Repository types
 714     */
 715    public static function get_types($visible=null) {
 716        global $DB, $CFG;
 717        $cache = cache::make('core', 'repositories');
 718        if (!$visible) {
 719            $typesnames = $cache->get('types');
 720        } else {
 721            $typesnames = $cache->get('typesvis');
 722        }
 723        $types = array();
 724        if ($typesnames === false) {
 725            $typesnames = array();
 726            $vistypesnames = array();
 727            if ($records = $DB->get_records('repository', null ,'sortorder')) {
 728                foreach($records as $type) {
 729                    if (($repositorytype = $cache->get('typename:'. $type->type)) === false) {
 730                        // Create new instance of repository_type.
 731                        if (file_exists($CFG->dirroot . '/repository/'. $type->type .'/lib.php')) {
 732                            $repositorytype = new repository_type($type->type, (array)get_config($type->type), $type->visible, $type->sortorder);
 733                            $cache->set('typeid:'. $type->id, $repositorytype);
 734                            $cache->set('typename:'. $type->type, $repositorytype);
 735                        }
 736                    }
 737                    if ($repositorytype) {
 738                        if (empty($visible) || $repositorytype->get_visible()) {
 739                            $types[] = $repositorytype;
 740                            $vistypesnames[] = $repositorytype->get_typename();
 741                        }
 742                        $typesnames[] = $repositorytype->get_typename();
 743                    }
 744                }
 745            }
 746            $cache->set('types', $typesnames);
 747            $cache->set('typesvis', $vistypesnames);
 748        } else {
 749            foreach ($typesnames as $typename) {
 750                $types[] = self::get_type_by_typename($typename);
 751            }
 752        }
 753        return $types;
 754    }
 755
 756    /**
 757     * Checks if user has a capability to view the current repository.
 758     *
 759     * @return bool true when the user can, otherwise throws an exception.
 760     * @throws repository_exception when the user does not meet the requirements.
 761     */
 762    public final function check_capability() {
 763        global $USER;
 764
 765        // The context we are on.
 766        $currentcontext = $this->context;
 767
 768        // Ensure that the user can view the repository in the current context.
 769        $can = has_capability('repository/'.$this->get_typename().':view', $currentcontext);
 770
 771        // Context in which the repository has been created.
 772        $repocontext = context::instance_by_id($this->instance->contextid);
 773
 774        // Prevent access to private repositories when logged in as.
 775        if ($can && \core\session\manager::is_loggedinas()) {
 776            if ($this->contains_private_data() || $repocontext->contextlevel == CONTEXT_USER) {
 777                $can = false;
 778            }
 779        }
 780
 781        // We are going to ensure that the current context was legit, and reliable to check
 782        // the capability against. (No need to do that if we already cannot).
 783        if ($can) {
 784            if ($repocontext->contextlevel == CONTEXT_USER) {
 785                // The repository is a user instance, ensure we're the right user to access it!
 786                if ($repocontext->instanceid != $USER->id) {
 787                    $can = false;
 788                }
 789            } else if ($repocontext->contextlevel == CONTEXT_COURSE) {
 790                // The repository is a course one. Let's check that we are on the right course.
 791                if (in_array($currentcontext->contextlevel, array(CONTEXT_COURSE, CONTEXT_MODULE, CONTEXT_BLOCK))) {
 792                    $coursecontext = $currentcontext->get_course_context();
 793                    if ($coursecontext->instanceid != $repocontext->instanceid) {
 794                        $can = false;
 795                    }
 796                } else {
 797                    // We are on a parent context, therefore it's legit to check the permissions
 798                    // in the current context.
 799                }
 800            } else {
 801                // Nothing to check here, system instances can have different permissions on different
 802                // levels. We do not want to prevent URL hack here, because it does not make sense to
 803                // prevent a user to access a repository in a context if it's accessible in another one.
 804            }
 805        }
 806
 807        if ($can) {
 808            return true;
 809        }
 810
 811        throw new repository_exception('nopermissiontoaccess', 'repository');
 812    }
 813
 814    /**
 815     * Check if file already exists in draft area.
 816     *
 817     * @static
 818     * @param int $itemid of the draft area.
 819     * @param string $filepath path to the file.
 820     * @param string $filename file name.
 821     * @return bool
 822     */
 823    public static function draftfile_exists($itemid, $filepath, $filename) {
 824        global $USER;
 825        $fs = get_file_storage();
 826        $usercontext = context_user::instance($USER->id);
 827        return $fs->file_exists($usercontext->id, 'user', 'draft', $itemid, $filepath, $filename);
 828    }
 829
 830    /**
 831     * Parses the moodle file reference and returns an instance of stored_file
 832     *
 833     * @param string $reference reference to the moodle internal file as retruned by
 834     *        {@link repository::get_file_reference()} or {@link file_storage::pack_reference()}
 835     * @return stored_file|null
 836     */
 837    public static function get_moodle_file($reference) {
 838        $params = file_storage::unpack_reference($reference, true);
 839        $fs = get_file_storage();
 840        return $fs->get_file($params['contextid'], $params['component'], $params['filearea'],
 841                    $params['itemid'], $params['filepath'], $params['filename']);
 842    }
 843
 844    /**
 845     * Repository method to make sure that user can access particular file.
 846     *
 847     * This is checked when user tries to pick the file from repository to deal with
 848     * potential parameter substitutions is request
 849     *
 850     * @param string $source source of the file, returned by repository as 'source' and received back from user (not cleaned)
 851     * @return bool whether the file is accessible by current user
 852     */
 853    public function file_is_accessible($source) {
 854        if ($this->has_moodle_files()) {
 855            $reference = $this->get_file_reference($source);
 856            try {
 857                $params = file_storage::unpack_reference($reference, true);
 858            } catch (file_reference_exception $e) {
 859                return false;
 860            }
 861            $browser = get_file_browser();
 862            $context = context::instance_by_id($params['contextid']);
 863            $file_info = $browser->get_file_info($context, $params['component'], $params['filearea'],
 864                    $params['itemid'], $params['filepath'], $params['filename']);
 865            return !empty($file_info);
 866        }
 867        return true;
 868    }
 869
 870    /**
 871     * This function is used to copy a moodle file to draft area.
 872     *
 873     * It DOES NOT check if the user is allowed to access this file because the actual file
 874     * can be located in the area where user does not have access to but there is an alias
 875     * to this file in the area where user CAN access it.
 876     * {@link file_is_accessible} should be called for alias location before calling this function.
 877     *
 878     * @param string $source The metainfo of file, it is base64 encoded php serialized data
 879     * @param stdClass|array $filerecord contains itemid, filepath, filename and optionally other
 880     *      attributes of the new file
 881     * @param int $maxbytes maximum allowed size of file, -1 if unlimited. If size of file exceeds
 882     *      the limit, the file_exception is thrown.
 883     * @param int $areamaxbytes the maximum size of the area. A file_exception is thrown if the
 884     *      new file will reach the limit.
 885     * @return array The information about the created file
 886     */
 887    public function copy_to_area($source, $filerecord, $maxbytes = -1, $areamaxbytes = FILE_AREA_MAX_BYTES_UNLIMITED) {
 888        global $USER;
 889        $fs = get_file_storage();
 890
 891        if ($this->has_moodle_files() == false) {
 892            throw new coding_exception('Only repository used to browse moodle files can use repository::copy_to_area()');
 893        }
 894
 895        $user_context = context_user::instance($USER->id);
 896
 897        $filerecord = (array)$filerecord;
 898        // make sure the new file will be created in user draft area
 899        $filerecord['component'] = 'user';
 900        $filerecord['filearea'] = 'draft';
 901        $filerecord['contextid'] = $user_context->id;
 902        $draftitemid = $filerecord['itemid'];
 903        $new_filepath = $filerecord['filepath'];
 904        $new_filename = $filerecord['filename'];
 905
 906        // the file needs to copied to draft area
 907        $stored_file = self::get_moodle_file($source);
 908        if ($maxbytes != -1 && $stored_file->get_filesize() > $maxbytes) {
 909            throw new file_exception('maxbytes');
 910        }
 911        // Validate the size of the draft area.
 912        if (file_is_draft_area_limit_reached($draftitemid, $areamaxbytes, $stored_file->get_filesize())) {
 913            throw new file_exception('maxareabytes');
 914        }
 915
 916        if (repository::draftfile_exists($draftitemid, $new_filepath, $new_filename)) {
 917            // create new file
 918            $unused_filename = repository::get_unused_filename($draftitemid, $new_filepath, $new_filename);
 919            $filerecord['filename'] = $unused_filename;
 920            $fs->create_file_from_storedfile($filerecord, $stored_file);
 921            $event = array();
 922            $event['event'] = 'fileexists';
 923            $event['newfile'] = new stdClass;
 924            $event['newfile']->filepath = $new_filepath;
 925            $event['newfile']->filename = $unused_filename;
 926            $event['newfile']->url = moodle_url::make_draftfile_url($draftitemid, $new_filepath, $unused_filename)->out();
 927            $event['existingfile'] = new stdClass;
 928            $event['existingfile']->filepath = $new_filepath;
 929            $event['existingfile']->filename = $new_filename;
 930            $event['existingfile']->url = moodle_url::make_draftfile_url($draftitemid, $new_filepath, $new_filename)->out();
 931            return $event;
 932        } else {
 933            $fs->create_file_from_storedfile($filerecord, $stored_file);
 934            $info = array();
 935            $info['itemid'] = $draftitemid;
 936            $info['file'] = $new_filename;
 937            $info['title'] = $new_filename;
 938            $info['contextid'] = $user_context->id;
 939            $info['url'] = moodle_url::make_draftfile_url($draftitemid, $new_filepath, $new_filename)->out();
 940            $info['filesize'] = $stored_file->get_filesize();
 941            return $info;
 942        }
 943    }
 944
 945    /**
 946     * Get an unused filename from the current draft area.
 947     *
 948     * Will check if the file ends with ([0-9]) and increase the number.
 949     *
 950     * @static
 951     * @param int $itemid draft item ID.
 952     * @param string $filepath path to the file.
 953     * @param string $filename name of the file.
 954     * @return string an unused file name.
 955     */
 956    public static function get_unused_filename($itemid, $filepath, $filename) {
 957        global $USER;
 958        $contextid = context_user::instance($USER->id)->id;
 959        $fs = get_file_storage();
 960        return $fs->get_unused_filename($contextid, 'user', 'draft', $itemid, $filepath, $filename);
 961    }
 962
 963    /**
 964     * Append a suffix to filename.
 965     *
 966     * @static
 967     * @param string $filename
 968     * @return string
 969     * @deprecated since 2.5
 970     */
 971    public static function append_suffix($filename) {
 972        debugging('The function repository::append_suffix() has been deprecated. Use repository::get_unused_filename() instead.',
 973            DEBUG_DEVELOPER);
 974        $pathinfo = pathinfo($filename);
 975        if (empty($pathinfo['extension'])) {
 976            return $filename . RENAME_SUFFIX;
 977        } else {
 978            return $pathinfo['filename'] . RENAME_SUFFIX . '.' . $pathinfo['extension'];
 979        }
 980    }
 981
 982    /**
 983     * Return all types that you a user can create/edit and which are also visible
 984     * Note: Mostly used in order to know if at least one editable type can be set
 985     *
 986     * @static
 987     * @param stdClass $context the context for which we want the editable types
 988     * @return array types
 989     */
 990    public static function get_editable_types($context = null) {
 991
 992        if (empty($context)) {
 993            $context = context_system::instance();
 994        }
 995
 996        $types= repository::get_types(true);
 997        $editabletypes = array();
 998        foreach ($types as $type) {
 999            $instanceoptionnames = repository::static_function($type->get_typename(), 'get_instance_option_names');
1000            if (!empty($instanceoptionnames)) {
1001                if ($type->get_contextvisibility($context)) {
1002                    $editabletypes[]=$type;
1003                }
1004             }
1005        }
1006        return $editabletypes;
1007    }
1008
1009    /**
1010     * Return repository instances
1011     *
1012     * @static
1013     * @param array $args Array containing the following keys:
1014     *           currentcontext : instance of context (default system context)
1015     *           context : array of instances of context (default empty array)
1016     *           onlyvisible : bool (default true)
1017     *           type : string return instances of this type only
1018     *           accepted_types : string|array return instances that contain files of those types (*, web_image, .pdf, ...)
1019     *           return_types : int combination of FILE_INTERNAL & FILE_EXTERNAL & FILE_REFERENCE.
1020     *                          0 means every type. The default is FILE_INTERNAL | FILE_EXTERNAL.
1021     *           userid : int if specified, instances belonging to other users will not be returned
1022     *
1023     * @return array repository instances
1024     */
1025    public static function get_instances($args = array()) {
1026        global $DB, $CFG, $USER;
1027
1028        // Fill $args attributes with default values unless specified
1029        if (!isset($args['currentcontext']) || !($args['currentcontext'] instanceof context)) {
1030            $current_context = context_system::instance();
1031        } else {
1032            $current_context = $args['currentcontext'];
1033        }
1034        $args['currentcontext'] = $current_context->id;
1035        $contextids = array();
1036        if (!empty($args['context'])) {
1037            foreach ($args['context'] as $context) {
1038                $contextids[] = $context->id;
1039            }
1040        }
1041        $args['context'] = $contextids;
1042        if (!isset($args['onlyvisible'])) {
1043            $args['onlyvisible'] = true;
1044        }
1045        if (!isset($args['return_types'])) {
1046            $args['return_types'] = FILE_INTERNAL | FILE_EXTERNAL;
1047        }
1048        if (!isset($args['type'])) {
1049            $args['type'] = null;
1050        }
1051        if (empty($args['disable_types']) || !is_array($args['disable_types'])) {
1052            $args['disable_types'] = null;
1053        }
1054        if (empty($args['userid']) || !is_numeric($args['userid'])) {
1055            $args['userid'] = null;
1056        }
1057        if (!isset($args['accepted_types']) || (is_array($args['accepted_types']) && in_array('*', $args['accepted_types']))) {
1058            $args['accepted_types'] = '*';
1059        }
1060        ksort($args);
1061        $cachekey = 'all:'. serialize($args);
1062
1063        // Check if we have cached list of repositories with the same query
1064        $cache = cache::make('core', 'repositories');
1065        if (($cachedrepositories = $cache->get($cachekey)) !== false) {
1066            // convert from cacheable_object_array to array
1067            $repositories = array();
1068            foreach ($cachedrepositories as $repository) {
1069                $repositories[$repository->id] = $repository;
1070            }
1071            return $repositories;
1072        }
1073
1074        // Prepare DB SQL query to retrieve repositories
1075        $params = array();
1076        $sql = "SELECT i.*, r.type AS repositorytype, r.sortorder, r.visible
1077                  FROM {repository} r, {repository_instances} i
1078                 WHERE i.typeid = r.id ";
1079
1080        if ($args['disable_types']) {
1081            list($types, $p) = $DB->get_in_or_equal($args['disable_types'], SQL_PARAMS_NAMED, 'distype', false);
1082            $sql .= " AND r.type $types";
1083            $params = array_merge($params, $p);
1084        }
1085
1086        if ($args['userid']) {
1087            $sql .= " AND (i.userid = 0 or i.userid = :userid)";
1088            $params['userid'] = $args['userid'];
1089        }
1090
1091        if ($args['context']) {
1092            list($ctxsql, $p2) = $DB->get_in_or_equal($args['context'], SQL_PARAMS_NAMED, 'ctx');
1093            $sql .= " AND i.contextid $ctxsql";
1094            $params = array_merge($params, $p2);
1095        }
1096
1097        if ($args['onlyvisible'] == true) {
1098            $sql .= " AND r.visible = 1";
1099        }
1100
1101        if ($args['type'] !== null) {
1102            $sql .= " AND r.type = :type";
1103            $params['type'] = $args['type'];
1104        }
1105        $sql .= " ORDER BY r.sortorder, i.name";
1106
1107        if (!$records = $DB->get_records_sql($sql, $params)) {
1108            $records = array();
1109        }
1110
1111        $repositories = array();
1112        // Sortorder should be unique, which is not true if we use $record->sortorder
1113        // and there are multiple instances of any repository type
1114        $sortorder = 1;
1115        foreach ($records as $record) {
1116            $cache->set('i:'. $record->id, $record);
1117            if (!file_exists($CFG->dirroot . '/repository/'. $record->repositorytype.'/lib.php')) {
1118                continue;
1119            }
1120            $repository = self::get_repository_by_id($record->id, $current_context);
1121            $repository->options['sortorder'] = $sortorder++;
1122
1123            $is_supported = true;
1124
1125            // check mimetypes
1126            if ($args['accepted_types'] !== '*' and $repository->supported_filetypes() !== '*') {
1127                $accepted_ext = file_get_typegroup('extension', $args['accepted_types']);
1128                $supported_ext = file_get_typegroup('extension', $repository->supported_filetypes());
1129                $valid_ext = array_intersect($accepted_ext, $supported_ext);
1130                $is_supported = !empty($valid_ext);
1131            }
1132            // Check return values.
1133            if (!empty($args['return_types']) && !($repository->supported_returntypes() & $args['return_types'])) {
1134                $is_supported = false;
1135            }
1136
1137            if (!$args['onlyvisible'] || ($repository->is_visible() && !$repository->disabled)) {
1138                // check capability in current context
1139                $capability = has_capability('repository/'.$record->repositorytype.':view', $current_context);
1140                if ($record->repositorytype == 'coursefiles') {
1141                    // coursefiles plugin needs managefiles permission
1142                    $capability = $capability && has_capability('moodle/course:managefiles', $current_context);
1143                }
1144                if ($is_supported && $capability) {
1145                    $repositories[$repository->id] = $repository;
1146                }
1147            }
1148        }
1149        $cache->set($cachekey, new cacheable_object_array($repositories));
1150        return $repositories;
1151    }
1152
1153    /**
1154     * Get single repository instance for administrative actions
1155     *
1156     * Do not use this function to access repository contents, because it
1157     * does not set the current context
1158     *
1159     * @see repository::get_repository_by_id()
1160     *
1161     * @static
1162     * @param integer $id repository instance id
1163     * @return repository
1164     */
1165    public static function get_instance($id) {
1166        return self::get_repository_by_id($id, context_system::instance());
1167    }
1168
1169    /**
1170     * Call a static function. Any additional arguments than plugin and function will be passed through.
1171     *
1172     * @static
1173     * @param string $plugin repository plugin name
1174     * @param string $function function name
1175     * @return mixed
1176     */
1177    public static function static_function($plugin, $function) {
1178        global $CFG;
1179
1180        //check that the plugin exists
1181        $typedirectory = $CFG->dirroot . '/repository/'. $plugin . '/lib.php';
1182        if (!file_exists($typedirectory)) {
1183            //throw new repository_exception('invalidplugin', 'repository');
1184            return false;
1185        }
1186
1187        $args = func_get_args();
1188        if (count($args) <= 2) {
1189            $args = array();
1190        } else {
1191            array_shift($args);
1192            array_shift($args);
1193        }
1194
1195        require_once($typedirectory);
1196        return call_user_func_array(array('repository_' . $plugin, $function), $args);
1197    }
1198
1199    /**
1200     * Scan file, throws exception in case of infected file.
1201     *
1202     * Please note that the scanning engine must be able to access the file,
1203     * permissions of the file are not modified here!
1204     *
1205     * @static
1206     * @param string $thefile
1207     * @param string $filename name of the file
1208     * @param bool $deleteinfected
1209     */
1210    public static function antivir_scan_file($thefile, $filename, $deleteinfected) {
1211        global $CFG;
1212
1213        if (!is_readable($thefile)) {
1214            // this should not happen
1215            return;
1216        }
1217
1218        if (empty($CFG->runclamonupload) or empty($CFG->pathtoclam)) {
1219            // clam not enabled
1220            return;
1221        }
1222
1223        $CFG->pathtoclam = trim($CFG->pathtoclam);
1224
1225        if (!file_exists($CFG->pathtoclam) or !is_executable($CFG->pathtoclam)) {
1226            // misconfigured clam - use the old notification for now
1227            require("$CFG->libdir/uploadlib.php");
1228            $notice = get_string('clamlost', 'moodle', $CFG->pathtoclam);
1229            clam_message_admins($notice);
1230            return;
1231        }
1232
1233        $clamparam = ' --stdout ';
1234        // If we are dealing with clamdscan, clamd is likely run as a different user
1235        // that might not have permissions to access your file.
1236        // To make clamdscan work, we use --fdpass parameter that passes the file
1237        // descriptor permissions to clamd, which allows it to scan given file
1238        // irrespective of directory and file permissions.
1239        if (basename($CFG->pathtoclam) == 'clamdscan') {
1240            $clamparam .= '--fdpass ';
1241        }
1242        // execute test
1243        $cmd = escapeshellcmd($CFG->pathtoclam).$clamparam.escapeshellarg($thefile);
1244        exec($cmd, $output, $return);
1245
1246        if ($return == 0) {
1247            // perfect, no problem found
1248            return;
1249
1250        } else if ($return == 1) {
1251            // infection found
1252            if ($deleteinfected) {
1253                unlink($thefile);
1254            }
1255            throw new moodle_exception('virusfounduser', 'moodle', '', array('filename'=>$filename));
1256
1257        } else {
1258            //unknown problem
1259            require("$CFG->libdir/uploadlib.php");
1260            $notice = get_string('clamfailed', 'moodle', get_clam_error_code($return));
1261            $notice .= "\n\n". implode("\n", $output);
1262            clam_message_admins($notice);
1263            if ($CFG->clamfailureonupload === 'actlikevirus') {
1264                if ($deleteinfected) {
1265                    unlink($thefile);
1266                }
1267                throw new moodle_exception('virusfounduser', 'moodle', '', array('filename'=>$filename));
1268            } else {
1269                return;
1270            }
1271        }
1272    }
1273
1274    /**
1275     * Repository method to serve the referenced file
1276     *
1277     * @see send_stored_file
1278     *
1279     * @param stored_file $storedfile the file that contains the reference
1280     * @param int $lifetime Number of seconds before the file should expire from caches (null means $CFG->filelifetime)
1281     * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only
1282     * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin
1283     * @param array $options additional options affecting the file serving
1284     */
1285    public function send_file($storedfile, $lifetime=null , $filter=0, $forcedownload=false, array $options = null) {
1286        if ($this->has_moodle_files()) {
1287            $fs = get_file_storage();
1288            $params = file_storage::unpack_referenc…

Large files files are truncated, but you can click here to view the full file