/repository/lib.php
PHP | 3309 lines | 1850 code | 291 blank | 1168 comment | 409 complexity | f1cb589d088fb743be7cb9cd934d0071 MD5 | raw file
Possible License(s): MIT, AGPL-3.0, MPL-2.0-no-copyleft-exception, LGPL-3.0, GPL-3.0, Apache-2.0, LGPL-2.1, BSD-3-Clause
Large files files are truncated, but you can click here to view the full file
- <?php
- // This file is part of Moodle - http://moodle.org/
- //
- // Moodle is free software: you can redistribute it and/or modify
- // it under the terms of the GNU General Public License as published by
- // the Free Software Foundation, either version 3 of the License, or
- // (at your option) any later version.
- //
- // Moodle is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU General Public License for more details.
- //
- // You should have received a copy of the GNU General Public License
- // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
- /**
- * This file contains classes used to manage the repository plugins in Moodle
- *
- * @since Moodle 2.0
- * @package core_repository
- * @copyright 2009 Dongsheng Cai {@link http://dongsheng.org}
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
- defined('MOODLE_INTERNAL') || die();
- require_once($CFG->libdir . '/filelib.php');
- require_once($CFG->libdir . '/formslib.php');
- define('FILE_EXTERNAL', 1);
- define('FILE_INTERNAL', 2);
- define('FILE_REFERENCE', 4);
- define('FILE_CONTROLLED_LINK', 8);
- define('RENAME_SUFFIX', '_2');
- /**
- * This class is used to manage repository plugins
- *
- * A repository_type is a repository plug-in. It can be Box.net, Flick-r, ...
- * A repository type can be edited, sorted and hidden. It is mandatory for an
- * administrator to create a repository type in order to be able to create
- * some instances of this type.
- * Coding note:
- * - a repository_type object is mapped to the "repository" database table
- * - "typename" attibut maps the "type" database field. It is unique.
- * - general "options" for a repository type are saved in the config_plugin table
- * - when you delete a repository, all instances are deleted, and general
- * options are also deleted from database
- * - When you create a type for a plugin that can't have multiple instances, a
- * instance is automatically created.
- *
- * @package core_repository
- * @copyright 2009 Jerome Mouneyrac
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
- class repository_type implements cacheable_object {
- /**
- * Type name (no whitespace) - A type name is unique
- * Note: for a user-friendly type name see get_readablename()
- * @var String
- */
- private $_typename;
- /**
- * Options of this type
- * They are general options that any instance of this type would share
- * e.g. API key
- * These options are saved in config_plugin table
- * @var array
- */
- private $_options;
- /**
- * Is the repository type visible or hidden
- * If false (hidden): no instances can be created, edited, deleted, showned , used...
- * @var boolean
- */
- private $_visible;
- /**
- * 0 => not ordered, 1 => first position, 2 => second position...
- * A not order type would appear in first position (should never happened)
- * @var integer
- */
- private $_sortorder;
- /**
- * Return if the instance is visible in a context
- *
- * @todo check if the context visibility has been overwritten by the plugin creator
- * (need to create special functions to be overvwritten in repository class)
- * @param stdClass $context context
- * @return bool
- */
- public function get_contextvisibility($context) {
- global $USER;
- if ($context->contextlevel == CONTEXT_COURSE) {
- return $this->_options['enablecourseinstances'];
- }
- if ($context->contextlevel == CONTEXT_USER) {
- return $this->_options['enableuserinstances'];
- }
- //the context is SITE
- return true;
- }
- /**
- * repository_type constructor
- *
- * @param int $typename
- * @param array $typeoptions
- * @param bool $visible
- * @param int $sortorder (don't really need set, it will be during create() call)
- */
- public function __construct($typename = '', $typeoptions = array(), $visible = true, $sortorder = 0) {
- global $CFG;
- //set type attributs
- $this->_typename = $typename;
- $this->_visible = $visible;
- $this->_sortorder = $sortorder;
- //set options attribut
- $this->_options = array();
- $options = repository::static_function($typename, 'get_type_option_names');
- //check that the type can be setup
- if (!empty($options)) {
- //set the type options
- foreach ($options as $config) {
- if (array_key_exists($config, $typeoptions)) {
- $this->_options[$config] = $typeoptions[$config];
- }
- }
- }
- //retrieve visibility from option
- if (array_key_exists('enablecourseinstances',$typeoptions)) {
- $this->_options['enablecourseinstances'] = $typeoptions['enablecourseinstances'];
- } else {
- $this->_options['enablecourseinstances'] = 0;
- }
- if (array_key_exists('enableuserinstances',$typeoptions)) {
- $this->_options['enableuserinstances'] = $typeoptions['enableuserinstances'];
- } else {
- $this->_options['enableuserinstances'] = 0;
- }
- }
- /**
- * Get the type name (no whitespace)
- * For a human readable name, use get_readablename()
- *
- * @return string the type name
- */
- public function get_typename() {
- return $this->_typename;
- }
- /**
- * Return a human readable and user-friendly type name
- *
- * @return string user-friendly type name
- */
- public function get_readablename() {
- return get_string('pluginname','repository_'.$this->_typename);
- }
- /**
- * Return general options
- *
- * @return array the general options
- */
- public function get_options() {
- return $this->_options;
- }
- /**
- * Return visibility
- *
- * @return bool
- */
- public function get_visible() {
- return $this->_visible;
- }
- /**
- * Return order / position of display in the file picker
- *
- * @return int
- */
- public function get_sortorder() {
- return $this->_sortorder;
- }
- /**
- * Create a repository type (the type name must not already exist)
- * @param bool $silent throw exception?
- * @return mixed return int if create successfully, return false if
- */
- public function create($silent = false) {
- global $DB;
- //check that $type has been set
- $timmedtype = trim($this->_typename);
- if (empty($timmedtype)) {
- throw new repository_exception('emptytype', 'repository');
- }
- //set sortorder as the last position in the list
- if (!isset($this->_sortorder) || $this->_sortorder == 0 ) {
- $sql = "SELECT MAX(sortorder) FROM {repository}";
- $this->_sortorder = 1 + $DB->get_field_sql($sql);
- }
- //only create a new type if it doesn't already exist
- $existingtype = $DB->get_record('repository', array('type'=>$this->_typename));
- if (!$existingtype) {
- //create the type
- $newtype = new stdClass();
- $newtype->type = $this->_typename;
- $newtype->visible = $this->_visible;
- $newtype->sortorder = $this->_sortorder;
- $plugin_id = $DB->insert_record('repository', $newtype);
- //save the options in DB
- $this->update_options();
- $instanceoptionnames = repository::static_function($this->_typename, 'get_instance_option_names');
- //if the plugin type has no multiple instance (e.g. has no instance option name) so it wont
- //be possible for the administrator to create a instance
- //in this case we need to create an instance
- if (empty($instanceoptionnames)) {
- $instanceoptions = array();
- if (empty($this->_options['pluginname'])) {
- // when moodle trying to install some repo plugin automatically
- // this option will be empty, get it from language string when display
- $instanceoptions['name'] = '';
- } else {
- // when admin trying to add a plugin manually, he will type a name
- // for it
- $instanceoptions['name'] = $this->_options['pluginname'];
- }
- repository::static_function($this->_typename, 'create', $this->_typename, 0, context_system::instance(), $instanceoptions);
- }
- //run plugin_init function
- if (!repository::static_function($this->_typename, 'plugin_init')) {
- $this->update_visibility(false);
- if (!$silent) {
- throw new repository_exception('cannotinitplugin', 'repository');
- }
- }
- cache::make('core', 'repositories')->purge();
- if(!empty($plugin_id)) {
- // return plugin_id if create successfully
- return $plugin_id;
- } else {
- return false;
- }
- } else {
- if (!$silent) {
- throw new repository_exception('existingrepository', 'repository');
- }
- // If plugin existed, return false, tell caller no new plugins were created.
- return false;
- }
- }
- /**
- * Update plugin options into the config_plugin table
- *
- * @param array $options
- * @return bool
- */
- public function update_options($options = null) {
- global $DB;
- $classname = 'repository_' . $this->_typename;
- $instanceoptions = repository::static_function($this->_typename, 'get_instance_option_names');
- if (empty($instanceoptions)) {
- // update repository instance name if this plugin type doesn't have muliti instances
- $params = array();
- $params['type'] = $this->_typename;
- $instances = repository::get_instances($params);
- $instance = array_pop($instances);
- if ($instance) {
- $DB->set_field('repository_instances', 'name', $options['pluginname'], array('id'=>$instance->id));
- }
- unset($options['pluginname']);
- }
- if (!empty($options)) {
- $this->_options = $options;
- }
- foreach ($this->_options as $name => $value) {
- set_config($name, $value, $this->_typename);
- }
- cache::make('core', 'repositories')->purge();
- return true;
- }
- /**
- * Update visible database field with the value given as parameter
- * or with the visible value of this object
- * This function is private.
- * For public access, have a look to switch_and_update_visibility()
- *
- * @param bool $visible
- * @return bool
- */
- private function update_visible($visible = null) {
- global $DB;
- if (!empty($visible)) {
- $this->_visible = $visible;
- }
- else if (!isset($this->_visible)) {
- throw new repository_exception('updateemptyvisible', 'repository');
- }
- cache::make('core', 'repositories')->purge();
- return $DB->set_field('repository', 'visible', $this->_visible, array('type'=>$this->_typename));
- }
- /**
- * Update database sortorder field with the value given as parameter
- * or with the sortorder value of this object
- * This function is private.
- * For public access, have a look to move_order()
- *
- * @param int $sortorder
- * @return bool
- */
- private function update_sortorder($sortorder = null) {
- global $DB;
- if (!empty($sortorder) && $sortorder!=0) {
- $this->_sortorder = $sortorder;
- }
- //if sortorder is not set, we set it as the ;ast position in the list
- else if (!isset($this->_sortorder) || $this->_sortorder == 0 ) {
- $sql = "SELECT MAX(sortorder) FROM {repository}";
- $this->_sortorder = 1 + $DB->get_field_sql($sql);
- }
- cache::make('core', 'repositories')->purge();
- return $DB->set_field('repository', 'sortorder', $this->_sortorder, array('type'=>$this->_typename));
- }
- /**
- * Change order of the type with its adjacent upper or downer type
- * (database fields are updated)
- * Algorithm details:
- * 1. retrieve all types in an array. This array is sorted by sortorder,
- * and the array keys start from 0 to X (incremented by 1)
- * 2. switch sortorder values of this type and its adjacent type
- *
- * @param string $move "up" or "down"
- */
- public function move_order($move) {
- global $DB;
- $types = repository::get_types(); // retrieve all types
- // retrieve this type into the returned array
- $i = 0;
- while (!isset($indice) && $i<count($types)) {
- if ($types[$i]->get_typename() == $this->_typename) {
- $indice = $i;
- }
- $i++;
- }
- // retrieve adjacent indice
- switch ($move) {
- case "up":
- $adjacentindice = $indice - 1;
- break;
- case "down":
- $adjacentindice = $indice + 1;
- break;
- default:
- throw new repository_exception('movenotdefined', 'repository');
- }
- //switch sortorder of this type and the adjacent type
- //TODO: we could reset sortorder for all types. This is not as good in performance term, but
- //that prevent from wrong behaviour on a screwed database. As performance are not important in this particular case
- //it worth to change the algo.
- if ($adjacentindice>=0 && !empty($types[$adjacentindice])) {
- $DB->set_field('repository', 'sortorder', $this->_sortorder, array('type'=>$types[$adjacentindice]->get_typename()));
- $this->update_sortorder($types[$adjacentindice]->get_sortorder());
- }
- }
- /**
- * 1. Change visibility to the value chosen
- * 2. Update the type
- *
- * @param bool $visible
- * @return bool
- */
- public function update_visibility($visible = null) {
- if (is_bool($visible)) {
- $this->_visible = $visible;
- } else {
- $this->_visible = !$this->_visible;
- }
- return $this->update_visible();
- }
- /**
- * Delete a repository_type (general options are removed from config_plugin
- * table, and all instances are deleted)
- *
- * @param bool $downloadcontents download external contents if exist
- * @return bool
- */
- public function delete($downloadcontents = false) {
- global $DB;
- //delete all instances of this type
- $params = array();
- $params['context'] = array();
- $params['onlyvisible'] = false;
- $params['type'] = $this->_typename;
- $instances = repository::get_instances($params);
- foreach ($instances as $instance) {
- $instance->delete($downloadcontents);
- }
- //delete all general options
- foreach ($this->_options as $name => $value) {
- set_config($name, null, $this->_typename);
- }
- cache::make('core', 'repositories')->purge();
- try {
- $DB->delete_records('repository', array('type' => $this->_typename));
- } catch (dml_exception $ex) {
- return false;
- }
- return true;
- }
- /**
- * Prepares the repository type to be cached. Implements method from cacheable_object interface.
- *
- * @return array
- */
- public function prepare_to_cache() {
- return array(
- 'typename' => $this->_typename,
- 'typeoptions' => $this->_options,
- 'visible' => $this->_visible,
- 'sortorder' => $this->_sortorder
- );
- }
- /**
- * Restores repository type from cache. Implements method from cacheable_object interface.
- *
- * @return array
- */
- public static function wake_from_cache($data) {
- return new repository_type($data['typename'], $data['typeoptions'], $data['visible'], $data['sortorder']);
- }
- }
- /**
- * This is the base class of the repository class.
- *
- * To create repository plugin, see: {@link http://docs.moodle.org/dev/Repository_plugins}
- * See an example: {@link repository_boxnet}
- *
- * @package core_repository
- * @copyright 2009 Dongsheng Cai {@link http://dongsheng.org}
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
- abstract class repository implements cacheable_object {
- /**
- * Timeout in seconds for downloading the external file into moodle
- * @deprecated since Moodle 2.7, please use $CFG->repositorygetfiletimeout instead
- */
- const GETFILE_TIMEOUT = 30;
- /**
- * Timeout in seconds for syncronising the external file size
- * @deprecated since Moodle 2.7, please use $CFG->repositorysyncfiletimeout instead
- */
- const SYNCFILE_TIMEOUT = 1;
- /**
- * Timeout in seconds for downloading an image file from external repository during syncronisation
- * @deprecated since Moodle 2.7, please use $CFG->repositorysyncimagetimeout instead
- */
- const SYNCIMAGE_TIMEOUT = 3;
- // $disabled can be set to true to disable a plugin by force
- // example: self::$disabled = true
- /** @var bool force disable repository instance */
- public $disabled = false;
- /** @var int repository instance id */
- public $id;
- /** @var stdClass current context */
- public $context;
- /** @var array repository options */
- public $options;
- /** @var bool Whether or not the repository instance is editable */
- public $readonly;
- /** @var int return types */
- public $returntypes;
- /** @var stdClass repository instance database record */
- public $instance;
- /** @var string Type of repository (webdav, google_docs, dropbox, ...). Read from $this->get_typename(). */
- protected $typename;
- /**
- * Constructor
- *
- * @param int $repositoryid repository instance id
- * @param int|stdClass $context a context id or context object
- * @param array $options repository options
- * @param int $readonly indicate this repo is readonly or not
- */
- public function __construct($repositoryid, $context = SYSCONTEXTID, $options = array(), $readonly = 0) {
- global $DB;
- $this->id = $repositoryid;
- if (is_object($context)) {
- $this->context = $context;
- } else {
- $this->context = context::instance_by_id($context);
- }
- $cache = cache::make('core', 'repositories');
- if (($this->instance = $cache->get('i:'. $this->id)) === false) {
- $this->instance = $DB->get_record_sql("SELECT i.*, r.type AS repositorytype, r.sortorder, r.visible
- FROM {repository} r, {repository_instances} i
- WHERE i.typeid = r.id and i.id = ?", array('id' => $this->id));
- $cache->set('i:'. $this->id, $this->instance);
- }
- $this->readonly = $readonly;
- $this->options = array();
- if (is_array($options)) {
- // The get_option() method will get stored options in database.
- $options = array_merge($this->get_option(), $options);
- } else {
- $options = $this->get_option();
- }
- foreach ($options as $n => $v) {
- $this->options[$n] = $v;
- }
- $this->name = $this->get_name();
- $this->returntypes = $this->supported_returntypes();
- $this->super_called = true;
- }
- /**
- * Get repository instance using repository id
- *
- * Note that this function does not check permission to access repository contents
- *
- * @throws repository_exception
- *
- * @param int $repositoryid repository instance ID
- * @param context|int $context context instance or context ID where this repository will be used
- * @param array $options additional repository options
- * @return repository
- */
- public static function get_repository_by_id($repositoryid, $context, $options = array()) {
- global $CFG, $DB;
- $cache = cache::make('core', 'repositories');
- if (!is_object($context)) {
- $context = context::instance_by_id($context);
- }
- $cachekey = 'rep:'. $repositoryid. ':'. $context->id. ':'. serialize($options);
- if ($repository = $cache->get($cachekey)) {
- return $repository;
- }
- if (!$record = $cache->get('i:'. $repositoryid)) {
- $sql = "SELECT i.*, r.type AS repositorytype, r.visible, r.sortorder
- FROM {repository_instances} i
- JOIN {repository} r ON r.id = i.typeid
- WHERE i.id = ?";
- if (!$record = $DB->get_record_sql($sql, array($repositoryid))) {
- throw new repository_exception('invalidrepositoryid', 'repository');
- }
- $cache->set('i:'. $record->id, $record);
- }
- $type = $record->repositorytype;
- if (file_exists($CFG->dirroot . "/repository/$type/lib.php")) {
- require_once($CFG->dirroot . "/repository/$type/lib.php");
- $classname = 'repository_' . $type;
- $options['type'] = $type;
- $options['typeid'] = $record->typeid;
- $options['visible'] = $record->visible;
- if (empty($options['name'])) {
- $options['name'] = $record->name;
- }
- $repository = new $classname($repositoryid, $context, $options, $record->readonly);
- if (empty($repository->super_called)) {
- // to make sure the super construct is called
- debugging('parent::__construct must be called by '.$type.' plugin.');
- }
- $cache->set($cachekey, $repository);
- return $repository;
- } else {
- throw new repository_exception('invalidplugin', 'repository');
- }
- }
- /**
- * Returns the type name of the repository.
- *
- * @return string type name of the repository.
- * @since Moodle 2.5
- */
- public function get_typename() {
- if (empty($this->typename)) {
- $matches = array();
- if (!preg_match("/^repository_(.*)$/", get_class($this), $matches)) {
- throw new coding_exception('The class name of a repository should be repository_<typeofrepository>, '.
- 'e.g. repository_dropbox');
- }
- $this->typename = $matches[1];
- }
- return $this->typename;
- }
- /**
- * Get a repository type object by a given type name.
- *
- * @static
- * @param string $typename the repository type name
- * @return repository_type|bool
- */
- public static function get_type_by_typename($typename) {
- global $DB;
- $cache = cache::make('core', 'repositories');
- if (($repositorytype = $cache->get('typename:'. $typename)) === false) {
- $repositorytype = null;
- if ($record = $DB->get_record('repository', array('type' => $typename))) {
- $repositorytype = new repository_type($record->type, (array)get_config($record->type), $record->visible, $record->sortorder);
- $cache->set('typeid:'. $record->id, $repositorytype);
- }
- $cache->set('typename:'. $typename, $repositorytype);
- }
- return $repositorytype;
- }
- /**
- * Get the repository type by a given repository type id.
- *
- * @static
- * @param int $id the type id
- * @return object
- */
- public static function get_type_by_id($id) {
- global $DB;
- $cache = cache::make('core', 'repositories');
- if (($repositorytype = $cache->get('typeid:'. $id)) === false) {
- $repositorytype = null;
- if ($record = $DB->get_record('repository', array('id' => $id))) {
- $repositorytype = new repository_type($record->type, (array)get_config($record->type), $record->visible, $record->sortorder);
- $cache->set('typename:'. $record->type, $repositorytype);
- }
- $cache->set('typeid:'. $id, $repositorytype);
- }
- return $repositorytype;
- }
- /**
- * Return all repository types ordered by sortorder field
- * first repository type in returnedarray[0], second repository type in returnedarray[1], ...
- *
- * @static
- * @param bool $visible can return types by visiblity, return all types if null
- * @return array Repository types
- */
- public static function get_types($visible=null) {
- global $DB, $CFG;
- $cache = cache::make('core', 'repositories');
- if (!$visible) {
- $typesnames = $cache->get('types');
- } else {
- $typesnames = $cache->get('typesvis');
- }
- $types = array();
- if ($typesnames === false) {
- $typesnames = array();
- $vistypesnames = array();
- if ($records = $DB->get_records('repository', null ,'sortorder')) {
- foreach($records as $type) {
- if (($repositorytype = $cache->get('typename:'. $type->type)) === false) {
- // Create new instance of repository_type.
- if (file_exists($CFG->dirroot . '/repository/'. $type->type .'/lib.php')) {
- $repositorytype = new repository_type($type->type, (array)get_config($type->type), $type->visible, $type->sortorder);
- $cache->set('typeid:'. $type->id, $repositorytype);
- $cache->set('typename:'. $type->type, $repositorytype);
- }
- }
- if ($repositorytype) {
- if (empty($visible) || $repositorytype->get_visible()) {
- $types[] = $repositorytype;
- $vistypesnames[] = $repositorytype->get_typename();
- }
- $typesnames[] = $repositorytype->get_typename();
- }
- }
- }
- $cache->set('types', $typesnames);
- $cache->set('typesvis', $vistypesnames);
- } else {
- foreach ($typesnames as $typename) {
- $types[] = self::get_type_by_typename($typename);
- }
- }
- return $types;
- }
- /**
- * Checks if user has a capability to view the current repository.
- *
- * @return bool true when the user can, otherwise throws an exception.
- * @throws repository_exception when the user does not meet the requirements.
- */
- public final function check_capability() {
- global $USER;
- // The context we are on.
- $currentcontext = $this->context;
- // Ensure that the user can view the repository in the current context.
- $can = has_capability('repository/'.$this->get_typename().':view', $currentcontext);
- // Context in which the repository has been created.
- $repocontext = context::instance_by_id($this->instance->contextid);
- // Prevent access to private repositories when logged in as.
- if ($can && \core\session\manager::is_loggedinas()) {
- if ($this->contains_private_data() || $repocontext->contextlevel == CONTEXT_USER) {
- $can = false;
- }
- }
- // We are going to ensure that the current context was legit, and reliable to check
- // the capability against. (No need to do that if we already cannot).
- if ($can) {
- if ($repocontext->contextlevel == CONTEXT_USER) {
- // The repository is a user instance, ensure we're the right user to access it!
- if ($repocontext->instanceid != $USER->id) {
- $can = false;
- }
- } else if ($repocontext->contextlevel == CONTEXT_COURSE) {
- // The repository is a course one. Let's check that we are on the right course.
- if (in_array($currentcontext->contextlevel, array(CONTEXT_COURSE, CONTEXT_MODULE, CONTEXT_BLOCK))) {
- $coursecontext = $currentcontext->get_course_context();
- if ($coursecontext->instanceid != $repocontext->instanceid) {
- $can = false;
- }
- } else {
- // We are on a parent context, therefore it's legit to check the permissions
- // in the current context.
- }
- } else {
- // Nothing to check here, system instances can have different permissions on different
- // levels. We do not want to prevent URL hack here, because it does not make sense to
- // prevent a user to access a repository in a context if it's accessible in another one.
- }
- }
- if ($can) {
- return true;
- }
- throw new repository_exception('nopermissiontoaccess', 'repository');
- }
- /**
- * Check if file already exists in draft area.
- *
- * @static
- * @param int $itemid of the draft area.
- * @param string $filepath path to the file.
- * @param string $filename file name.
- * @return bool
- */
- public static function draftfile_exists($itemid, $filepath, $filename) {
- global $USER;
- $fs = get_file_storage();
- $usercontext = context_user::instance($USER->id);
- return $fs->file_exists($usercontext->id, 'user', 'draft', $itemid, $filepath, $filename);
- }
- /**
- * Parses the moodle file reference and returns an instance of stored_file
- *
- * @param string $reference reference to the moodle internal file as retruned by
- * {@link repository::get_file_reference()} or {@link file_storage::pack_reference()}
- * @return stored_file|null
- */
- public static function get_moodle_file($reference) {
- $params = file_storage::unpack_reference($reference, true);
- $fs = get_file_storage();
- return $fs->get_file($params['contextid'], $params['component'], $params['filearea'],
- $params['itemid'], $params['filepath'], $params['filename']);
- }
- /**
- * Repository method to make sure that user can access particular file.
- *
- * This is checked when user tries to pick the file from repository to deal with
- * potential parameter substitutions is request
- *
- * @param string $source source of the file, returned by repository as 'source' and received back from user (not cleaned)
- * @return bool whether the file is accessible by current user
- */
- public function file_is_accessible($source) {
- if ($this->has_moodle_files()) {
- $reference = $this->get_file_reference($source);
- try {
- $params = file_storage::unpack_reference($reference, true);
- } catch (file_reference_exception $e) {
- return false;
- }
- $browser = get_file_browser();
- $context = context::instance_by_id($params['contextid']);
- $file_info = $browser->get_file_info($context, $params['component'], $params['filearea'],
- $params['itemid'], $params['filepath'], $params['filename']);
- return !empty($file_info);
- }
- return true;
- }
- /**
- * This function is used to copy a moodle file to draft area.
- *
- * It DOES NOT check if the user is allowed to access this file because the actual file
- * can be located in the area where user does not have access to but there is an alias
- * to this file in the area where user CAN access it.
- * {@link file_is_accessible} should be called for alias location before calling this function.
- *
- * @param string $source The metainfo of file, it is base64 encoded php serialized data
- * @param stdClass|array $filerecord contains itemid, filepath, filename and optionally other
- * attributes of the new file
- * @param int $maxbytes maximum allowed size of file, -1 if unlimited. If size of file exceeds
- * the limit, the file_exception is thrown.
- * @param int $areamaxbytes the maximum size of the area. A file_exception is thrown if the
- * new file will reach the limit.
- * @return array The information about the created file
- */
- public function copy_to_area($source, $filerecord, $maxbytes = -1, $areamaxbytes = FILE_AREA_MAX_BYTES_UNLIMITED) {
- global $USER;
- $fs = get_file_storage();
- if ($this->has_moodle_files() == false) {
- throw new coding_exception('Only repository used to browse moodle files can use repository::copy_to_area()');
- }
- $user_context = context_user::instance($USER->id);
- $filerecord = (array)$filerecord;
- // make sure the new file will be created in user draft area
- $filerecord['component'] = 'user';
- $filerecord['filearea'] = 'draft';
- $filerecord['contextid'] = $user_context->id;
- $draftitemid = $filerecord['itemid'];
- $new_filepath = $filerecord['filepath'];
- $new_filename = $filerecord['filename'];
- // the file needs to copied to draft area
- $stored_file = self::get_moodle_file($source);
- if ($maxbytes != -1 && $stored_file->get_filesize() > $maxbytes) {
- $maxbytesdisplay = display_size($maxbytes);
- throw new file_exception('maxbytesfile', (object) array('file' => $filerecord['filename'],
- 'size' => $maxbytesdisplay));
- }
- // Validate the size of the draft area.
- if (file_is_draft_area_limit_reached($draftitemid, $areamaxbytes, $stored_file->get_filesize())) {
- throw new file_exception('maxareabytes');
- }
- if (repository::draftfile_exists($draftitemid, $new_filepath, $new_filename)) {
- // create new file
- $unused_filename = repository::get_unused_filename($draftitemid, $new_filepath, $new_filename);
- $filerecord['filename'] = $unused_filename;
- $fs->create_file_from_storedfile($filerecord, $stored_file);
- $event = array();
- $event['event'] = 'fileexists';
- $event['newfile'] = new stdClass;
- $event['newfile']->filepath = $new_filepath;
- $event['newfile']->filename = $unused_filename;
- $event['newfile']->url = moodle_url::make_draftfile_url($draftitemid, $new_filepath, $unused_filename)->out();
- $event['existingfile'] = new stdClass;
- $event['existingfile']->filepath = $new_filepath;
- $event['existingfile']->filename = $new_filename;
- $event['existingfile']->url = moodle_url::make_draftfile_url($draftitemid, $new_filepath, $new_filename)->out();
- return $event;
- } else {
- $fs->create_file_from_storedfile($filerecord, $stored_file);
- $info = array();
- $info['itemid'] = $draftitemid;
- $info['file'] = $new_filename;
- $info['title'] = $new_filename;
- $info['contextid'] = $user_context->id;
- $info['url'] = moodle_url::make_draftfile_url($draftitemid, $new_filepath, $new_filename)->out();
- $info['filesize'] = $stored_file->get_filesize();
- return $info;
- }
- }
- /**
- * Get an unused filename from the current draft area.
- *
- * Will check if the file ends with ([0-9]) and increase the number.
- *
- * @static
- * @param int $itemid draft item ID.
- * @param string $filepath path to the file.
- * @param string $filename name of the file.
- * @return string an unused file name.
- */
- public static function get_unused_filename($itemid, $filepath, $filename) {
- global $USER;
- $contextid = context_user::instance($USER->id)->id;
- $fs = get_file_storage();
- return $fs->get_unused_filename($contextid, 'user', 'draft', $itemid, $filepath, $filename);
- }
- /**
- * Append a suffix to filename.
- *
- * @static
- * @param string $filename
- * @return string
- * @deprecated since 2.5
- */
- public static function append_suffix($filename) {
- debugging('The function repository::append_suffix() has been deprecated. Use repository::get_unused_filename() instead.',
- DEBUG_DEVELOPER);
- $pathinfo = pathinfo($filename);
- if (empty($pathinfo['extension'])) {
- return $filename . RENAME_SUFFIX;
- } else {
- return $pathinfo['filename'] . RENAME_SUFFIX . '.' . $pathinfo['extension'];
- }
- }
- /**
- * Return all types that you a user can create/edit and which are also visible
- * Note: Mostly used in order to know if at least one editable type can be set
- *
- * @static
- * @param stdClass $context the context for which we want the editable types
- * @return array types
- */
- public static function get_editable_types($context = null) {
- if (empty($context)) {
- $context = context_system::instance();
- }
- $types= repository::get_types(true);
- $editabletypes = array();
- foreach ($types as $type) {
- $instanceoptionnames = repository::static_function($type->get_typename(), 'get_instance_option_names');
- if (!empty($instanceoptionnames)) {
- if ($type->get_contextvisibility($context)) {
- $editabletypes[]=$type;
- }
- }
- }
- return $editabletypes;
- }
- /**
- * Return repository instances
- *
- * @static
- * @param array $args Array containing the following keys:
- * currentcontext : instance of context (default system context)
- * context : array of instances of context (default empty array)
- * onlyvisible : bool (default true)
- * type : string return instances of this type only
- * accepted_types : string|array return instances that contain files of those types (*, web_image, .pdf, ...)
- * return_types : int combination of FILE_INTERNAL & FILE_EXTERNAL & FILE_REFERENCE & FILE_CONTROLLED_LINK.
- * 0 means every type. The default is FILE_INTERNAL | FILE_EXTERNAL.
- * userid : int if specified, instances belonging to other users will not be returned
- *
- * @return array repository instances
- */
- public static function get_instances($args = array()) {
- global $DB, $CFG, $USER;
- // Fill $args attributes with default values unless specified
- if (!isset($args['currentcontext']) || !($args['currentcontext'] instanceof context)) {
- $current_context = context_system::instance();
- } else {
- $current_context = $args['currentcontext'];
- }
- $args['currentcontext'] = $current_context->id;
- $contextids = array();
- if (!empty($args['context'])) {
- foreach ($args['context'] as $context) {
- $contextids[] = $context->id;
- }
- }
- $args['context'] = $contextids;
- if (!isset($args['onlyvisible'])) {
- $args['onlyvisible'] = true;
- }
- if (!isset($args['return_types'])) {
- $args['return_types'] = FILE_INTERNAL | FILE_EXTERNAL;
- }
- if (!isset($args['type'])) {
- $args['type'] = null;
- }
- if (empty($args['disable_types']) || !is_array($args['disable_types'])) {
- $args['disable_types'] = null;
- }
- if (empty($args['userid']) || !is_numeric($args['userid'])) {
- $args['userid'] = null;
- }
- if (!isset($args['accepted_types']) || (is_array($args['accepted_types']) && in_array('*', $args['accepted_types']))) {
- $args['accepted_types'] = '*';
- }
- ksort($args);
- $cachekey = 'all:'. serialize($args);
- // Check if we have cached list of repositories with the same query
- $cache = cache::make('core', 'repositories');
- if (($cachedrepositories = $cache->get($cachekey)) !== false) {
- // convert from cacheable_object_array to array
- $repositories = array();
- foreach ($cachedrepositories as $repository) {
- $repositories[$repository->id] = $repository;
- }
- return $repositories;
- }
- // Prepare DB SQL query to retrieve repositories
- $params = array();
- $sql = "SELECT i.*, r.type AS repositorytype, r.sortorder, r.visible
- FROM {repository} r, {repository_instances} i
- WHERE i.typeid = r.id ";
- if ($args['disable_types']) {
- list($types, $p) = $DB->get_in_or_equal($args['disable_types'], SQL_PARAMS_NAMED, 'distype', false);
- $sql .= " AND r.type $types";
- $params = array_merge($params, $p);
- }
- if ($args['userid']) {
- $sql .= " AND (i.userid = 0 or i.userid = :userid)";
- $params['userid'] = $args['userid'];
- }
- if ($args['context']) {
- list($ctxsql, $p2) = $DB->get_in_or_equal($args['context'], SQL_PARAMS_NAMED, 'ctx');
- $sql .= " AND i.contextid $ctxsql";
- $params = array_merge($params, $p2);
- }
- if ($args['onlyvisible'] == true) {
- $sql .= " AND r.visible = 1";
- }
- if ($args['type'] !== null) {
- $sql .= " AND r.type = :type";
- $params['type'] = $args['type'];
- }
- $sql .= " ORDER BY r.sortorder, i.name";
- if (!$records = $DB->get_records_sql($sql, $params)) {
- $records = array();
- }
- $repositories = array();
- // Sortorder should be unique, which is not true if we use $record->sortorder
- // and there are multiple instances of any repository type
- $sortorder = 1;
- foreach ($records as $record) {
- $cache->set('i:'. $record->id, $record);
- if (!file_exists($CFG->dirroot . '/repository/'. $record->repositorytype.'/lib.php')) {
- continue;
- }
- $repository = self::get_repository_by_id($record->id, $current_context);
- $repository->options['sortorder'] = $sortorder++;
- $is_supported = true;
- // check mimetypes
- if ($args['accepted_types'] !== '*' and $repository->supported_filetypes() !== '*') {
- $accepted_ext = file_get_typegroup('extension', $args['accepted_types']);
- $supported_ext = file_get_typegroup('extension', $repository->supported_filetypes());
- $valid_ext = array_intersect($accepted_ext, $supported_ext);
- $is_supported = !empty($valid_ext);
- }
- // Check return values.
- if (!empty($args['return_types']) && !($repository->supported_returntypes() & $args['return_types'])) {
- $is_supported = false;
- }
- if (!$args['onlyvisible'] || ($repository->is_visible() && !$repository->disabled)) {
- // check capability in current context
- $capability = has_capability('repository/'.$record->repositorytype.':view', $current_context);
- if ($record->repositorytype == 'coursefiles') {
- // coursefiles plugin needs managefiles permission
- $capability = $capability && has_capability('moodle/course:managefiles', $current_context);
- }
- if ($is_supported && $capability) {
- $repositories[$repository->id] = $repository;
- }
- }
- }
- $cache->set($cachekey, new cacheable_object_array($repositories));
- return $repositories;
- }
- /**
- * Get single repository instance for administrative actions
- *
- * Do not use this function to access repository contents, because it
- * does not set the current context
- *
- * @see repository::get_repository_by_id()
- *
- * @static
- * @param integer $id repository instance id
- * @return repository
- */
- public static function get_instance($id) {
- return self::get_repository_by_id($id, context_system::instance());
- }
- /**
- * Call a static function. Any additional arguments than plugin and function will be passed through.
- *
- * @static
- * @param string $plugin repository plugin name
- * @param string $function function name
- * @return mixed
- */
- public static function static_function($plugin, $function) {
- global $CFG;
- //check that the plugin exists
- $typedirectory = $CFG->dirroot . '/repository/'. $plugin . '/lib.php';
- if (!file_exists($typedirectory)) {
- //throw new repository_exception('invalidplugin', 'repository');
- return false;
- }
- $args = func_get_args();
- if (count($args) <= 2) {
- $args = array();
- } else {
- array_shift($args);
- array_shift($args);
- }
- require_once($typedirectory);
- return call_user_func_array(array('repository_' . $plugin, $function), $args);
- }
- /**
- * Scan file, throws exception in case of infected file.
- *
- * Please note that the scanning engine must be able to access the file,
- * permissions of the file are not modified here!
- *
- * @static
- * @deprecated since Moodle 3.0
- * @param string $thefile
- * @param string $filename name of the file
- * @param bool $deleteinfected
- */
- public static function antivir_scan_file($thefile, $filename, $deleteinfected) {
- debugging('Please upgrade your code to use \core\antivirus\manager::scan_file instead', DEBUG_DEVELOPER);
- \core\antivirus\manager::scan_file($thefile, $filename, $deleteinfected);
- }
- /**
- * Repository method to serve the referenced file
- *
- * @see send_stored_file
- *
- * @param stored_file $storedfile the file that contains the reference
- * @param int $lifetime Number of seconds before the file should expire from caches (null means $CFG->filelifetime)
- * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only
- * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin
- * @param array $options additional options affecting the file serving
- */
- public function send_file($storedfile, $lifetime=null , $filter=0, $forcedownload=false, array $options = null) {
- if ($this->has_moodle_files()) {
- $fs = get_file_storage();
- $params = file_storage::unpack_reference($storedfile->get_reference(), true);
- $srcfile = null;
- if (is_array($params)) {
- $srcfile = $fs->get_file($params['contextid'], $params['component'], $params['filearea'],
- $params['itemid'], $params['filepath'], $params['filename']);
- }
- if (empty($options)) {
- $options = array();
- }
- if (!isset($options['filename'])) {
- $options['filename'] = $storedfile->get_filename();
- }
- if (!$srcfile) {
- send_file_not_found();
- } else {
- send_stored_file($srcfile, $lifetime, $filter, $forcedownload, $options);
- }
- } else {
- throw new coding_exception("Repository plugin must implement send_file() method.");
- }
- }
- /**
- * Return human readable reference information
- *
- * @param string $reference value of DB field files_reference.reference
- * @param int $filestatus status of the file, 0 - ok, 666 - source missing
- * @return string
- */
- public function get_reference_details($reference, $filestatus = 0) {
- if ($this->has_moodle_files()) {
- $fileinfo = null;
- $params = file_storage::unpack_reference($reference, true);
- if (is_array($params)) {
- $context = context::instance_by_id($params['contextid'], IGNORE_MISSING);
- if ($context) {
- $browser = get_file_browser();
- $fileinfo = $browser->get_file_info($context, $params['component'], $params['filearea'], $params['itemid'], $params['filepath'], $params['filename']);
- }
- }
- if (empty($fileinfo)) {
- if ($filestatus == 666) {
- if (is_siteadmin() || ($context && has_capability('moodle/course:managefiles', $context))) {
- return get_string('lostsource', 'repository',
- $params['contextid']. '/'. $params['component']. '/'. $params['filearea']. '/'. $params['itemid']. $params['filepath']. $params['filename']);
- } else {
- return get_string('lostsource', 'repository', '');
- }
- }
- return get_string('undisclosedsource', 'repository');
- } else {
- return $fileinfo->get_readable_fullname();
- }
- }
- return '';
- }
- /**
- * Cache file from external repository by reference
- * {@link repository::get_file_reference()}
- * {@link repository::get_file()}
- * Invoked at MOODLE/repository/repository_ajax.php
- *
- * @param string $reference this reference is generated by
- * repository::ge…
Large files files are truncated, but you can click here to view the full file