PageRenderTime 56ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/repository/lib.php

https://github.com/dongsheng/moodle
PHP | 3310 lines | 1848 code | 294 blank | 1168 comment | 408 complexity | 9d26368b8b770f458765c3625bc6d938 MD5 | raw file
Possible License(s): BSD-3-Clause, MIT, GPL-3.0, Apache-2.0, LGPL-2.1

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. * This file contains classes used to manage the repository plugins in Moodle
  18. *
  19. * @since Moodle 2.0
  20. * @package core_repository
  21. * @copyright 2009 Dongsheng Cai {@link http://dongsheng.org}
  22. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23. */
  24. defined('MOODLE_INTERNAL') || die();
  25. require_once($CFG->libdir . '/filelib.php');
  26. require_once($CFG->libdir . '/formslib.php');
  27. define('FILE_EXTERNAL', 1);
  28. define('FILE_INTERNAL', 2);
  29. define('FILE_REFERENCE', 4);
  30. define('FILE_CONTROLLED_LINK', 8);
  31. define('RENAME_SUFFIX', '_2');
  32. /**
  33. * This class is used to manage repository plugins
  34. *
  35. * A repository_type is a repository plug-in. It can be Box.net, Flick-r, ...
  36. * A repository type can be edited, sorted and hidden. It is mandatory for an
  37. * administrator to create a repository type in order to be able to create
  38. * some instances of this type.
  39. * Coding note:
  40. * - a repository_type object is mapped to the "repository" database table
  41. * - "typename" attibut maps the "type" database field. It is unique.
  42. * - general "options" for a repository type are saved in the config_plugin table
  43. * - when you delete a repository, all instances are deleted, and general
  44. * options are also deleted from database
  45. * - When you create a type for a plugin that can't have multiple instances, a
  46. * instance is automatically created.
  47. *
  48. * @package core_repository
  49. * @copyright 2009 Jerome Mouneyrac
  50. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  51. */
  52. class repository_type implements cacheable_object {
  53. /**
  54. * Type name (no whitespace) - A type name is unique
  55. * Note: for a user-friendly type name see get_readablename()
  56. * @var String
  57. */
  58. private $_typename;
  59. /**
  60. * Options of this type
  61. * They are general options that any instance of this type would share
  62. * e.g. API key
  63. * These options are saved in config_plugin table
  64. * @var array
  65. */
  66. private $_options;
  67. /**
  68. * Is the repository type visible or hidden
  69. * If false (hidden): no instances can be created, edited, deleted, showned , used...
  70. * @var boolean
  71. */
  72. private $_visible;
  73. /**
  74. * 0 => not ordered, 1 => first position, 2 => second position...
  75. * A not order type would appear in first position (should never happened)
  76. * @var integer
  77. */
  78. private $_sortorder;
  79. /**
  80. * Return if the instance is visible in a context
  81. *
  82. * @todo check if the context visibility has been overwritten by the plugin creator
  83. * (need to create special functions to be overvwritten in repository class)
  84. * @param stdClass $context context
  85. * @return bool
  86. */
  87. public function get_contextvisibility($context) {
  88. global $USER;
  89. if ($context->contextlevel == CONTEXT_COURSE) {
  90. return $this->_options['enablecourseinstances'];
  91. }
  92. if ($context->contextlevel == CONTEXT_USER) {
  93. return $this->_options['enableuserinstances'];
  94. }
  95. //the context is SITE
  96. return true;
  97. }
  98. /**
  99. * repository_type constructor
  100. *
  101. * @param int $typename
  102. * @param array $typeoptions
  103. * @param bool $visible
  104. * @param int $sortorder (don't really need set, it will be during create() call)
  105. */
  106. public function __construct($typename = '', $typeoptions = array(), $visible = true, $sortorder = 0) {
  107. global $CFG;
  108. //set type attributs
  109. $this->_typename = $typename;
  110. $this->_visible = $visible;
  111. $this->_sortorder = $sortorder;
  112. //set options attribut
  113. $this->_options = array();
  114. $options = repository::static_function($typename, 'get_type_option_names');
  115. //check that the type can be setup
  116. if (!empty($options)) {
  117. //set the type options
  118. foreach ($options as $config) {
  119. if (array_key_exists($config, $typeoptions)) {
  120. $this->_options[$config] = $typeoptions[$config];
  121. }
  122. }
  123. }
  124. //retrieve visibility from option
  125. if (array_key_exists('enablecourseinstances',$typeoptions)) {
  126. $this->_options['enablecourseinstances'] = $typeoptions['enablecourseinstances'];
  127. } else {
  128. $this->_options['enablecourseinstances'] = 0;
  129. }
  130. if (array_key_exists('enableuserinstances',$typeoptions)) {
  131. $this->_options['enableuserinstances'] = $typeoptions['enableuserinstances'];
  132. } else {
  133. $this->_options['enableuserinstances'] = 0;
  134. }
  135. }
  136. /**
  137. * Get the type name (no whitespace)
  138. * For a human readable name, use get_readablename()
  139. *
  140. * @return string the type name
  141. */
  142. public function get_typename() {
  143. return $this->_typename;
  144. }
  145. /**
  146. * Return a human readable and user-friendly type name
  147. *
  148. * @return string user-friendly type name
  149. */
  150. public function get_readablename() {
  151. return get_string('pluginname','repository_'.$this->_typename);
  152. }
  153. /**
  154. * Return general options
  155. *
  156. * @return array the general options
  157. */
  158. public function get_options() {
  159. return $this->_options;
  160. }
  161. /**
  162. * Return visibility
  163. *
  164. * @return bool
  165. */
  166. public function get_visible() {
  167. return $this->_visible;
  168. }
  169. /**
  170. * Return order / position of display in the file picker
  171. *
  172. * @return int
  173. */
  174. public function get_sortorder() {
  175. return $this->_sortorder;
  176. }
  177. /**
  178. * Create a repository type (the type name must not already exist)
  179. * @param bool $silent throw exception?
  180. * @return mixed return int if create successfully, return false if
  181. */
  182. public function create($silent = false) {
  183. global $DB;
  184. //check that $type has been set
  185. $timmedtype = trim($this->_typename);
  186. if (empty($timmedtype)) {
  187. throw new repository_exception('emptytype', 'repository');
  188. }
  189. //set sortorder as the last position in the list
  190. if (!isset($this->_sortorder) || $this->_sortorder == 0 ) {
  191. $sql = "SELECT MAX(sortorder) FROM {repository}";
  192. $this->_sortorder = 1 + $DB->get_field_sql($sql);
  193. }
  194. //only create a new type if it doesn't already exist
  195. $existingtype = $DB->get_record('repository', array('type'=>$this->_typename));
  196. if (!$existingtype) {
  197. //create the type
  198. $newtype = new stdClass();
  199. $newtype->type = $this->_typename;
  200. $newtype->visible = $this->_visible;
  201. $newtype->sortorder = $this->_sortorder;
  202. $plugin_id = $DB->insert_record('repository', $newtype);
  203. //save the options in DB
  204. $this->update_options();
  205. $instanceoptionnames = repository::static_function($this->_typename, 'get_instance_option_names');
  206. //if the plugin type has no multiple instance (e.g. has no instance option name) so it wont
  207. //be possible for the administrator to create a instance
  208. //in this case we need to create an instance
  209. if (empty($instanceoptionnames)) {
  210. $instanceoptions = array();
  211. if (empty($this->_options['pluginname'])) {
  212. // when moodle trying to install some repo plugin automatically
  213. // this option will be empty, get it from language string when display
  214. $instanceoptions['name'] = '';
  215. } else {
  216. // when admin trying to add a plugin manually, he will type a name
  217. // for it
  218. $instanceoptions['name'] = $this->_options['pluginname'];
  219. }
  220. repository::static_function($this->_typename, 'create', $this->_typename, 0, context_system::instance(), $instanceoptions);
  221. }
  222. //run plugin_init function
  223. if (!repository::static_function($this->_typename, 'plugin_init')) {
  224. $this->update_visibility(false);
  225. if (!$silent) {
  226. throw new repository_exception('cannotinitplugin', 'repository');
  227. }
  228. }
  229. cache::make('core', 'repositories')->purge();
  230. if(!empty($plugin_id)) {
  231. // return plugin_id if create successfully
  232. return $plugin_id;
  233. } else {
  234. return false;
  235. }
  236. } else {
  237. if (!$silent) {
  238. throw new repository_exception('existingrepository', 'repository');
  239. }
  240. // If plugin existed, return false, tell caller no new plugins were created.
  241. return false;
  242. }
  243. }
  244. /**
  245. * Update plugin options into the config_plugin table
  246. *
  247. * @param array $options
  248. * @return bool
  249. */
  250. public function update_options($options = null) {
  251. global $DB;
  252. $classname = 'repository_' . $this->_typename;
  253. $instanceoptions = repository::static_function($this->_typename, 'get_instance_option_names');
  254. if (empty($instanceoptions)) {
  255. // update repository instance name if this plugin type doesn't have muliti instances
  256. $params = array();
  257. $params['type'] = $this->_typename;
  258. $instances = repository::get_instances($params);
  259. $instance = array_pop($instances);
  260. if ($instance) {
  261. $DB->set_field('repository_instances', 'name', $options['pluginname'], array('id'=>$instance->id));
  262. }
  263. unset($options['pluginname']);
  264. }
  265. if (!empty($options)) {
  266. $this->_options = $options;
  267. }
  268. foreach ($this->_options as $name => $value) {
  269. set_config($name, $value, $this->_typename);
  270. }
  271. cache::make('core', 'repositories')->purge();
  272. return true;
  273. }
  274. /**
  275. * Update visible database field with the value given as parameter
  276. * or with the visible value of this object
  277. * This function is private.
  278. * For public access, have a look to switch_and_update_visibility()
  279. *
  280. * @param bool $visible
  281. * @return bool
  282. */
  283. private function update_visible($visible = null) {
  284. global $DB;
  285. if (!empty($visible)) {
  286. $this->_visible = $visible;
  287. }
  288. else if (!isset($this->_visible)) {
  289. throw new repository_exception('updateemptyvisible', 'repository');
  290. }
  291. cache::make('core', 'repositories')->purge();
  292. return $DB->set_field('repository', 'visible', $this->_visible, array('type'=>$this->_typename));
  293. }
  294. /**
  295. * Update database sortorder field with the value given as parameter
  296. * or with the sortorder value of this object
  297. * This function is private.
  298. * For public access, have a look to move_order()
  299. *
  300. * @param int $sortorder
  301. * @return bool
  302. */
  303. private function update_sortorder($sortorder = null) {
  304. global $DB;
  305. if (!empty($sortorder) && $sortorder!=0) {
  306. $this->_sortorder = $sortorder;
  307. }
  308. //if sortorder is not set, we set it as the ;ast position in the list
  309. else if (!isset($this->_sortorder) || $this->_sortorder == 0 ) {
  310. $sql = "SELECT MAX(sortorder) FROM {repository}";
  311. $this->_sortorder = 1 + $DB->get_field_sql($sql);
  312. }
  313. cache::make('core', 'repositories')->purge();
  314. return $DB->set_field('repository', 'sortorder', $this->_sortorder, array('type'=>$this->_typename));
  315. }
  316. /**
  317. * Change order of the type with its adjacent upper or downer type
  318. * (database fields are updated)
  319. * Algorithm details:
  320. * 1. retrieve all types in an array. This array is sorted by sortorder,
  321. * and the array keys start from 0 to X (incremented by 1)
  322. * 2. switch sortorder values of this type and its adjacent type
  323. *
  324. * @param string $move "up" or "down"
  325. */
  326. public function move_order($move) {
  327. global $DB;
  328. $types = repository::get_types(); // retrieve all types
  329. // retrieve this type into the returned array
  330. $i = 0;
  331. while (!isset($indice) && $i<count($types)) {
  332. if ($types[$i]->get_typename() == $this->_typename) {
  333. $indice = $i;
  334. }
  335. $i++;
  336. }
  337. // retrieve adjacent indice
  338. switch ($move) {
  339. case "up":
  340. $adjacentindice = $indice - 1;
  341. break;
  342. case "down":
  343. $adjacentindice = $indice + 1;
  344. break;
  345. default:
  346. throw new repository_exception('movenotdefined', 'repository');
  347. }
  348. //switch sortorder of this type and the adjacent type
  349. //TODO: we could reset sortorder for all types. This is not as good in performance term, but
  350. //that prevent from wrong behaviour on a screwed database. As performance are not important in this particular case
  351. //it worth to change the algo.
  352. if ($adjacentindice>=0 && !empty($types[$adjacentindice])) {
  353. $DB->set_field('repository', 'sortorder', $this->_sortorder, array('type'=>$types[$adjacentindice]->get_typename()));
  354. $this->update_sortorder($types[$adjacentindice]->get_sortorder());
  355. }
  356. }
  357. /**
  358. * 1. Change visibility to the value chosen
  359. * 2. Update the type
  360. *
  361. * @param bool $visible
  362. * @return bool
  363. */
  364. public function update_visibility($visible = null) {
  365. if (is_bool($visible)) {
  366. $this->_visible = $visible;
  367. } else {
  368. $this->_visible = !$this->_visible;
  369. }
  370. return $this->update_visible();
  371. }
  372. /**
  373. * Delete a repository_type (general options are removed from config_plugin
  374. * table, and all instances are deleted)
  375. *
  376. * @param bool $downloadcontents download external contents if exist
  377. * @return bool
  378. */
  379. public function delete($downloadcontents = false) {
  380. global $DB;
  381. //delete all instances of this type
  382. $params = array();
  383. $params['context'] = array();
  384. $params['onlyvisible'] = false;
  385. $params['type'] = $this->_typename;
  386. $instances = repository::get_instances($params);
  387. foreach ($instances as $instance) {
  388. $instance->delete($downloadcontents);
  389. }
  390. //delete all general options
  391. foreach ($this->_options as $name => $value) {
  392. set_config($name, null, $this->_typename);
  393. }
  394. cache::make('core', 'repositories')->purge();
  395. try {
  396. $DB->delete_records('repository', array('type' => $this->_typename));
  397. } catch (dml_exception $ex) {
  398. return false;
  399. }
  400. return true;
  401. }
  402. /**
  403. * Prepares the repository type to be cached. Implements method from cacheable_object interface.
  404. *
  405. * @return array
  406. */
  407. public function prepare_to_cache() {
  408. return array(
  409. 'typename' => $this->_typename,
  410. 'typeoptions' => $this->_options,
  411. 'visible' => $this->_visible,
  412. 'sortorder' => $this->_sortorder
  413. );
  414. }
  415. /**
  416. * Restores repository type from cache. Implements method from cacheable_object interface.
  417. *
  418. * @return array
  419. */
  420. public static function wake_from_cache($data) {
  421. return new repository_type($data['typename'], $data['typeoptions'], $data['visible'], $data['sortorder']);
  422. }
  423. }
  424. /**
  425. * This is the base class of the repository class.
  426. *
  427. * To create repository plugin, see: {@link http://docs.moodle.org/dev/Repository_plugins}
  428. * See an example: repository_dropbox
  429. *
  430. * @package core_repository
  431. * @copyright 2009 Dongsheng Cai {@link http://dongsheng.org}
  432. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  433. */
  434. abstract class repository implements cacheable_object {
  435. /**
  436. * Timeout in seconds for downloading the external file into moodle
  437. * @deprecated since Moodle 2.7, please use $CFG->repositorygetfiletimeout instead
  438. */
  439. const GETFILE_TIMEOUT = 30;
  440. /**
  441. * Timeout in seconds for syncronising the external file size
  442. * @deprecated since Moodle 2.7, please use $CFG->repositorysyncfiletimeout instead
  443. */
  444. const SYNCFILE_TIMEOUT = 1;
  445. /**
  446. * Timeout in seconds for downloading an image file from external repository during syncronisation
  447. * @deprecated since Moodle 2.7, please use $CFG->repositorysyncimagetimeout instead
  448. */
  449. const SYNCIMAGE_TIMEOUT = 3;
  450. // $disabled can be set to true to disable a plugin by force
  451. // example: self::$disabled = true
  452. /** @var bool force disable repository instance */
  453. public $disabled = false;
  454. /** @var int repository instance id */
  455. public $id;
  456. /** @var stdClass current context */
  457. public $context;
  458. /** @var array repository options */
  459. public $options;
  460. /** @var bool Whether or not the repository instance is editable */
  461. public $readonly;
  462. /** @var int return types */
  463. public $returntypes;
  464. /** @var stdClass repository instance database record */
  465. public $instance;
  466. /** @var string Type of repository (webdav, google_docs, dropbox, ...). Read from $this->get_typename(). */
  467. protected $typename;
  468. /**
  469. * Constructor
  470. *
  471. * @param int $repositoryid repository instance id
  472. * @param int|stdClass $context a context id or context object
  473. * @param array $options repository options
  474. * @param int $readonly indicate this repo is readonly or not
  475. */
  476. public function __construct($repositoryid, $context = SYSCONTEXTID, $options = array(), $readonly = 0) {
  477. global $DB;
  478. $this->id = $repositoryid;
  479. if (is_object($context)) {
  480. $this->context = $context;
  481. } else {
  482. $this->context = context::instance_by_id($context);
  483. }
  484. $cache = cache::make('core', 'repositories');
  485. if (($this->instance = $cache->get('i:'. $this->id)) === false) {
  486. $this->instance = $DB->get_record_sql("SELECT i.*, r.type AS repositorytype, r.sortorder, r.visible
  487. FROM {repository} r, {repository_instances} i
  488. WHERE i.typeid = r.id and i.id = ?", array('id' => $this->id));
  489. $cache->set('i:'. $this->id, $this->instance);
  490. }
  491. $this->readonly = $readonly;
  492. $this->options = array();
  493. if (is_array($options)) {
  494. // The get_option() method will get stored options in database.
  495. $options = array_merge($this->get_option(), $options);
  496. } else {
  497. $options = $this->get_option();
  498. }
  499. foreach ($options as $n => $v) {
  500. $this->options[$n] = $v;
  501. }
  502. $this->name = $this->get_name();
  503. $this->returntypes = $this->supported_returntypes();
  504. $this->super_called = true;
  505. }
  506. /**
  507. * Get repository instance using repository id
  508. *
  509. * Note that this function does not check permission to access repository contents
  510. *
  511. * @throws repository_exception
  512. *
  513. * @param int $repositoryid repository instance ID
  514. * @param context|int $context context instance or context ID where this repository will be used
  515. * @param array $options additional repository options
  516. * @return repository
  517. */
  518. public static function get_repository_by_id($repositoryid, $context, $options = array()) {
  519. global $CFG, $DB;
  520. $cache = cache::make('core', 'repositories');
  521. if (!is_object($context)) {
  522. $context = context::instance_by_id($context);
  523. }
  524. $cachekey = 'rep:'. $repositoryid. ':'. $context->id. ':'. serialize($options);
  525. if ($repository = $cache->get($cachekey)) {
  526. return $repository;
  527. }
  528. if (!$record = $cache->get('i:'. $repositoryid)) {
  529. $sql = "SELECT i.*, r.type AS repositorytype, r.visible, r.sortorder
  530. FROM {repository_instances} i
  531. JOIN {repository} r ON r.id = i.typeid
  532. WHERE i.id = ?";
  533. if (!$record = $DB->get_record_sql($sql, array($repositoryid))) {
  534. throw new repository_exception('invalidrepositoryid', 'repository');
  535. }
  536. $cache->set('i:'. $record->id, $record);
  537. }
  538. $type = $record->repositorytype;
  539. if (file_exists($CFG->dirroot . "/repository/$type/lib.php")) {
  540. require_once($CFG->dirroot . "/repository/$type/lib.php");
  541. $classname = 'repository_' . $type;
  542. $options['type'] = $type;
  543. $options['typeid'] = $record->typeid;
  544. $options['visible'] = $record->visible;
  545. if (empty($options['name'])) {
  546. $options['name'] = $record->name;
  547. }
  548. $repository = new $classname($repositoryid, $context, $options, $record->readonly);
  549. if (empty($repository->super_called)) {
  550. // to make sure the super construct is called
  551. debugging('parent::__construct must be called by '.$type.' plugin.');
  552. }
  553. $cache->set($cachekey, $repository);
  554. return $repository;
  555. } else {
  556. throw new repository_exception('invalidplugin', 'repository');
  557. }
  558. }
  559. /**
  560. * Returns the type name of the repository.
  561. *
  562. * @return string type name of the repository.
  563. * @since Moodle 2.5
  564. */
  565. public function get_typename() {
  566. if (empty($this->typename)) {
  567. $matches = array();
  568. if (!preg_match("/^repository_(.*)$/", get_class($this), $matches)) {
  569. throw new coding_exception('The class name of a repository should be repository_<typeofrepository>, '.
  570. 'e.g. repository_dropbox');
  571. }
  572. $this->typename = $matches[1];
  573. }
  574. return $this->typename;
  575. }
  576. /**
  577. * Get a repository type object by a given type name.
  578. *
  579. * @static
  580. * @param string $typename the repository type name
  581. * @return repository_type|bool
  582. */
  583. public static function get_type_by_typename($typename) {
  584. global $DB;
  585. $cache = cache::make('core', 'repositories');
  586. if (($repositorytype = $cache->get('typename:'. $typename)) === false) {
  587. $repositorytype = null;
  588. if ($record = $DB->get_record('repository', array('type' => $typename))) {
  589. $repositorytype = new repository_type($record->type, (array)get_config($record->type), $record->visible, $record->sortorder);
  590. $cache->set('typeid:'. $record->id, $repositorytype);
  591. }
  592. $cache->set('typename:'. $typename, $repositorytype);
  593. }
  594. return $repositorytype;
  595. }
  596. /**
  597. * Get the repository type by a given repository type id.
  598. *
  599. * @static
  600. * @param int $id the type id
  601. * @return object
  602. */
  603. public static function get_type_by_id($id) {
  604. global $DB;
  605. $cache = cache::make('core', 'repositories');
  606. if (($repositorytype = $cache->get('typeid:'. $id)) === false) {
  607. $repositorytype = null;
  608. if ($record = $DB->get_record('repository', array('id' => $id))) {
  609. $repositorytype = new repository_type($record->type, (array)get_config($record->type), $record->visible, $record->sortorder);
  610. $cache->set('typename:'. $record->type, $repositorytype);
  611. }
  612. $cache->set('typeid:'. $id, $repositorytype);
  613. }
  614. return $repositorytype;
  615. }
  616. /**
  617. * Return all repository types ordered by sortorder field
  618. * first repository type in returnedarray[0], second repository type in returnedarray[1], ...
  619. *
  620. * @static
  621. * @param bool $visible can return types by visiblity, return all types if null
  622. * @return array Repository types
  623. */
  624. public static function get_types($visible=null) {
  625. global $DB, $CFG;
  626. $cache = cache::make('core', 'repositories');
  627. if (!$visible) {
  628. $typesnames = $cache->get('types');
  629. } else {
  630. $typesnames = $cache->get('typesvis');
  631. }
  632. $types = array();
  633. if ($typesnames === false) {
  634. $typesnames = array();
  635. $vistypesnames = array();
  636. if ($records = $DB->get_records('repository', null ,'sortorder')) {
  637. foreach($records as $type) {
  638. if (($repositorytype = $cache->get('typename:'. $type->type)) === false) {
  639. // Create new instance of repository_type.
  640. if (file_exists($CFG->dirroot . '/repository/'. $type->type .'/lib.php')) {
  641. $repositorytype = new repository_type($type->type, (array)get_config($type->type), $type->visible, $type->sortorder);
  642. $cache->set('typeid:'. $type->id, $repositorytype);
  643. $cache->set('typename:'. $type->type, $repositorytype);
  644. }
  645. }
  646. if ($repositorytype) {
  647. if (empty($visible) || $repositorytype->get_visible()) {
  648. $types[] = $repositorytype;
  649. $vistypesnames[] = $repositorytype->get_typename();
  650. }
  651. $typesnames[] = $repositorytype->get_typename();
  652. }
  653. }
  654. }
  655. $cache->set('types', $typesnames);
  656. $cache->set('typesvis', $vistypesnames);
  657. } else {
  658. foreach ($typesnames as $typename) {
  659. $types[] = self::get_type_by_typename($typename);
  660. }
  661. }
  662. return $types;
  663. }
  664. /**
  665. * Checks if user has a capability to view the current repository.
  666. *
  667. * @return bool true when the user can, otherwise throws an exception.
  668. * @throws repository_exception when the user does not meet the requirements.
  669. */
  670. public final function check_capability() {
  671. global $USER;
  672. // The context we are on.
  673. $currentcontext = $this->context;
  674. // Ensure that the user can view the repository in the current context.
  675. $can = has_capability('repository/'.$this->get_typename().':view', $currentcontext);
  676. // Context in which the repository has been created.
  677. $repocontext = context::instance_by_id($this->instance->contextid);
  678. // Prevent access to private repositories when logged in as.
  679. if ($can && \core\session\manager::is_loggedinas()) {
  680. if ($this->contains_private_data() || $repocontext->contextlevel == CONTEXT_USER) {
  681. $can = false;
  682. }
  683. }
  684. // We are going to ensure that the current context was legit, and reliable to check
  685. // the capability against. (No need to do that if we already cannot).
  686. if ($can) {
  687. if ($repocontext->contextlevel == CONTEXT_USER) {
  688. // The repository is a user instance, ensure we're the right user to access it!
  689. if ($repocontext->instanceid != $USER->id) {
  690. $can = false;
  691. }
  692. } else if ($repocontext->contextlevel == CONTEXT_COURSE) {
  693. // The repository is a course one. Let's check that we are on the right course.
  694. if (in_array($currentcontext->contextlevel, array(CONTEXT_COURSE, CONTEXT_MODULE, CONTEXT_BLOCK))) {
  695. $coursecontext = $currentcontext->get_course_context();
  696. if ($coursecontext->instanceid != $repocontext->instanceid) {
  697. $can = false;
  698. }
  699. } else {
  700. // We are on a parent context, therefore it's legit to check the permissions
  701. // in the current context.
  702. }
  703. } else {
  704. // Nothing to check here, system instances can have different permissions on different
  705. // levels. We do not want to prevent URL hack here, because it does not make sense to
  706. // prevent a user to access a repository in a context if it's accessible in another one.
  707. }
  708. }
  709. if ($can) {
  710. return true;
  711. }
  712. throw new repository_exception('nopermissiontoaccess', 'repository');
  713. }
  714. /**
  715. * Check if file already exists in draft area.
  716. *
  717. * @static
  718. * @param int $itemid of the draft area.
  719. * @param string $filepath path to the file.
  720. * @param string $filename file name.
  721. * @return bool
  722. */
  723. public static function draftfile_exists($itemid, $filepath, $filename) {
  724. global $USER;
  725. $fs = get_file_storage();
  726. $usercontext = context_user::instance($USER->id);
  727. return $fs->file_exists($usercontext->id, 'user', 'draft', $itemid, $filepath, $filename);
  728. }
  729. /**
  730. * Parses the moodle file reference and returns an instance of stored_file
  731. *
  732. * @param string $reference reference to the moodle internal file as retruned by
  733. * {@link repository::get_file_reference()} or {@link file_storage::pack_reference()}
  734. * @return stored_file|null
  735. */
  736. public static function get_moodle_file($reference) {
  737. $params = file_storage::unpack_reference($reference, true);
  738. $fs = get_file_storage();
  739. return $fs->get_file($params['contextid'], $params['component'], $params['filearea'],
  740. $params['itemid'], $params['filepath'], $params['filename']);
  741. }
  742. /**
  743. * Repository method to make sure that user can access particular file.
  744. *
  745. * This is checked when user tries to pick the file from repository to deal with
  746. * potential parameter substitutions is request
  747. *
  748. * @param string $source source of the file, returned by repository as 'source' and received back from user (not cleaned)
  749. * @return bool whether the file is accessible by current user
  750. */
  751. public function file_is_accessible($source) {
  752. if ($this->has_moodle_files()) {
  753. $reference = $this->get_file_reference($source);
  754. try {
  755. $params = file_storage::unpack_reference($reference, true);
  756. } catch (file_reference_exception $e) {
  757. return false;
  758. }
  759. $browser = get_file_browser();
  760. $context = context::instance_by_id($params['contextid']);
  761. $file_info = $browser->get_file_info($context, $params['component'], $params['filearea'],
  762. $params['itemid'], $params['filepath'], $params['filename']);
  763. return !empty($file_info);
  764. }
  765. return true;
  766. }
  767. /**
  768. * This function is used to copy a moodle file to draft area.
  769. *
  770. * It DOES NOT check if the user is allowed to access this file because the actual file
  771. * can be located in the area where user does not have access to but there is an alias
  772. * to this file in the area where user CAN access it.
  773. * {@link file_is_accessible} should be called for alias location before calling this function.
  774. *
  775. * @param string $source The metainfo of file, it is base64 encoded php serialized data
  776. * @param stdClass|array $filerecord contains itemid, filepath, filename and optionally other
  777. * attributes of the new file
  778. * @param int $maxbytes maximum allowed size of file, -1 if unlimited. If size of file exceeds
  779. * the limit, the file_exception is thrown.
  780. * @param int $areamaxbytes the maximum size of the area. A file_exception is thrown if the
  781. * new file will reach the limit.
  782. * @return array The information about the created file
  783. */
  784. public function copy_to_area($source, $filerecord, $maxbytes = -1, $areamaxbytes = FILE_AREA_MAX_BYTES_UNLIMITED) {
  785. global $USER;
  786. $fs = get_file_storage();
  787. if ($this->has_moodle_files() == false) {
  788. throw new coding_exception('Only repository used to browse moodle files can use repository::copy_to_area()');
  789. }
  790. $user_context = context_user::instance($USER->id);
  791. $filerecord = (array)$filerecord;
  792. // make sure the new file will be created in user draft area
  793. $filerecord['component'] = 'user';
  794. $filerecord['filearea'] = 'draft';
  795. $filerecord['contextid'] = $user_context->id;
  796. $draftitemid = $filerecord['itemid'];
  797. $new_filepath = $filerecord['filepath'];
  798. $new_filename = $filerecord['filename'];
  799. // the file needs to copied to draft area
  800. $stored_file = self::get_moodle_file($source);
  801. if ($maxbytes != -1 && $stored_file->get_filesize() > $maxbytes) {
  802. $maxbytesdisplay = display_size($maxbytes, 0);
  803. throw new file_exception('maxbytesfile', (object) array('file' => $filerecord['filename'],
  804. 'size' => $maxbytesdisplay));
  805. }
  806. // Validate the size of the draft area.
  807. if (file_is_draft_area_limit_reached($draftitemid, $areamaxbytes, $stored_file->get_filesize())) {
  808. throw new file_exception('maxareabytes');
  809. }
  810. if (repository::draftfile_exists($draftitemid, $new_filepath, $new_filename)) {
  811. // create new file
  812. $unused_filename = repository::get_unused_filename($draftitemid, $new_filepath, $new_filename);
  813. $filerecord['filename'] = $unused_filename;
  814. $fs->create_file_from_storedfile($filerecord, $stored_file);
  815. $event = array();
  816. $event['event'] = 'fileexists';
  817. $event['newfile'] = new stdClass;
  818. $event['newfile']->filepath = $new_filepath;
  819. $event['newfile']->filename = $unused_filename;
  820. $event['newfile']->url = moodle_url::make_draftfile_url($draftitemid, $new_filepath, $unused_filename)->out();
  821. $event['existingfile'] = new stdClass;
  822. $event['existingfile']->filepath = $new_filepath;
  823. $event['existingfile']->filename = $new_filename;
  824. $event['existingfile']->url = moodle_url::make_draftfile_url($draftitemid, $new_filepath, $new_filename)->out();
  825. return $event;
  826. } else {
  827. $fs->create_file_from_storedfile($filerecord, $stored_file);
  828. $info = array();
  829. $info['itemid'] = $draftitemid;
  830. $info['file'] = $new_filename;
  831. $info['title'] = $new_filename;
  832. $info['contextid'] = $user_context->id;
  833. $info['url'] = moodle_url::make_draftfile_url($draftitemid, $new_filepath, $new_filename)->out();
  834. $info['filesize'] = $stored_file->get_filesize();
  835. return $info;
  836. }
  837. }
  838. /**
  839. * Get an unused filename from the current draft area.
  840. *
  841. * Will check if the file ends with ([0-9]) and increase the number.
  842. *
  843. * @static
  844. * @param int $itemid draft item ID.
  845. * @param string $filepath path to the file.
  846. * @param string $filename name of the file.
  847. * @return string an unused file name.
  848. */
  849. public static function get_unused_filename($itemid, $filepath, $filename) {
  850. global $USER;
  851. $contextid = context_user::instance($USER->id)->id;
  852. $fs = get_file_storage();
  853. return $fs->get_unused_filename($contextid, 'user', 'draft', $itemid, $filepath, $filename);
  854. }
  855. /**
  856. * Append a suffix to filename.
  857. *
  858. * @static
  859. * @param string $filename
  860. * @return string
  861. * @deprecated since 2.5
  862. */
  863. public static function append_suffix($filename) {
  864. debugging('The function repository::append_suffix() has been deprecated. Use repository::get_unused_filename() instead.',
  865. DEBUG_DEVELOPER);
  866. $pathinfo = pathinfo($filename);
  867. if (empty($pathinfo['extension'])) {
  868. return $filename . RENAME_SUFFIX;
  869. } else {
  870. return $pathinfo['filename'] . RENAME_SUFFIX . '.' . $pathinfo['extension'];
  871. }
  872. }
  873. /**
  874. * Return all types that you a user can create/edit and which are also visible
  875. * Note: Mostly used in order to know if at least one editable type can be set
  876. *
  877. * @static
  878. * @param stdClass $context the context for which we want the editable types
  879. * @return array types
  880. */
  881. public static function get_editable_types($context = null) {
  882. if (empty($context)) {
  883. $context = context_system::instance();
  884. }
  885. $types= repository::get_types(true);
  886. $editabletypes = array();
  887. foreach ($types as $type) {
  888. $instanceoptionnames = repository::static_function($type->get_typename(), 'get_instance_option_names');
  889. if (!empty($instanceoptionnames)) {
  890. if ($type->get_contextvisibility($context)) {
  891. $editabletypes[]=$type;
  892. }
  893. }
  894. }
  895. return $editabletypes;
  896. }
  897. /**
  898. * Return repository instances
  899. *
  900. * @static
  901. * @param array $args Array containing the following keys:
  902. * currentcontext : instance of context (default system context)
  903. * context : array of instances of context (default empty array)
  904. * onlyvisible : bool (default true)
  905. * type : string return instances of this type only
  906. * accepted_types : string|array return instances that contain files of those types (*, web_image, .pdf, ...)
  907. * return_types : int combination of FILE_INTERNAL & FILE_EXTERNAL & FILE_REFERENCE & FILE_CONTROLLED_LINK.
  908. * 0 means every type. The default is FILE_INTERNAL | FILE_EXTERNAL.
  909. * userid : int if specified, instances belonging to other users will not be returned
  910. *
  911. * @return array repository instances
  912. */
  913. public static function get_instances($args = array()) {
  914. global $DB, $CFG, $USER;
  915. // Fill $args attributes with default values unless specified
  916. if (isset($args['currentcontext'])) {
  917. if ($args['currentcontext'] instanceof context) {
  918. $current_context = $args['currentcontext'];
  919. } else {
  920. debugging('currentcontext passed to repository::get_instances was ' .
  921. 'not a context object. Using system context instead, but ' .
  922. 'you should probably fix your code.', DEBUG_DEVELOPER);
  923. $current_context = context_system::instance();
  924. }
  925. } else {
  926. $current_context = context_system::instance();
  927. }
  928. $args['currentcontext'] = $current_context->id;
  929. $contextids = array();
  930. if (!empty($args['context'])) {
  931. foreach ($args['context'] as $context) {
  932. $contextids[] = $context->id;
  933. }
  934. }
  935. $args['context'] = $contextids;
  936. if (!isset($args['onlyvisible'])) {
  937. $args['onlyvisible'] = true;
  938. }
  939. if (!isset($args['return_types'])) {
  940. $args['return_types'] = FILE_INTERNAL | FILE_EXTERNAL;
  941. }
  942. if (!isset($args['type'])) {
  943. $args['type'] = null;
  944. }
  945. if (empty($args['disable_types']) || !is_array($args['disable_types'])) {
  946. $args['disable_types'] = null;
  947. }
  948. if (empty($args['userid']) || !is_numeric($args['userid'])) {
  949. $args['userid'] = null;
  950. }
  951. if (!isset($args['accepted_types']) || (is_array($args['accepted_types']) && in_array('*', $args['accepted_types']))) {
  952. $args['accepted_types'] = '*';
  953. }
  954. ksort($args);
  955. $cachekey = 'all:'. serialize($args);
  956. // Check if we have cached list of repositories with the same query
  957. $cache = cache::make('core', 'repositories');
  958. if (($cachedrepositories = $cache->get($cachekey)) !== false) {
  959. // convert from cacheable_object_array to array
  960. $repositories = array();
  961. foreach ($cachedrepositories as $repository) {
  962. $repositories[$repository->id] = $repository;
  963. }
  964. return $repositories;
  965. }
  966. // Prepare DB SQL query to retrieve repositories
  967. $params = array();
  968. $sql = "SELECT i.*, r.type AS repositorytype, r.sortorder, r.visible
  969. FROM {repository} r, {repository_instances} i
  970. WHERE i.typeid = r.id ";
  971. if ($args['disable_types']) {
  972. list($types, $p) = $DB->get_in_or_equal($args['disable_types'], SQL_PARAMS_NAMED, 'distype', false);
  973. $sql .= " AND r.type $types";
  974. $params = array_merge($params, $p);
  975. }
  976. if ($args['userid']) {
  977. $sql .= " AND (i.userid = 0 or i.userid = :userid)";
  978. $params['userid'] = $args['userid'];
  979. }
  980. if ($args['context']) {
  981. list($ctxsql, $p2) = $DB->get_in_or_equal($args['context'], SQL_PARAMS_NAMED, 'ctx');
  982. $sql .= " AND i.contextid $ctxsql";
  983. $params = array_merge($params, $p2);
  984. }
  985. if ($args['onlyvisible'] == true) {
  986. $sql .= " AND r.visible = 1";
  987. }
  988. if ($args['type'] !== null) {
  989. $sql .= " AND r.type = :type";
  990. $params['type'] = $args['type'];
  991. }
  992. $sql .= " ORDER BY r.sortorder, i.name";
  993. if (!$records = $DB->get_records_sql($sql, $params)) {
  994. $records = array();
  995. }
  996. $repositories = array();
  997. // Sortorder should be unique, which is not true if we use $record->sortorder
  998. // and there are multiple instances of any repository type
  999. $sortorder = 1;
  1000. foreach ($records as $record) {
  1001. $cache->set('i:'. $record->id, $record);
  1002. if (!file_exists($CFG->dirroot . '/repository/'. $record->repositorytype.'/lib.php')) {
  1003. continue;
  1004. }
  1005. $repository = self::get_repository_by_id($record->id, $current_context);
  1006. $repository->options['sortorder'] = $sortorder++;
  1007. $is_supported = true;
  1008. // check mimetypes
  1009. if ($args['accepted_types'] !== '*' and $repository->supported_filetypes() !== '*') {
  1010. $accepted_ext = file_get_typegroup('extension', $args['accepted_types']);
  1011. $supported_ext = file_get_typegroup('extension', $repository->supported_filetypes());
  1012. $valid_ext = array_intersect($accepted_ext, $supported_ext);
  1013. $is_supported = !empty($valid_ext);
  1014. }
  1015. // Check return values.
  1016. if (!empty($args['return_types']) && !($repository->supported_returntypes() & $args['return_types'])) {
  1017. $is_supported = false;
  1018. }
  1019. if (!$args['onlyvisible'] || ($repository->is_visible() && !$repository->disabled)) {
  1020. // check capability in current context
  1021. $capability = has_capability('repository/'.$record->repositorytype.':view', $current_context);
  1022. if ($record->repositorytype == 'coursefiles') {
  1023. // coursefiles plugin needs managefiles permission
  1024. $capability = $capability && has_capability('moodle/course:managefiles', $current_context);
  1025. }
  1026. if ($is_supported && $capability) {
  1027. $repositories[$repository->id] = $repository;
  1028. }
  1029. }
  1030. }
  1031. $cache->set($cachekey, new cacheable_object_array($repositories));
  1032. return $repositories;
  1033. }
  1034. /**
  1035. * Get single repository instance for administrative actions
  1036. *
  1037. * Do not use this function to access repository contents, because it
  1038. * does not set the current context
  1039. *
  1040. * @see repository::get_repository_by_id()
  1041. *
  1042. * @static
  1043. * @param integer $id repository instance id
  1044. * @return repository
  1045. */
  1046. public static function get_instance($id) {
  1047. return self::get_repository_by_id($id, context_system::instance());
  1048. }
  1049. /**
  1050. * Call a static function. Any additional arguments than plugin and function will be passed through.
  1051. *
  1052. * @static
  1053. * @param string $plugin repository plugin name
  1054. * @param string $function function name
  1055. * @return mixed
  1056. */
  1057. public static function static_function($plugin, $function) {
  1058. global $CFG;
  1059. //check that the plugin exists
  1060. $typedirectory = $CFG->dirroot . '/repository/'. $plugin . '/lib.php';
  1061. if (!file_exists($typedirectory)) {
  1062. //throw new repository_exception('invalidplugin', 'repository');
  1063. return false;
  1064. }
  1065. $args = func_get_args();
  1066. if (count($args) <= 2) {
  1067. $args = array();
  1068. } else {
  1069. array_shift($args);
  1070. array_shift($args);
  1071. }
  1072. require_once($typedirectory);
  1073. return call_user_func_array(array('repository_' . $plugin, $function), $args);
  1074. }
  1075. /**
  1076. * Scan file, throws exception in case of infected file.
  1077. *
  1078. * Please note that the scanning engine must be able to access the file,
  1079. * permissions of the file are not modified here!
  1080. *
  1081. * @static
  1082. * @deprecated since Moodle 3.0
  1083. * @param string $thefile
  1084. * @param string $filename name of the file
  1085. * @param bool $deleteinfected
  1086. */
  1087. public static function antivir_scan_file($thefile, $filename, $deleteinfected) {
  1088. debugging('Please upgrade your code to use \core\antivirus\manager::scan_file instead', DEBUG_DEVELOPER);
  1089. \core\antivirus\manager::scan_file($thefile, $filename, $deleteinfected);
  1090. }
  1091. /**
  1092. * Repository method to serve the referenced file
  1093. *
  1094. * @see send_stored_file
  1095. *
  1096. * @param stored_file $storedfile the file that contains the reference
  1097. * @param int $lifetime Number of seconds before the file should expire from caches (null means $CFG->filelifetime)
  1098. * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only
  1099. * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin
  1100. * @param array $options additional options affecting the file serving
  1101. */
  1102. public function send_file($storedfile, $lifetime=null , $filter=0, $forcedownload=false, array $options = null) {
  1103. if ($this->has_moodle_files()) {
  1104. $fs = get_file_storage();
  1105. $params = file_storage::unpack_reference($storedfile->get_reference(), true);
  1106. $srcfile = null;
  1107. if (is_array($params)) {
  1108. $srcfile = $fs->get_file($params['contextid'], $params['component'], $params['filearea'],
  1109. $params['itemid'], $params['filepath'], $params['filename']);
  1110. }
  1111. if (empty($options)) {
  1112. $options = array();
  1113. }
  1114. if (!isset($options['filename'])) {
  1115. $options['filename'] = $storedfile->get_filename();
  1116. }
  1117. if (!$srcfile) {
  1118. send_file_not_found();
  1119. } else {
  1120. send_stored_file($srcfile, $lifetime, $filter, $forcedownload, $options);
  1121. }
  1122. } else {
  1123. throw new coding_exception("Repository plugin must implement send_file() method.");
  1124. }
  1125. }
  1126. /**
  1127. * Return human readable reference information
  1128. *
  1129. * @param string $reference value of DB field files_reference.reference
  1130. * @param int $filestatus status of the file, 0 - ok, 666 - source missing
  1131. * @return string
  1132. */
  1133. public function get_reference_details($reference, $filestatus = 0) {
  1134. if ($this->has_moodle_files()) {
  1135. $fileinfo = null;
  1136. $params = file_storage::unpack_reference($reference, true);
  1137. if (is_array($params)) {
  1138. $context = context::instance_by_id($params['contextid'], IGNORE_MISSING);
  1139. if ($context) {
  1140. $browser = get_file_browser();
  1141. $fileinfo = $browser->get_file_info($context, $params['component'], $params['filearea'], $params['itemid'], $params['filepath'], $params['filename']);
  1142. }
  1143. }
  1144. if (empty($fileinfo)) {
  1145. if ($filestatus == 666) {
  1146. if (is_siteadmin() || ($context && has_capability('moodle/course:managefiles', $context))) {
  1147. return get_string('lostsource', 'repository',
  1148. $params['contextid']. '/'. $params['component']. '/'. $params['filearea']. '/'. $params['itemid']. $params['filepath']. $params['filename']);
  1149. } else {
  1150. return get_string('lostsource', 'repository', '');
  1151. }
  1152. }
  1153. return get_string('undisclosedsource', 'repository');
  1154. } else {
  1155. return $fileinfo->get_readable_fullname();

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