PageRenderTime 102ms CodeModel.GetById 31ms RepoModel.GetById 7ms 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
Possible License(s): MPL-2.0-no-copyleft-exception, LGPL-3.0, GPL-3.0, LGPL-2.1, Apache-2.0, BSD-3-Clause, AGPL-3.0

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

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