PageRenderTime 76ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/filelib.php

https://bitbucket.org/ngmares/moodle
PHP | 4318 lines | 2788 code | 456 blank | 1074 comment | 770 complexity | e7d049b0d138a717c9b285bec7bdf502 MD5 | raw file
Possible License(s): LGPL-2.1, AGPL-3.0, MPL-2.0-no-copyleft-exception, GPL-3.0, Apache-2.0, BSD-3-Clause
  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. * Functions for file handling.
  18. *
  19. * @package core_files
  20. * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com)
  21. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  22. */
  23. defined('MOODLE_INTERNAL') || die();
  24. /**
  25. * BYTESERVING_BOUNDARY - string unique string constant.
  26. */
  27. define('BYTESERVING_BOUNDARY', 's1k2o3d4a5k6s7');
  28. require_once("$CFG->libdir/filestorage/file_exceptions.php");
  29. require_once("$CFG->libdir/filestorage/file_storage.php");
  30. require_once("$CFG->libdir/filestorage/zip_packer.php");
  31. require_once("$CFG->libdir/filebrowser/file_browser.php");
  32. /**
  33. * Encodes file serving url
  34. *
  35. * @deprecated use moodle_url factory methods instead
  36. *
  37. * @todo MDL-31071 deprecate this function
  38. * @global stdClass $CFG
  39. * @param string $urlbase
  40. * @param string $path /filearea/itemid/dir/dir/file.exe
  41. * @param bool $forcedownload
  42. * @param bool $https https url required
  43. * @return string encoded file url
  44. */
  45. function file_encode_url($urlbase, $path, $forcedownload=false, $https=false) {
  46. global $CFG;
  47. //TODO: deprecate this
  48. if ($CFG->slasharguments) {
  49. $parts = explode('/', $path);
  50. $parts = array_map('rawurlencode', $parts);
  51. $path = implode('/', $parts);
  52. $return = $urlbase.$path;
  53. if ($forcedownload) {
  54. $return .= '?forcedownload=1';
  55. }
  56. } else {
  57. $path = rawurlencode($path);
  58. $return = $urlbase.'?file='.$path;
  59. if ($forcedownload) {
  60. $return .= '&amp;forcedownload=1';
  61. }
  62. }
  63. if ($https) {
  64. $return = str_replace('http://', 'https://', $return);
  65. }
  66. return $return;
  67. }
  68. /**
  69. * Prepares 'editor' formslib element from data in database
  70. *
  71. * The passed $data record must contain field foobar, foobarformat and optionally foobartrust. This
  72. * function then copies the embedded files into draft area (assigning itemids automatically),
  73. * creates the form element foobar_editor and rewrites the URLs so the embedded images can be
  74. * displayed.
  75. * In your mform definition, you must have an 'editor' element called foobar_editor. Then you call
  76. * your mform's set_data() supplying the object returned by this function.
  77. *
  78. * @category files
  79. * @param stdClass $data database field that holds the html text with embedded media
  80. * @param string $field the name of the database field that holds the html text with embedded media
  81. * @param array $options editor options (like maxifiles, maxbytes etc.)
  82. * @param stdClass $context context of the editor
  83. * @param string $component
  84. * @param string $filearea file area name
  85. * @param int $itemid item id, required if item exists
  86. * @return stdClass modified data object
  87. */
  88. function file_prepare_standard_editor($data, $field, array $options, $context=null, $component=null, $filearea=null, $itemid=null) {
  89. $options = (array)$options;
  90. if (!isset($options['trusttext'])) {
  91. $options['trusttext'] = false;
  92. }
  93. if (!isset($options['forcehttps'])) {
  94. $options['forcehttps'] = false;
  95. }
  96. if (!isset($options['subdirs'])) {
  97. $options['subdirs'] = false;
  98. }
  99. if (!isset($options['maxfiles'])) {
  100. $options['maxfiles'] = 0; // no files by default
  101. }
  102. if (!isset($options['noclean'])) {
  103. $options['noclean'] = false;
  104. }
  105. //sanity check for passed context. This function doesn't expect $option['context'] to be set
  106. //But this function is called before creating editor hence, this is one of the best places to check
  107. //if context is used properly. This check notify developer that they missed passing context to editor.
  108. if (isset($context) && !isset($options['context'])) {
  109. //if $context is not null then make sure $option['context'] is also set.
  110. debugging('Context for editor is not set in editoroptions. Hence editor will not respect editor filters', DEBUG_DEVELOPER);
  111. } else if (isset($options['context']) && isset($context)) {
  112. //If both are passed then they should be equal.
  113. if ($options['context']->id != $context->id) {
  114. $exceptionmsg = 'Editor context ['.$options['context']->id.'] is not equal to passed context ['.$context->id.']';
  115. throw new coding_exception($exceptionmsg);
  116. }
  117. }
  118. if (is_null($itemid) or is_null($context)) {
  119. $contextid = null;
  120. $itemid = null;
  121. if (!isset($data)) {
  122. $data = new stdClass();
  123. }
  124. if (!isset($data->{$field})) {
  125. $data->{$field} = '';
  126. }
  127. if (!isset($data->{$field.'format'})) {
  128. $data->{$field.'format'} = editors_get_preferred_format();
  129. }
  130. if (!$options['noclean']) {
  131. $data->{$field} = clean_text($data->{$field}, $data->{$field.'format'});
  132. }
  133. } else {
  134. if ($options['trusttext']) {
  135. // noclean ignored if trusttext enabled
  136. if (!isset($data->{$field.'trust'})) {
  137. $data->{$field.'trust'} = 0;
  138. }
  139. $data = trusttext_pre_edit($data, $field, $context);
  140. } else {
  141. if (!$options['noclean']) {
  142. $data->{$field} = clean_text($data->{$field}, $data->{$field.'format'});
  143. }
  144. }
  145. $contextid = $context->id;
  146. }
  147. if ($options['maxfiles'] != 0) {
  148. $draftid_editor = file_get_submitted_draft_itemid($field);
  149. $currenttext = file_prepare_draft_area($draftid_editor, $contextid, $component, $filearea, $itemid, $options, $data->{$field});
  150. $data->{$field.'_editor'} = array('text'=>$currenttext, 'format'=>$data->{$field.'format'}, 'itemid'=>$draftid_editor);
  151. } else {
  152. $data->{$field.'_editor'} = array('text'=>$data->{$field}, 'format'=>$data->{$field.'format'}, 'itemid'=>0);
  153. }
  154. return $data;
  155. }
  156. /**
  157. * Prepares the content of the 'editor' form element with embedded media files to be saved in database
  158. *
  159. * This function moves files from draft area to the destination area and
  160. * encodes URLs to the draft files so they can be safely saved into DB. The
  161. * form has to contain the 'editor' element named foobar_editor, where 'foobar'
  162. * is the name of the database field to hold the wysiwyg editor content. The
  163. * editor data comes as an array with text, format and itemid properties. This
  164. * function automatically adds $data properties foobar, foobarformat and
  165. * foobartrust, where foobar has URL to embedded files encoded.
  166. *
  167. * @category files
  168. * @param stdClass $data raw data submitted by the form
  169. * @param string $field name of the database field containing the html with embedded media files
  170. * @param array $options editor options (trusttext, subdirs, maxfiles, maxbytes etc.)
  171. * @param stdClass $context context, required for existing data
  172. * @param string $component file component
  173. * @param string $filearea file area name
  174. * @param int $itemid item id, required if item exists
  175. * @return stdClass modified data object
  176. */
  177. function file_postupdate_standard_editor($data, $field, array $options, $context, $component=null, $filearea=null, $itemid=null) {
  178. $options = (array)$options;
  179. if (!isset($options['trusttext'])) {
  180. $options['trusttext'] = false;
  181. }
  182. if (!isset($options['forcehttps'])) {
  183. $options['forcehttps'] = false;
  184. }
  185. if (!isset($options['subdirs'])) {
  186. $options['subdirs'] = false;
  187. }
  188. if (!isset($options['maxfiles'])) {
  189. $options['maxfiles'] = 0; // no files by default
  190. }
  191. if (!isset($options['maxbytes'])) {
  192. $options['maxbytes'] = 0; // unlimited
  193. }
  194. if ($options['trusttext']) {
  195. $data->{$field.'trust'} = trusttext_trusted($context);
  196. } else {
  197. $data->{$field.'trust'} = 0;
  198. }
  199. $editor = $data->{$field.'_editor'};
  200. if ($options['maxfiles'] == 0 or is_null($filearea) or is_null($itemid) or empty($editor['itemid'])) {
  201. $data->{$field} = $editor['text'];
  202. } else {
  203. $data->{$field} = file_save_draft_area_files($editor['itemid'], $context->id, $component, $filearea, $itemid, $options, $editor['text'], $options['forcehttps']);
  204. }
  205. $data->{$field.'format'} = $editor['format'];
  206. return $data;
  207. }
  208. /**
  209. * Saves text and files modified by Editor formslib element
  210. *
  211. * @category files
  212. * @param stdClass $data $database entry field
  213. * @param string $field name of data field
  214. * @param array $options various options
  215. * @param stdClass $context context - must already exist
  216. * @param string $component
  217. * @param string $filearea file area name
  218. * @param int $itemid must already exist, usually means data is in db
  219. * @return stdClass modified data obejct
  220. */
  221. function file_prepare_standard_filemanager($data, $field, array $options, $context=null, $component=null, $filearea=null, $itemid=null) {
  222. $options = (array)$options;
  223. if (!isset($options['subdirs'])) {
  224. $options['subdirs'] = false;
  225. }
  226. if (is_null($itemid) or is_null($context)) {
  227. $itemid = null;
  228. $contextid = null;
  229. } else {
  230. $contextid = $context->id;
  231. }
  232. $draftid_editor = file_get_submitted_draft_itemid($field.'_filemanager');
  233. file_prepare_draft_area($draftid_editor, $contextid, $component, $filearea, $itemid, $options);
  234. $data->{$field.'_filemanager'} = $draftid_editor;
  235. return $data;
  236. }
  237. /**
  238. * Saves files modified by File manager formslib element
  239. *
  240. * @todo MDL-31073 review this function
  241. * @category files
  242. * @param stdClass $data $database entry field
  243. * @param string $field name of data field
  244. * @param array $options various options
  245. * @param stdClass $context context - must already exist
  246. * @param string $component
  247. * @param string $filearea file area name
  248. * @param int $itemid must already exist, usually means data is in db
  249. * @return stdClass modified data obejct
  250. */
  251. function file_postupdate_standard_filemanager($data, $field, array $options, $context, $component, $filearea, $itemid) {
  252. $options = (array)$options;
  253. if (!isset($options['subdirs'])) {
  254. $options['subdirs'] = false;
  255. }
  256. if (!isset($options['maxfiles'])) {
  257. $options['maxfiles'] = -1; // unlimited
  258. }
  259. if (!isset($options['maxbytes'])) {
  260. $options['maxbytes'] = 0; // unlimited
  261. }
  262. if (empty($data->{$field.'_filemanager'})) {
  263. $data->$field = '';
  264. } else {
  265. file_save_draft_area_files($data->{$field.'_filemanager'}, $context->id, $component, $filearea, $itemid, $options);
  266. $fs = get_file_storage();
  267. if ($fs->get_area_files($context->id, $component, $filearea, $itemid)) {
  268. $data->$field = '1'; // TODO: this is an ugly hack (skodak)
  269. } else {
  270. $data->$field = '';
  271. }
  272. }
  273. return $data;
  274. }
  275. /**
  276. * Generate a draft itemid
  277. *
  278. * @category files
  279. * @global moodle_database $DB
  280. * @global stdClass $USER
  281. * @return int a random but available draft itemid that can be used to create a new draft
  282. * file area.
  283. */
  284. function file_get_unused_draft_itemid() {
  285. global $DB, $USER;
  286. if (isguestuser() or !isloggedin()) {
  287. // guests and not-logged-in users can not be allowed to upload anything!!!!!!
  288. print_error('noguest');
  289. }
  290. $contextid = get_context_instance(CONTEXT_USER, $USER->id)->id;
  291. $fs = get_file_storage();
  292. $draftitemid = rand(1, 999999999);
  293. while ($files = $fs->get_area_files($contextid, 'user', 'draft', $draftitemid)) {
  294. $draftitemid = rand(1, 999999999);
  295. }
  296. return $draftitemid;
  297. }
  298. /**
  299. * Initialise a draft file area from a real one by copying the files. A draft
  300. * area will be created if one does not already exist. Normally you should
  301. * get $draftitemid by calling file_get_submitted_draft_itemid('elementname');
  302. *
  303. * @category files
  304. * @global stdClass $CFG
  305. * @global stdClass $USER
  306. * @param int $draftitemid the id of the draft area to use, or 0 to create a new one, in which case this parameter is updated.
  307. * @param int $contextid This parameter and the next two identify the file area to copy files from.
  308. * @param string $component
  309. * @param string $filearea helps indentify the file area.
  310. * @param int $itemid helps identify the file area. Can be null if there are no files yet.
  311. * @param array $options text and file options ('subdirs'=>false, 'forcehttps'=>false)
  312. * @param string $text some html content that needs to have embedded links rewritten to point to the draft area.
  313. * @return string|null returns string if $text was passed in, the rewritten $text is returned. Otherwise NULL.
  314. */
  315. function file_prepare_draft_area(&$draftitemid, $contextid, $component, $filearea, $itemid, array $options=null, $text=null) {
  316. global $CFG, $USER, $CFG;
  317. $options = (array)$options;
  318. if (!isset($options['subdirs'])) {
  319. $options['subdirs'] = false;
  320. }
  321. if (!isset($options['forcehttps'])) {
  322. $options['forcehttps'] = false;
  323. }
  324. $usercontext = get_context_instance(CONTEXT_USER, $USER->id);
  325. $fs = get_file_storage();
  326. if (empty($draftitemid)) {
  327. // create a new area and copy existing files into
  328. $draftitemid = file_get_unused_draft_itemid();
  329. $file_record = array('contextid'=>$usercontext->id, 'component'=>'user', 'filearea'=>'draft', 'itemid'=>$draftitemid);
  330. if (!is_null($itemid) and $files = $fs->get_area_files($contextid, $component, $filearea, $itemid)) {
  331. foreach ($files as $file) {
  332. if ($file->is_directory() and $file->get_filepath() === '/') {
  333. // we need a way to mark the age of each draft area,
  334. // by not copying the root dir we force it to be created automatically with current timestamp
  335. continue;
  336. }
  337. if (!$options['subdirs'] and ($file->is_directory() or $file->get_filepath() !== '/')) {
  338. continue;
  339. }
  340. $draftfile = $fs->create_file_from_storedfile($file_record, $file);
  341. // XXX: This is a hack for file manager (MDL-28666)
  342. // File manager needs to know the original file information before copying
  343. // to draft area, so we append these information in mdl_files.source field
  344. // {@link file_storage::search_references()}
  345. // {@link file_storage::search_references_count()}
  346. $sourcefield = $file->get_source();
  347. $newsourcefield = new stdClass;
  348. $newsourcefield->source = $sourcefield;
  349. $original = new stdClass;
  350. $original->contextid = $contextid;
  351. $original->component = $component;
  352. $original->filearea = $filearea;
  353. $original->itemid = $itemid;
  354. $original->filename = $file->get_filename();
  355. $original->filepath = $file->get_filepath();
  356. $newsourcefield->original = file_storage::pack_reference($original);
  357. $draftfile->set_source(serialize($newsourcefield));
  358. // End of file manager hack
  359. }
  360. }
  361. if (!is_null($text)) {
  362. // at this point there should not be any draftfile links yet,
  363. // because this is a new text from database that should still contain the @@pluginfile@@ links
  364. // this happens when developers forget to post process the text
  365. $text = str_replace("\"$CFG->httpswwwroot/draftfile.php", "\"$CFG->httpswwwroot/brokenfile.php#", $text);
  366. }
  367. } else {
  368. // nothing to do
  369. }
  370. if (is_null($text)) {
  371. return null;
  372. }
  373. // relink embedded files - editor can not handle @@PLUGINFILE@@ !
  374. return file_rewrite_pluginfile_urls($text, 'draftfile.php', $usercontext->id, 'user', 'draft', $draftitemid, $options);
  375. }
  376. /**
  377. * Convert encoded URLs in $text from the @@PLUGINFILE@@/... form to an actual URL.
  378. *
  379. * @category files
  380. * @global stdClass $CFG
  381. * @param string $text The content that may contain ULRs in need of rewriting.
  382. * @param string $file The script that should be used to serve these files. pluginfile.php, draftfile.php, etc.
  383. * @param int $contextid This parameter and the next two identify the file area to use.
  384. * @param string $component
  385. * @param string $filearea helps identify the file area.
  386. * @param int $itemid helps identify the file area.
  387. * @param array $options text and file options ('forcehttps'=>false)
  388. * @return string the processed text.
  389. */
  390. function file_rewrite_pluginfile_urls($text, $file, $contextid, $component, $filearea, $itemid, array $options=null) {
  391. global $CFG;
  392. $options = (array)$options;
  393. if (!isset($options['forcehttps'])) {
  394. $options['forcehttps'] = false;
  395. }
  396. if (!$CFG->slasharguments) {
  397. $file = $file . '?file=';
  398. }
  399. $baseurl = "$CFG->wwwroot/$file/$contextid/$component/$filearea/";
  400. if ($itemid !== null) {
  401. $baseurl .= "$itemid/";
  402. }
  403. if ($options['forcehttps']) {
  404. $baseurl = str_replace('http://', 'https://', $baseurl);
  405. }
  406. return str_replace('@@PLUGINFILE@@/', $baseurl, $text);
  407. }
  408. /**
  409. * Returns information about files in a draft area.
  410. *
  411. * @global stdClass $CFG
  412. * @global stdClass $USER
  413. * @param int $draftitemid the draft area item id.
  414. * @return array with the following entries:
  415. * 'filecount' => number of files in the draft area.
  416. * (more information will be added as needed).
  417. */
  418. function file_get_draft_area_info($draftitemid) {
  419. global $CFG, $USER;
  420. $usercontext = get_context_instance(CONTEXT_USER, $USER->id);
  421. $fs = get_file_storage();
  422. $results = array();
  423. // The number of files
  424. $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid, 'id', false);
  425. $results['filecount'] = count($draftfiles);
  426. $results['filesize'] = 0;
  427. foreach ($draftfiles as $file) {
  428. $results['filesize'] += $file->get_filesize();
  429. }
  430. return $results;
  431. }
  432. /**
  433. * Get used space of files
  434. * @global moodle_database $DB
  435. * @global stdClass $USER
  436. * @return int total bytes
  437. */
  438. function file_get_user_used_space() {
  439. global $DB, $USER;
  440. $usercontext = get_context_instance(CONTEXT_USER, $USER->id);
  441. $sql = "SELECT SUM(files1.filesize) AS totalbytes FROM {files} files1
  442. JOIN (SELECT contenthash, filename, MAX(id) AS id
  443. FROM {files}
  444. WHERE contextid = ? AND component = ? AND filearea != ?
  445. GROUP BY contenthash, filename) files2 ON files1.id = files2.id";
  446. $params = array('contextid'=>$usercontext->id, 'component'=>'user', 'filearea'=>'draft');
  447. $record = $DB->get_record_sql($sql, $params);
  448. return (int)$record->totalbytes;
  449. }
  450. /**
  451. * Convert any string to a valid filepath
  452. * @todo review this function
  453. * @param string $str
  454. * @return string path
  455. */
  456. function file_correct_filepath($str) { //TODO: what is this? (skodak)
  457. if ($str == '/' or empty($str)) {
  458. return '/';
  459. } else {
  460. return '/'.trim($str, './@#$ ').'/';
  461. }
  462. }
  463. /**
  464. * Generate a folder tree of draft area of current USER recursively
  465. *
  466. * @todo MDL-31073 use normal return value instead, this does not fit the rest of api here (skodak)
  467. * @param int $draftitemid
  468. * @param string $filepath
  469. * @param mixed $data
  470. */
  471. function file_get_drafarea_folders($draftitemid, $filepath, &$data) {
  472. global $USER, $OUTPUT, $CFG;
  473. $data->children = array();
  474. $context = get_context_instance(CONTEXT_USER, $USER->id);
  475. $fs = get_file_storage();
  476. if ($files = $fs->get_directory_files($context->id, 'user', 'draft', $draftitemid, $filepath, false)) {
  477. foreach ($files as $file) {
  478. if ($file->is_directory()) {
  479. $item = new stdClass();
  480. $item->sortorder = $file->get_sortorder();
  481. $item->filepath = $file->get_filepath();
  482. $foldername = explode('/', trim($item->filepath, '/'));
  483. $item->fullname = trim(array_pop($foldername), '/');
  484. $item->id = uniqid();
  485. file_get_drafarea_folders($draftitemid, $item->filepath, $item);
  486. $data->children[] = $item;
  487. } else {
  488. continue;
  489. }
  490. }
  491. }
  492. }
  493. /**
  494. * Listing all files (including folders) in current path (draft area)
  495. * used by file manager
  496. * @param int $draftitemid
  497. * @param string $filepath
  498. * @return stdClass
  499. */
  500. function file_get_drafarea_files($draftitemid, $filepath = '/') {
  501. global $USER, $OUTPUT, $CFG;
  502. $context = get_context_instance(CONTEXT_USER, $USER->id);
  503. $fs = get_file_storage();
  504. $data = new stdClass();
  505. $data->path = array();
  506. $data->path[] = array('name'=>get_string('files'), 'path'=>'/');
  507. // will be used to build breadcrumb
  508. $trail = '/';
  509. if ($filepath !== '/') {
  510. $filepath = file_correct_filepath($filepath);
  511. $parts = explode('/', $filepath);
  512. foreach ($parts as $part) {
  513. if ($part != '' && $part != null) {
  514. $trail .= ($part.'/');
  515. $data->path[] = array('name'=>$part, 'path'=>$trail);
  516. }
  517. }
  518. }
  519. $list = array();
  520. $maxlength = 12;
  521. if ($files = $fs->get_directory_files($context->id, 'user', 'draft', $draftitemid, $filepath, false)) {
  522. foreach ($files as $file) {
  523. $item = new stdClass();
  524. $item->filename = $file->get_filename();
  525. $item->filepath = $file->get_filepath();
  526. $item->fullname = trim($item->filename, '/');
  527. $filesize = $file->get_filesize();
  528. $item->size = $filesize ? $filesize : null;
  529. $item->filesize = $filesize ? display_size($filesize) : '';
  530. $item->sortorder = $file->get_sortorder();
  531. $item->author = $file->get_author();
  532. $item->license = $file->get_license();
  533. $item->datemodified = $file->get_timemodified();
  534. $item->datecreated = $file->get_timecreated();
  535. $item->isref = $file->is_external_file();
  536. if ($item->isref && $file->get_status() == 666) {
  537. $item->originalmissing = true;
  538. }
  539. // find the file this draft file was created from and count all references in local
  540. // system pointing to that file
  541. $source = @unserialize($file->get_source());
  542. if (isset($source->original)) {
  543. $item->refcount = $fs->search_references_count($source->original);
  544. }
  545. if ($file->is_directory()) {
  546. $item->filesize = 0;
  547. $item->icon = $OUTPUT->pix_url(file_folder_icon(24))->out(false);
  548. $item->type = 'folder';
  549. $foldername = explode('/', trim($item->filepath, '/'));
  550. $item->fullname = trim(array_pop($foldername), '/');
  551. $item->thumbnail = $OUTPUT->pix_url(file_folder_icon(90))->out(false);
  552. } else {
  553. // do NOT use file browser here!
  554. $item->mimetype = get_mimetype_description($file);
  555. if (file_extension_in_typegroup($file->get_filename(), 'archive')) {
  556. $item->type = 'zip';
  557. } else {
  558. $item->type = 'file';
  559. }
  560. $itemurl = moodle_url::make_draftfile_url($draftitemid, $item->filepath, $item->filename);
  561. $item->url = $itemurl->out();
  562. $item->icon = $OUTPUT->pix_url(file_file_icon($file, 24))->out(false);
  563. $item->thumbnail = $OUTPUT->pix_url(file_file_icon($file, 90))->out(false);
  564. if ($imageinfo = $file->get_imageinfo()) {
  565. $item->realthumbnail = $itemurl->out(false, array('preview' => 'thumb', 'oid' => $file->get_timemodified()));
  566. $item->realicon = $itemurl->out(false, array('preview' => 'tinyicon', 'oid' => $file->get_timemodified()));
  567. $item->image_width = $imageinfo['width'];
  568. $item->image_height = $imageinfo['height'];
  569. }
  570. }
  571. $list[] = $item;
  572. }
  573. }
  574. $data->itemid = $draftitemid;
  575. $data->list = $list;
  576. return $data;
  577. }
  578. /**
  579. * Returns draft area itemid for a given element.
  580. *
  581. * @category files
  582. * @param string $elname name of formlib editor element, or a hidden form field that stores the draft area item id, etc.
  583. * @return int the itemid, or 0 if there is not one yet.
  584. */
  585. function file_get_submitted_draft_itemid($elname) {
  586. // this is a nasty hack, ideally all new elements should use arrays here or there should be a new parameter
  587. if (!isset($_REQUEST[$elname])) {
  588. return 0;
  589. }
  590. if (is_array($_REQUEST[$elname])) {
  591. $param = optional_param_array($elname, 0, PARAM_INT);
  592. if (!empty($param['itemid'])) {
  593. $param = $param['itemid'];
  594. } else {
  595. debugging('Missing itemid, maybe caused by unset maxfiles option', DEBUG_DEVELOPER);
  596. return false;
  597. }
  598. } else {
  599. $param = optional_param($elname, 0, PARAM_INT);
  600. }
  601. if ($param) {
  602. require_sesskey();
  603. }
  604. return $param;
  605. }
  606. /**
  607. * Restore the original source field from draft files
  608. *
  609. * @param stored_file $storedfile This only works with draft files
  610. * @return stored_file
  611. */
  612. function file_restore_source_field_from_draft_file($storedfile) {
  613. $source = @unserialize($storedfile->get_source());
  614. if (!empty($source)) {
  615. if (is_object($source)) {
  616. $restoredsource = $source->source;
  617. $storedfile->set_source($restoredsource);
  618. } else {
  619. throw new moodle_exception('invalidsourcefield', 'error');
  620. }
  621. }
  622. return $storedfile;
  623. }
  624. /**
  625. * Saves files from a draft file area to a real one (merging the list of files).
  626. * Can rewrite URLs in some content at the same time if desired.
  627. *
  628. * @category files
  629. * @global stdClass $USER
  630. * @param int $draftitemid the id of the draft area to use. Normally obtained
  631. * from file_get_submitted_draft_itemid('elementname') or similar.
  632. * @param int $contextid This parameter and the next two identify the file area to save to.
  633. * @param string $component
  634. * @param string $filearea indentifies the file area.
  635. * @param int $itemid helps identifies the file area.
  636. * @param array $options area options (subdirs=>false, maxfiles=-1, maxbytes=0)
  637. * @param string $text some html content that needs to have embedded links rewritten
  638. * to the @@PLUGINFILE@@ form for saving in the database.
  639. * @param bool $forcehttps force https urls.
  640. * @return string|null if $text was passed in, the rewritten $text is returned. Otherwise NULL.
  641. */
  642. function file_save_draft_area_files($draftitemid, $contextid, $component, $filearea, $itemid, array $options=null, $text=null, $forcehttps=false) {
  643. global $USER;
  644. $usercontext = get_context_instance(CONTEXT_USER, $USER->id);
  645. $fs = get_file_storage();
  646. $options = (array)$options;
  647. if (!isset($options['subdirs'])) {
  648. $options['subdirs'] = false;
  649. }
  650. if (!isset($options['maxfiles'])) {
  651. $options['maxfiles'] = -1; // unlimited
  652. }
  653. if (!isset($options['maxbytes'])) {
  654. $options['maxbytes'] = 0; // unlimited
  655. }
  656. $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid, 'id');
  657. $oldfiles = $fs->get_area_files($contextid, $component, $filearea, $itemid, 'id');
  658. if (count($draftfiles) < 2) {
  659. // means there are no files - one file means root dir only ;-)
  660. $fs->delete_area_files($contextid, $component, $filearea, $itemid);
  661. } else if (count($oldfiles) < 2) {
  662. $filecount = 0;
  663. // there were no files before - one file means root dir only ;-)
  664. foreach ($draftfiles as $file) {
  665. $file_record = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea, 'itemid'=>$itemid);
  666. if (!$options['subdirs']) {
  667. if ($file->get_filepath() !== '/' or $file->is_directory()) {
  668. continue;
  669. }
  670. }
  671. if ($options['maxbytes'] and $options['maxbytes'] < $file->get_filesize()) {
  672. // oversized file - should not get here at all
  673. continue;
  674. }
  675. if ($options['maxfiles'] != -1 and $options['maxfiles'] <= $filecount) {
  676. // more files - should not get here at all
  677. break;
  678. }
  679. if (!$file->is_directory()) {
  680. $filecount++;
  681. }
  682. if ($file->is_external_file()) {
  683. $repoid = $file->get_repository_id();
  684. if (!empty($repoid)) {
  685. $file_record['repositoryid'] = $repoid;
  686. $file_record['reference'] = $file->get_reference();
  687. }
  688. }
  689. file_restore_source_field_from_draft_file($file);
  690. $fs->create_file_from_storedfile($file_record, $file);
  691. }
  692. } else {
  693. // we have to merge old and new files - we want to keep file ids for files that were not changed
  694. // we change time modified for all new and changed files, we keep time created as is
  695. $newhashes = array();
  696. foreach ($draftfiles as $file) {
  697. $newhash = $fs->get_pathname_hash($contextid, $component, $filearea, $itemid, $file->get_filepath(), $file->get_filename());
  698. file_restore_source_field_from_draft_file($file);
  699. $newhashes[$newhash] = $file;
  700. }
  701. $filecount = 0;
  702. foreach ($oldfiles as $oldfile) {
  703. $oldhash = $oldfile->get_pathnamehash();
  704. if (!isset($newhashes[$oldhash])) {
  705. // delete files not needed any more - deleted by user
  706. $oldfile->delete();
  707. continue;
  708. }
  709. $newfile = $newhashes[$oldhash];
  710. // status changed, we delete old file, and create a new one
  711. if ($oldfile->get_status() != $newfile->get_status()) {
  712. // file was changed, use updated with new timemodified data
  713. $oldfile->delete();
  714. // This file will be added later
  715. continue;
  716. }
  717. // Replaced file content
  718. if ($oldfile->get_contenthash() != $newfile->get_contenthash()) {
  719. $oldfile->replace_content_with($newfile);
  720. }
  721. // Updated author
  722. if ($oldfile->get_author() != $newfile->get_author()) {
  723. $oldfile->set_author($newfile->get_author());
  724. }
  725. // Updated license
  726. if ($oldfile->get_license() != $newfile->get_license()) {
  727. $oldfile->set_license($newfile->get_license());
  728. }
  729. // Updated file source
  730. if ($oldfile->get_source() != $newfile->get_source()) {
  731. $oldfile->set_source($newfile->get_source());
  732. }
  733. // Updated sort order
  734. if ($oldfile->get_sortorder() != $newfile->get_sortorder()) {
  735. $oldfile->set_sortorder($newfile->get_sortorder());
  736. }
  737. // Update file size
  738. if ($oldfile->get_filesize() != $newfile->get_filesize()) {
  739. $oldfile->set_filesize($newfile->get_filesize());
  740. }
  741. // Update file timemodified
  742. if ($oldfile->get_timemodified() != $newfile->get_timemodified()) {
  743. $oldfile->set_timemodified($newfile->get_timemodified());
  744. }
  745. // unchanged file or directory - we keep it as is
  746. unset($newhashes[$oldhash]);
  747. if (!$oldfile->is_directory()) {
  748. $filecount++;
  749. }
  750. }
  751. // Add fresh file or the file which has changed status
  752. // the size and subdirectory tests are extra safety only, the UI should prevent it
  753. foreach ($newhashes as $file) {
  754. $file_record = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea, 'itemid'=>$itemid, 'timemodified'=>time());
  755. if (!$options['subdirs']) {
  756. if ($file->get_filepath() !== '/' or $file->is_directory()) {
  757. continue;
  758. }
  759. }
  760. if ($options['maxbytes'] and $options['maxbytes'] < $file->get_filesize()) {
  761. // oversized file - should not get here at all
  762. continue;
  763. }
  764. if ($options['maxfiles'] != -1 and $options['maxfiles'] <= $filecount) {
  765. // more files - should not get here at all
  766. break;
  767. }
  768. if (!$file->is_directory()) {
  769. $filecount++;
  770. }
  771. if ($file->is_external_file()) {
  772. $repoid = $file->get_repository_id();
  773. if (!empty($repoid)) {
  774. $file_record['repositoryid'] = $repoid;
  775. $file_record['reference'] = $file->get_reference();
  776. }
  777. }
  778. $fs->create_file_from_storedfile($file_record, $file);
  779. }
  780. }
  781. // note: do not purge the draft area - we clean up areas later in cron,
  782. // the reason is that user might press submit twice and they would loose the files,
  783. // also sometimes we might want to use hacks that save files into two different areas
  784. if (is_null($text)) {
  785. return null;
  786. } else {
  787. return file_rewrite_urls_to_pluginfile($text, $draftitemid, $forcehttps);
  788. }
  789. }
  790. /**
  791. * Convert the draft file area URLs in some content to @@PLUGINFILE@@ tokens
  792. * ready to be saved in the database. Normally, this is done automatically by
  793. * {@link file_save_draft_area_files()}.
  794. *
  795. * @category files
  796. * @param string $text the content to process.
  797. * @param int $draftitemid the draft file area the content was using.
  798. * @param bool $forcehttps whether the content contains https URLs. Default false.
  799. * @return string the processed content.
  800. */
  801. function file_rewrite_urls_to_pluginfile($text, $draftitemid, $forcehttps = false) {
  802. global $CFG, $USER;
  803. $usercontext = get_context_instance(CONTEXT_USER, $USER->id);
  804. $wwwroot = $CFG->wwwroot;
  805. if ($forcehttps) {
  806. $wwwroot = str_replace('http://', 'https://', $wwwroot);
  807. }
  808. // relink embedded files if text submitted - no absolute links allowed in database!
  809. $text = str_ireplace("$wwwroot/draftfile.php/$usercontext->id/user/draft/$draftitemid/", '@@PLUGINFILE@@/', $text);
  810. if (strpos($text, 'draftfile.php?file=') !== false) {
  811. $matches = array();
  812. preg_match_all("!$wwwroot/draftfile.php\?file=%2F{$usercontext->id}%2Fuser%2Fdraft%2F{$draftitemid}%2F[^'\",&<>|`\s:\\\\]+!iu", $text, $matches);
  813. if ($matches) {
  814. foreach ($matches[0] as $match) {
  815. $replace = str_ireplace('%2F', '/', $match);
  816. $text = str_replace($match, $replace, $text);
  817. }
  818. }
  819. $text = str_ireplace("$wwwroot/draftfile.php?file=/$usercontext->id/user/draft/$draftitemid/", '@@PLUGINFILE@@/', $text);
  820. }
  821. return $text;
  822. }
  823. /**
  824. * Set file sort order
  825. *
  826. * @global moodle_database $DB
  827. * @param int $contextid the context id
  828. * @param string $component file component
  829. * @param string $filearea file area.
  830. * @param int $itemid itemid.
  831. * @param string $filepath file path.
  832. * @param string $filename file name.
  833. * @param int $sortorder the sort order of file.
  834. * @return bool
  835. */
  836. function file_set_sortorder($contextid, $component, $filearea, $itemid, $filepath, $filename, $sortorder) {
  837. global $DB;
  838. $conditions = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea, 'itemid'=>$itemid, 'filepath'=>$filepath, 'filename'=>$filename);
  839. if ($file_record = $DB->get_record('files', $conditions)) {
  840. $sortorder = (int)$sortorder;
  841. $file_record->sortorder = $sortorder;
  842. $DB->update_record('files', $file_record);
  843. return true;
  844. }
  845. return false;
  846. }
  847. /**
  848. * reset file sort order number to 0
  849. * @global moodle_database $DB
  850. * @param int $contextid the context id
  851. * @param string $component
  852. * @param string $filearea file area.
  853. * @param int|bool $itemid itemid.
  854. * @return bool
  855. */
  856. function file_reset_sortorder($contextid, $component, $filearea, $itemid=false) {
  857. global $DB;
  858. $conditions = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea);
  859. if ($itemid !== false) {
  860. $conditions['itemid'] = $itemid;
  861. }
  862. $file_records = $DB->get_records('files', $conditions);
  863. foreach ($file_records as $file_record) {
  864. $file_record->sortorder = 0;
  865. $DB->update_record('files', $file_record);
  866. }
  867. return true;
  868. }
  869. /**
  870. * Returns description of upload error
  871. *
  872. * @param int $errorcode found in $_FILES['filename.ext']['error']
  873. * @return string error description string, '' if ok
  874. */
  875. function file_get_upload_error($errorcode) {
  876. switch ($errorcode) {
  877. case 0: // UPLOAD_ERR_OK - no error
  878. $errmessage = '';
  879. break;
  880. case 1: // UPLOAD_ERR_INI_SIZE
  881. $errmessage = get_string('uploadserverlimit');
  882. break;
  883. case 2: // UPLOAD_ERR_FORM_SIZE
  884. $errmessage = get_string('uploadformlimit');
  885. break;
  886. case 3: // UPLOAD_ERR_PARTIAL
  887. $errmessage = get_string('uploadpartialfile');
  888. break;
  889. case 4: // UPLOAD_ERR_NO_FILE
  890. $errmessage = get_string('uploadnofilefound');
  891. break;
  892. // Note: there is no error with a value of 5
  893. case 6: // UPLOAD_ERR_NO_TMP_DIR
  894. $errmessage = get_string('uploadnotempdir');
  895. break;
  896. case 7: // UPLOAD_ERR_CANT_WRITE
  897. $errmessage = get_string('uploadcantwrite');
  898. break;
  899. case 8: // UPLOAD_ERR_EXTENSION
  900. $errmessage = get_string('uploadextension');
  901. break;
  902. default:
  903. $errmessage = get_string('uploadproblem');
  904. }
  905. return $errmessage;
  906. }
  907. /**
  908. * Recursive function formating an array in POST parameter
  909. * @param array $arraydata - the array that we are going to format and add into &$data array
  910. * @param string $currentdata - a row of the final postdata array at instant T
  911. * when finish, it's assign to $data under this format: name[keyname][][]...[]='value'
  912. * @param array $data - the final data array containing all POST parameters : 1 row = 1 parameter
  913. */
  914. function format_array_postdata_for_curlcall($arraydata, $currentdata, &$data) {
  915. foreach ($arraydata as $k=>$v) {
  916. $newcurrentdata = $currentdata;
  917. if (is_array($v)) { //the value is an array, call the function recursively
  918. $newcurrentdata = $newcurrentdata.'['.urlencode($k).']';
  919. format_array_postdata_for_curlcall($v, $newcurrentdata, $data);
  920. } else { //add the POST parameter to the $data array
  921. $data[] = $newcurrentdata.'['.urlencode($k).']='.urlencode($v);
  922. }
  923. }
  924. }
  925. /**
  926. * Transform a PHP array into POST parameter
  927. * (see the recursive function format_array_postdata_for_curlcall)
  928. * @param array $postdata
  929. * @return array containing all POST parameters (1 row = 1 POST parameter)
  930. */
  931. function format_postdata_for_curlcall($postdata) {
  932. $data = array();
  933. foreach ($postdata as $k=>$v) {
  934. if (is_array($v)) {
  935. $currentdata = urlencode($k);
  936. format_array_postdata_for_curlcall($v, $currentdata, $data);
  937. } else {
  938. $data[] = urlencode($k).'='.urlencode($v);
  939. }
  940. }
  941. $convertedpostdata = implode('&', $data);
  942. return $convertedpostdata;
  943. }
  944. /**
  945. * Fetches content of file from Internet (using proxy if defined). Uses cURL extension if present.
  946. * Due to security concerns only downloads from http(s) sources are supported.
  947. *
  948. * @todo MDL-31073 add version test for '7.10.5'
  949. * @category files
  950. * @param string $url file url starting with http(s)://
  951. * @param array $headers http headers, null if none. If set, should be an
  952. * associative array of header name => value pairs.
  953. * @param array $postdata array means use POST request with given parameters
  954. * @param bool $fullresponse return headers, responses, etc in a similar way snoopy does
  955. * (if false, just returns content)
  956. * @param int $timeout timeout for complete download process including all file transfer
  957. * (default 5 minutes)
  958. * @param int $connecttimeout timeout for connection to server; this is the timeout that
  959. * usually happens if the remote server is completely down (default 20 seconds);
  960. * may not work when using proxy
  961. * @param bool $skipcertverify If true, the peer's SSL certificate will not be checked.
  962. * Only use this when already in a trusted location.
  963. * @param string $tofile store the downloaded content to file instead of returning it.
  964. * @param bool $calctimeout false by default, true enables an extra head request to try and determine
  965. * filesize and appropriately larger timeout based on $CFG->curltimeoutkbitrate
  966. * @return mixed false if request failed or content of the file as string if ok. True if file downloaded into $tofile successfully.
  967. */
  968. function download_file_content($url, $headers=null, $postdata=null, $fullresponse=false, $timeout=300, $connecttimeout=20, $skipcertverify=false, $tofile=NULL, $calctimeout=false) {
  969. global $CFG;
  970. // some extra security
  971. $newlines = array("\r", "\n");
  972. if (is_array($headers) ) {
  973. foreach ($headers as $key => $value) {
  974. $headers[$key] = str_replace($newlines, '', $value);
  975. }
  976. }
  977. $url = str_replace($newlines, '', $url);
  978. if (!preg_match('|^https?://|i', $url)) {
  979. if ($fullresponse) {
  980. $response = new stdClass();
  981. $response->status = 0;
  982. $response->headers = array();
  983. $response->response_code = 'Invalid protocol specified in url';
  984. $response->results = '';
  985. $response->error = 'Invalid protocol specified in url';
  986. return $response;
  987. } else {
  988. return false;
  989. }
  990. }
  991. // check if proxy (if used) should be bypassed for this url
  992. $proxybypass = is_proxybypass($url);
  993. if (!$ch = curl_init($url)) {
  994. debugging('Can not init curl.');
  995. return false;
  996. }
  997. // set extra headers
  998. if (is_array($headers) ) {
  999. $headers2 = array();
  1000. foreach ($headers as $key => $value) {
  1001. $headers2[] = "$key: $value";
  1002. }
  1003. curl_setopt($ch, CURLOPT_HTTPHEADER, $headers2);
  1004. }
  1005. if ($skipcertverify) {
  1006. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
  1007. }
  1008. // use POST if requested
  1009. if (is_array($postdata)) {
  1010. $postdata = format_postdata_for_curlcall($postdata);
  1011. curl_setopt($ch, CURLOPT_POST, true);
  1012. curl_setopt($ch, CURLOPT_POSTFIELDS, $postdata);
  1013. }
  1014. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  1015. curl_setopt($ch, CURLOPT_HEADER, false);
  1016. curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $connecttimeout);
  1017. if (!ini_get('open_basedir') and !ini_get('safe_mode')) {
  1018. // TODO: add version test for '7.10.5'
  1019. curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
  1020. curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
  1021. }
  1022. if (!empty($CFG->proxyhost) and !$proxybypass) {
  1023. // SOCKS supported in PHP5 only
  1024. if (!empty($CFG->proxytype) and ($CFG->proxytype == 'SOCKS5')) {
  1025. if (defined('CURLPROXY_SOCKS5')) {
  1026. curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
  1027. } else {
  1028. curl_close($ch);
  1029. if ($fullresponse) {
  1030. $response = new stdClass();
  1031. $response->status = '0';
  1032. $response->headers = array();
  1033. $response->response_code = 'SOCKS5 proxy is not supported in PHP4';
  1034. $response->results = '';
  1035. $response->error = 'SOCKS5 proxy is not supported in PHP4';
  1036. return $response;
  1037. } else {
  1038. debugging("SOCKS5 proxy is not supported in PHP4.", DEBUG_ALL);
  1039. return false;
  1040. }
  1041. }
  1042. }
  1043. curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, false);
  1044. if (empty($CFG->proxyport)) {
  1045. curl_setopt($ch, CURLOPT_PROXY, $CFG->proxyhost);
  1046. } else {
  1047. curl_setopt($ch, CURLOPT_PROXY, $CFG->proxyhost.':'.$CFG->proxyport);
  1048. }
  1049. if (!empty($CFG->proxyuser) and !empty($CFG->proxypassword)) {
  1050. curl_setopt($ch, CURLOPT_PROXYUSERPWD, $CFG->proxyuser.':'.$CFG->proxypassword);
  1051. if (defined('CURLOPT_PROXYAUTH')) {
  1052. // any proxy authentication if PHP 5.1
  1053. curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_BASIC | CURLAUTH_NTLM);
  1054. }
  1055. }
  1056. }
  1057. // set up header and content handlers
  1058. $received = new stdClass();
  1059. $received->headers = array(); // received headers array
  1060. $received->tofile = $tofile;
  1061. $received->fh = null;
  1062. curl_setopt($ch, CURLOPT_HEADERFUNCTION, partial('download_file_content_header_handler', $received));
  1063. if ($tofile) {
  1064. curl_setopt($ch, CURLOPT_WRITEFUNCTION, partial('download_file_content_write_handler', $received));
  1065. }
  1066. if (!isset($CFG->curltimeoutkbitrate)) {
  1067. //use very slow rate of 56kbps as a timeout speed when not set
  1068. $bitrate = 56;
  1069. } else {
  1070. $bitrate = $CFG->curltimeoutkbitrate;
  1071. }
  1072. // try to calculate the proper amount for timeout from remote file size.
  1073. // if disabled or zero, we won't do any checks nor head requests.
  1074. if ($calctimeout && $bitrate > 0) {
  1075. //setup header request only options
  1076. curl_setopt_array ($ch, array(
  1077. CURLOPT_RETURNTRANSFER => false,
  1078. CURLOPT_NOBODY => true)
  1079. );
  1080. curl_exec($ch);
  1081. $info = curl_getinfo($ch);
  1082. $err = curl_error($ch);
  1083. if ($err === '' && $info['download_content_length'] > 0) { //no curl errors
  1084. $timeout = max($timeout, ceil($info['download_content_length'] * 8 / ($bitrate * 1024))); //adjust for large files only - take max timeout.
  1085. }
  1086. //reinstate affected curl options
  1087. curl_setopt_array ($ch, array(
  1088. CURLOPT_RETURNTRANSFER => true,
  1089. CURLOPT_NOBODY => false)
  1090. );
  1091. }
  1092. curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
  1093. $result = curl_exec($ch);
  1094. // try to detect encoding problems
  1095. if ((curl_errno($ch) == 23 or curl_errno($ch) == 61) and defined('CURLOPT_ENCODING')) {
  1096. curl_setopt($ch, CURLOPT_ENCODING, 'none');
  1097. $result = curl_exec($ch);
  1098. }
  1099. if ($received->fh) {
  1100. fclose($received->fh);
  1101. }
  1102. if (curl_errno($ch)) {
  1103. $error = curl_error($ch);
  1104. $error_no = curl_errno($ch);
  1105. curl_close($ch);
  1106. if ($fullresponse) {
  1107. $response = new stdClass();
  1108. if ($error_no == 28) {
  1109. $response->status = '-100'; // mimic snoopy
  1110. } else {
  1111. $response->status = '0';
  1112. }
  1113. $response->headers = array();
  1114. $response->response_code = $error;
  1115. $response->results = false;
  1116. $response->error = $error;
  1117. return $response;
  1118. } else {
  1119. debugging("cURL request for \"$url\" failed with: $error ($error_no)", DEBUG_ALL);
  1120. return false;
  1121. }
  1122. } else {
  1123. $info = curl_getinfo($ch);
  1124. curl_close($ch);
  1125. if (empty($info['http_code'])) {
  1126. // for security reasons we support only true http connections (Location: file:// exploit prevention)
  1127. $response = new stdClass();
  1128. $response->status = '0';
  1129. $response->headers = array();
  1130. $response->response_code = 'Unknown cURL error';
  1131. $response->results = false; // do NOT change this, we really want to ignore the result!
  1132. $response->error = 'Unknown cURL error';
  1133. } else {
  1134. $response = new stdClass();;
  1135. $response->status = (string)$info['http_code'];
  1136. $response->headers = $received->headers;
  1137. $response->response_code = $received->headers[0];
  1138. $response->results = $result;
  1139. $response->error = '';
  1140. }
  1141. if ($fullresponse) {
  1142. return $response;
  1143. } else if ($info['http_code'] != 200) {
  1144. debugging("cURL request for \"$url\" failed, HTTP response code: ".$response->response_code, DEBUG_ALL);
  1145. return false;
  1146. } else {
  1147. return $response->results;
  1148. }
  1149. }
  1150. }
  1151. /**
  1152. * internal implementation
  1153. * @param stdClass $received
  1154. * @param resource $ch
  1155. * @param mixed $header
  1156. * @return int header length
  1157. */
  1158. function download_file_content_header_handler($received, $ch, $header) {
  1159. $received->headers[] = $header;
  1160. return strlen($header);
  1161. }
  1162. /**
  1163. * internal implementation
  1164. * @param stdClass $received
  1165. * @param resource $ch
  1166. * @param mixed $data
  1167. */
  1168. function download_file_content_write_handler($received, $ch, $data) {
  1169. if (!$received->fh) {
  1170. $received->fh = fopen($received->tofile, 'w');
  1171. if ($received->fh === false) {
  1172. // bad luck, file creation or overriding failed
  1173. return 0;
  1174. }
  1175. }
  1176. if (fwrite($received->fh, $data) === false) {
  1177. // bad luck, write failed, let's abort completely
  1178. return 0;
  1179. }
  1180. return strlen($data);
  1181. }
  1182. /**
  1183. * Returns a list of information about file types based on extensions.
  1184. *
  1185. * The following elements expected in value array for each extension:
  1186. * 'type' - mimetype
  1187. * 'icon' - location of the icon file. If value is FILENAME, then either pix/f/FILENAME.gif
  1188. * or pix/f/FILENAME.png must be present in moodle and contain 16x16 filetype icon;
  1189. * also files with bigger sizes under names
  1190. * FILENAME-24, FILENAME-32, FILENAME-64, FILENAME-128, FILENAME-256 are recommended.
  1191. * 'groups' (optional) - array of filetype groups this filetype extension is part of;
  1192. * commonly used in moodle the following groups:
  1193. * - web_image - image that can be included as <img> in HTML
  1194. * - image - image that we can parse using GD to find it's dimensions, also used for portfolio format
  1195. * - video - file that can be imported as video in text editor
  1196. * - audio - file that can be imported as audio in text editor
  1197. * - archive - we can extract files from this archive
  1198. * - spreadsheet - used for portfolio format
  1199. * - document - used for portfolio format
  1200. * - presentation - used for portfolio format
  1201. * 'string' (optional) - the name of the string from lang/en/mimetypes.php that displays
  1202. * human-readable description for this filetype;
  1203. * Function {@link get_mimetype_description()} first looks at the presence of string for
  1204. * particular mimetype (value of 'type'), if not found looks for string specified in 'string'
  1205. * attribute, if not found returns the value of 'type';
  1206. * 'defaulticon' (boolean, optional) - used by function {@link file_mimetype_icon()} to find
  1207. * an icon for mimetype. If an entry with 'defaulticon' is not found for a particular mimetype,
  1208. * this function will return first found icon; Especially usefull for types such as 'text/plain'
  1209. *
  1210. * @category files
  1211. * @return array List of information about file types based on extensions.
  1212. * Associative array of extension (lower-case) to associative array
  1213. * from 'element name' to data. Current element names are 'type' and 'icon'.
  1214. * Unknown types should use the 'xxx' entry which includes defaults.
  1215. */
  1216. function &get_mimetypes_array() {
  1217. static $mimearray = array (
  1218. 'xxx' => array ('type'=>'document/unknown', 'icon'=>'unknown'),
  1219. '3gp' => array ('type'=>'video/quicktime', 'icon'=>'quicktime', 'groups'=>array('video'), 'string'=>'video'),
  1220. 'aac' => array ('type'=>'audio/aac', 'icon'=>'audio', 'groups'=>array('audio'), 'string'=>'audio'),
  1221. 'ai' => array ('type'=>'application/postscript', 'icon'=>'eps', 'groups'=>array('image'), 'string'=>'image'),
  1222. 'aif' => array ('type'=>'audio/x-aiff', 'icon'=>'audio', 'groups'=>array('audio'), 'string'=>'audio'),
  1223. 'aiff' => array ('type'=>'audio/x-aiff', 'icon'=>'audio', 'groups'=>array('audio'), 'string'=>'audio'),
  1224. 'aifc' => array ('type'=>'audio/x-aiff', 'icon'=>'audio', 'groups'=>array('audio'), 'string'=>'audio'),
  1225. 'applescript' => array ('type'=>'text/plain', 'icon'=>'text'),
  1226. 'asc' => array ('type'=>'text/plain', 'icon'=>'sourcecode'),
  1227. 'asm' => array ('type'=>'text/plain', 'icon'=>'sourcecode'),
  1228. 'au' => array ('type'=>'audio/au', 'icon'=>'audio', 'groups'=>array('audio'), 'string'=>'audio'),
  1229. 'avi' => array ('type'=>'video/x-ms-wm', 'icon'=>'avi', 'groups'=>array('video','web_video'), 'string'=>'video'),
  1230. 'bmp' => array ('type'=>'image/bmp', 'icon'=>'bmp', 'groups'=>array('image'), 'string'=>'image'),
  1231. 'c' => array ('type'=>'text/plain', 'icon'=>'sourcecode'),
  1232. 'cct' => array ('type'=>'shockwave/director', 'icon'=>'flash'),
  1233. 'cpp' => array ('type'=>'text/plain', 'icon'=>'sourcecode'),
  1234. 'cs' => array ('type'=>'application/x-csh', 'icon'=>'sourcecode'),
  1235. 'css' => array ('type'=>'text/css', 'icon'=>'text', 'groups'=>array('web_file')),
  1236. 'csv' => array ('type'=>'text/csv', 'icon'=>'spreadsheet', 'groups'=>array('spreadsheet')),
  1237. 'dv' => array ('type'=>'video/x-dv', 'icon'=>'quicktime', 'groups'=>array('video'), 'string'=>'video'),
  1238. 'dmg' => array ('type'=>'application/octet-stream', 'icon'=>'unknown'),
  1239. 'doc' => array ('type'=>'application/msword', 'icon'=>'document', 'groups'=>array('document')),
  1240. 'docx' => array ('type'=>'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'icon'=>'document', 'groups'=>array('document')),
  1241. 'docm' => array ('type'=>'application/vnd.ms-word.document.macroEnabled.12', 'icon'=>'document'),
  1242. 'dotx' => array ('type'=>'application/vnd.openxmlformats-officedocument.wordprocessingml.template', 'icon'=>'document'),
  1243. 'dotm' => array ('type'=>'application/vnd.ms-word.template.macroEnabled.12', 'icon'=>'document'),
  1244. 'dcr' => array ('type'=>'application/x-director', 'icon'=>'flash'),
  1245. 'dif' => array ('type'=>'video/x-dv', 'icon'=>'quicktime', 'groups'=>array('video'), 'string'=>'video'),
  1246. 'dir' => array ('type'=>'application/x-director', 'icon'=>'flash'),
  1247. 'dxr' => array ('type'=>'application/x-director', 'icon'=>'flash'),
  1248. 'eps' => array ('type'=>'application/postscript', 'icon'=>'eps'),
  1249. 'fdf' => array ('type'=>'application/pdf', 'icon'=>'pdf'),
  1250. 'flv' => array ('type'=>'video/x-flv', 'icon'=>'flash', 'groups'=>array('video','web_video'), 'string'=>'video'),
  1251. 'f4v' => array ('type'=>'video/mp4', 'icon'=>'flash', 'groups'=>array('video','web_video'), 'string'=>'video'),
  1252. 'gif' => array ('type'=>'image/gif', 'icon'=>'gif', 'groups'=>array('image', 'web_image'), 'string'=>'image'),
  1253. 'gtar' => array ('type'=>'application/x-gtar', 'icon'=>'archive', 'groups'=>array('archive'), 'string'=>'archive'),
  1254. 'tgz' => array ('type'=>'application/g-zip', 'icon'=>'archive', 'groups'=>array('archive'), 'string'=>'archive'),
  1255. 'gz' => array ('type'=>'application/g-zip', 'icon'=>'archive', 'groups'=>array('archive'), 'string'=>'archive'),
  1256. 'gzip' => array ('type'=>'application/g-zip', 'icon'=>'archive', 'groups'=>array('archive'), 'string'=>'archive'),
  1257. 'h' => array ('type'=>'text/plain', 'icon'=>'sourcecode'),
  1258. 'hpp' => array ('type'=>'text/plain', 'icon'=>'sourcecode'),
  1259. 'hqx' => array ('type'=>'application/mac-binhex40', 'icon'=>'archive', 'groups'=>array('archive'), 'string'=>'archive'),
  1260. 'htc' => array ('type'=>'text/x-component', 'icon'=>'markup'),
  1261. 'html' => array ('type'=>'text/html', 'icon'=>'html', 'groups'=>array('web_file')),
  1262. 'xhtml'=> array ('type'=>'application/xhtml+xml', 'icon'=>'markup', 'groups'=>array('web_file')),
  1263. 'htm' => array ('type'=>'text/html', 'icon'=>'html', 'groups'=>array('web_file')),
  1264. 'ico' => array ('type'=>'image/vnd.microsoft.icon', 'icon'=>'image', 'groups'=>array('image'), 'string'=>'image'),
  1265. 'ics' => array ('type'=>'text/calendar', 'icon'=>'text'),
  1266. 'isf' => array ('type'=>'application/inspiration', 'icon'=>'isf'),
  1267. 'ist' => array ('type'=>'application/inspiration.template', 'icon'=>'isf'),
  1268. 'java' => array ('type'=>'text/plain', 'icon'=>'sourcecode'),
  1269. 'jcb' => array ('type'=>'text/xml', 'icon'=>'markup'),
  1270. 'jcl' => array ('type'=>'text/xml', 'icon'=>'markup'),
  1271. 'jcw' => array ('type'=>'text/xml', 'icon'=>'markup'),
  1272. 'jmt' => array ('type'=>'text/xml', 'icon'=>'markup'),
  1273. 'jmx' => array ('type'=>'text/xml', 'icon'=>'markup'),
  1274. 'jpe' => array ('type'=>'image/jpeg', 'icon'=>'jpeg', 'groups'=>array('image', 'web_image'), 'string'=>'image'),
  1275. 'jpeg' => array ('type'=>'image/jpeg', 'icon'=>'jpeg', 'groups'=>array('image', 'web_image'), 'string'=>'image'),
  1276. 'jpg' => array ('type'=>'image/jpeg', 'icon'=>'jpeg', 'groups'=>array('image', 'web_image'), 'string'=>'image'),
  1277. 'jqz' => array ('type'=>'text/xml', 'icon'=>'markup'),
  1278. 'js' => array ('type'=>'application/x-javascript', 'icon'=>'text', 'groups'=>array('web_file')),
  1279. 'latex'=> array ('type'=>'application/x-latex', 'icon'=>'text'),
  1280. 'm' => array ('type'=>'text/plain', 'icon'=>'sourcecode'),
  1281. 'mbz' => array ('type'=>'application/vnd.moodle.backup', 'icon'=>'moodle'),
  1282. 'mov' => array ('type'=>'video/quicktime', 'icon'=>'quicktime', 'groups'=>array('video','web_video'), 'string'=>'video'),
  1283. 'movie'=> array ('type'=>'video/x-sgi-movie', 'icon'=>'quicktime', 'groups'=>array('video'), 'string'=>'video'),
  1284. 'm3u' => array ('type'=>'audio/x-mpegurl', 'icon'=>'mp3', 'groups'=>array('audio'), 'string'=>'audio'),
  1285. 'mp3' => array ('type'=>'audio/mp3', 'icon'=>'mp3', 'groups'=>array('audio','web_audio'), 'string'=>'audio'),
  1286. 'mp4' => array ('type'=>'video/mp4', 'icon'=>'mpeg', 'groups'=>array('video','web_video'), 'string'=>'video'),
  1287. 'm4v' => array ('type'=>'video/mp4', 'icon'=>'mpeg', 'groups'=>array('video','web_video'), 'string'=>'video'),
  1288. 'm4a' => array ('type'=>'audio/mp4', 'icon'=>'mp3', 'groups'=>array('audio'), 'string'=>'audio'),
  1289. 'mpeg' => array ('type'=>'video/mpeg', 'icon'=>'mpeg', 'groups'=>array('video','web_video'), 'string'=>'video'),
  1290. 'mpe' => array ('type'=>'video/mpeg', 'icon'=>'mpeg', 'groups'=>array('video','web_video'), 'string'=>'video'),
  1291. 'mpg' => array ('type'=>'video/mpeg', 'icon'=>'mpeg', 'groups'=>array('video','web_video'), 'string'=>'video'),
  1292. 'odt' => array ('type'=>'application/vnd.oasis.opendocument.text', 'icon'=>'writer', 'groups'=>array('document')),
  1293. 'ott' => array ('type'=>'application/vnd.oasis.opendocument.text-template', 'icon'=>'writer', 'groups'=>array('document')),
  1294. 'oth' => array ('type'=>'application/vnd.oasis.opendocument.text-web', 'icon'=>'oth', 'groups'=>array('document')),
  1295. 'odm' => array ('type'=>'application/vnd.oasis.opendocument.text-master', 'icon'=>'writer'),
  1296. 'odg' => array ('type'=>'application/vnd.oasis.opendocument.graphics', 'icon'=>'draw'),
  1297. 'otg' => array ('type'=>'application/vnd.oasis.opendocument.graphics-template', 'icon'=>'draw'),
  1298. 'odp' => array ('type'=>'application/vnd.oasis.opendocument.presentation', 'icon'=>'impress'),
  1299. 'otp' => array ('type'=>'application/vnd.oasis.opendocument.presentation-template', 'icon'=>'impress'),
  1300. 'ods' => array ('type'=>'application/vnd.oasis.opendocument.spreadsheet', 'icon'=>'calc', 'groups'=>array('spreadsheet')),
  1301. 'ots' => array ('type'=>'application/vnd.oasis.opendocument.spreadsheet-template', 'icon'=>'calc', 'groups'=>array('spreadsheet')),
  1302. 'odc' => array ('type'=>'application/vnd.oasis.opendocument.chart', 'icon'=>'chart'),
  1303. 'odf' => array ('type'=>'application/vnd.oasis.opendocument.formula', 'icon'=>'math'),
  1304. 'odb' => array ('type'=>'application/vnd.oasis.opendocument.database', 'icon'=>'base'),
  1305. 'odi' => array ('type'=>'application/vnd.oasis.opendocument.image', 'icon'=>'draw'),
  1306. 'oga' => array ('type'=>'audio/ogg', 'icon'=>'audio', 'groups'=>array('audio'), 'string'=>'audio'),
  1307. 'ogg' => array ('type'=>'audio/ogg', 'icon'=>'audio', 'groups'=>array('audio'), 'string'=>'audio'),
  1308. 'ogv' => array ('type'=>'video/ogg', 'icon'=>'video', 'groups'=>array('video'), 'string'=>'video'),
  1309. 'pct' => array ('type'=>'image/pict', 'icon'=>'image', 'groups'=>array('image'), 'string'=>'image'),
  1310. 'pdf' => array ('type'=>'application/pdf', 'icon'=>'pdf'),
  1311. 'php' => array ('type'=>'text/plain', 'icon'=>'sourcecode'),
  1312. 'pic' => array ('type'=>'image/pict', 'icon'=>'image', 'groups'=>array('image'), 'string'=>'image'),
  1313. 'pict' => array ('type'=>'image/pict', 'icon'=>'image', 'groups'=>array('image'), 'string'=>'image'),
  1314. 'png' => array ('type'=>'image/png', 'icon'=>'png', 'groups'=>array('image', 'web_image'), 'string'=>'image'),
  1315. 'pps' => array ('type'=>'application/vnd.ms-powerpoint', 'icon'=>'powerpoint', 'groups'=>array('presentation')),
  1316. 'ppt' => array ('type'=>'application/vnd.ms-powerpoint', 'icon'=>'powerpoint', 'groups'=>array('presentation')),
  1317. 'pptx' => array ('type'=>'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'icon'=>'powerpoint'),
  1318. 'pptm' => array ('type'=>'application/vnd.ms-powerpoint.presentation.macroEnabled.12', 'icon'=>'powerpoint'),
  1319. 'potx' => array ('type'=>'application/vnd.openxmlformats-officedocument.presentationml.template', 'icon'=>'powerpoint'),
  1320. 'potm' => array ('type'=>'application/vnd.ms-powerpoint.template.macroEnabled.12', 'icon'=>'powerpoint'),
  1321. 'ppam' => array ('type'=>'application/vnd.ms-powerpoint.addin.macroEnabled.12', 'icon'=>'powerpoint'),
  1322. 'ppsx' => array ('type'=>'application/vnd.openxmlformats-officedocument.presentationml.slideshow', 'icon'=>'powerpoint'),
  1323. 'ppsm' => array ('type'=>'application/vnd.ms-powerpoint.slideshow.macroEnabled.12', 'icon'=>'powerpoint'),
  1324. 'ps' => array ('type'=>'application/postscript', 'icon'=>'pdf'),
  1325. 'qt' => array ('type'=>'video/quicktime', 'icon'=>'quicktime', 'groups'=>array('video','web_video'), 'string'=>'video'),
  1326. 'ra' => array ('type'=>'audio/x-realaudio-plugin', 'icon'=>'audio', 'groups'=>array('audio','web_audio'), 'string'=>'audio'),
  1327. 'ram' => array ('type'=>'audio/x-pn-realaudio-plugin', 'icon'=>'audio', 'groups'=>array('audio'), 'string'=>'audio'),
  1328. 'rhb' => array ('type'=>'text/xml', 'icon'=>'markup'),
  1329. 'rm' => array ('type'=>'audio/x-pn-realaudio-plugin', 'icon'=>'audio', 'groups'=>array('audio'), 'string'=>'audio'),
  1330. 'rmvb' => array ('type'=>'application/vnd.rn-realmedia-vbr', 'icon'=>'video', 'groups'=>array('video'), 'string'=>'video'),
  1331. 'rtf' => array ('type'=>'text/rtf', 'icon'=>'text', 'groups'=>array('document')),
  1332. 'rtx' => array ('type'=>'text/richtext', 'icon'=>'text'),
  1333. 'rv' => array ('type'=>'audio/x-pn-realaudio-plugin', 'icon'=>'audio', 'groups'=>array('video'), 'string'=>'video'),
  1334. 'sh' => array ('type'=>'application/x-sh', 'icon'=>'sourcecode'),
  1335. 'sit' => array ('type'=>'application/x-stuffit', 'icon'=>'archive', 'groups'=>array('archive'), 'string'=>'archive'),
  1336. 'smi' => array ('type'=>'application/smil', 'icon'=>'text'),
  1337. 'smil' => array ('type'=>'application/smil', 'icon'=>'text'),
  1338. 'sqt' => array ('type'=>'text/xml', 'icon'=>'markup'),
  1339. 'svg' => array ('type'=>'image/svg+xml', 'icon'=>'image', 'groups'=>array('image','web_image'), 'string'=>'image'),
  1340. 'svgz' => array ('type'=>'image/svg+xml', 'icon'=>'image', 'groups'=>array('image','web_image'), 'string'=>'image'),
  1341. 'swa' => array ('type'=>'application/x-director', 'icon'=>'flash'),
  1342. 'swf' => array ('type'=>'application/x-shockwave-flash', 'icon'=>'flash', 'groups'=>array('video','web_video')),
  1343. 'swfl' => array ('type'=>'application/x-shockwave-flash', 'icon'=>'flash', 'groups'=>array('video','web_video')),
  1344. 'sxw' => array ('type'=>'application/vnd.sun.xml.writer', 'icon'=>'writer'),
  1345. 'stw' => array ('type'=>'application/vnd.sun.xml.writer.template', 'icon'=>'writer'),
  1346. 'sxc' => array ('type'=>'application/vnd.sun.xml.calc', 'icon'=>'calc'),
  1347. 'stc' => array ('type'=>'application/vnd.sun.xml.calc.template', 'icon'=>'calc'),
  1348. 'sxd' => array ('type'=>'application/vnd.sun.xml.draw', 'icon'=>'draw'),
  1349. 'std' => array ('type'=>'application/vnd.sun.xml.draw.template', 'icon'=>'draw'),
  1350. 'sxi' => array ('type'=>'application/vnd.sun.xml.impress', 'icon'=>'impress'),
  1351. 'sti' => array ('type'=>'application/vnd.sun.xml.impress.template', 'icon'=>'impress'),
  1352. 'sxg' => array ('type'=>'application/vnd.sun.xml.writer.global', 'icon'=>'writer'),
  1353. 'sxm' => array ('type'=>'application/vnd.sun.xml.math', 'icon'=>'math'),
  1354. 'tar' => array ('type'=>'application/x-tar', 'icon'=>'archive', 'groups'=>array('archive'), 'string'=>'archive'),
  1355. 'tif' => array ('type'=>'image/tiff', 'icon'=>'tiff', 'groups'=>array('image'), 'string'=>'image'),
  1356. 'tiff' => array ('type'=>'image/tiff', 'icon'=>'tiff', 'groups'=>array('image'), 'string'=>'image'),
  1357. 'tex' => array ('type'=>'application/x-tex', 'icon'=>'text'),
  1358. 'texi' => array ('type'=>'application/x-texinfo', 'icon'=>'text'),
  1359. 'texinfo' => array ('type'=>'application/x-texinfo', 'icon'=>'text'),
  1360. 'tsv' => array ('type'=>'text/tab-separated-values', 'icon'=>'text'),
  1361. 'txt' => array ('type'=>'text/plain', 'icon'=>'text', 'defaulticon'=>true),
  1362. 'wav' => array ('type'=>'audio/wav', 'icon'=>'wav', 'groups'=>array('audio'), 'string'=>'audio'),
  1363. 'webm' => array ('type'=>'video/webm', 'icon'=>'video', 'groups'=>array('video'), 'string'=>'video'),
  1364. 'wmv' => array ('type'=>'video/x-ms-wmv', 'icon'=>'wmv', 'groups'=>array('video'), 'string'=>'video'),
  1365. 'asf' => array ('type'=>'video/x-ms-asf', 'icon'=>'wmv', 'groups'=>array('video'), 'string'=>'video'),
  1366. 'xdp' => array ('type'=>'application/pdf', 'icon'=>'pdf'),
  1367. 'xfd' => array ('type'=>'application/pdf', 'icon'=>'pdf'),
  1368. 'xfdf' => array ('type'=>'application/pdf', 'icon'=>'pdf'),
  1369. 'xls' => array ('type'=>'application/vnd.ms-excel', 'icon'=>'spreadsheet', 'groups'=>array('spreadsheet')),
  1370. 'xlsx' => array ('type'=>'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'icon'=>'spreadsheet'),
  1371. 'xlsm' => array ('type'=>'application/vnd.ms-excel.sheet.macroEnabled.12', 'icon'=>'spreadsheet', 'groups'=>array('spreadsheet')),
  1372. 'xltx' => array ('type'=>'application/vnd.openxmlformats-officedocument.spreadsheetml.template', 'icon'=>'spreadsheet'),
  1373. 'xltm' => array ('type'=>'application/vnd.ms-excel.template.macroEnabled.12', 'icon'=>'spreadsheet'),
  1374. 'xlsb' => array ('type'=>'application/vnd.ms-excel.sheet.binary.macroEnabled.12', 'icon'=>'spreadsheet'),
  1375. 'xlam' => array ('type'=>'application/vnd.ms-excel.addin.macroEnabled.12', 'icon'=>'spreadsheet'),
  1376. 'xml' => array ('type'=>'application/xml', 'icon'=>'markup'),
  1377. 'xsl' => array ('type'=>'text/xml', 'icon'=>'markup'),
  1378. 'zip' => array ('type'=>'application/zip', 'icon'=>'archive', 'groups'=>array('archive'), 'string'=>'archive')
  1379. );
  1380. return $mimearray;
  1381. }
  1382. /**
  1383. * Obtains information about a filetype based on its extension. Will
  1384. * use a default if no information is present about that particular
  1385. * extension.
  1386. *
  1387. * @category files
  1388. * @param string $element Desired information (usually 'icon'
  1389. * for icon filename or 'type' for MIME type. Can also be
  1390. * 'icon24', ...32, 48, 64, 72, 80, 96, 128, 256)
  1391. * @param string $filename Filename we're looking up
  1392. * @return string Requested piece of information from array
  1393. */
  1394. function mimeinfo($element, $filename) {
  1395. global $CFG;
  1396. $mimeinfo = & get_mimetypes_array();
  1397. static $iconpostfixes = array(256=>'-256', 128=>'-128', 96=>'-96', 80=>'-80', 72=>'-72', 64=>'-64', 48=>'-48', 32=>'-32', 24=>'-24', 16=>'');
  1398. $filetype = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
  1399. if (empty($filetype)) {
  1400. $filetype = 'xxx'; // file without extension
  1401. }
  1402. if (preg_match('/^icon(\d*)$/', $element, $iconsizematch)) {
  1403. $iconsize = max(array(16, (int)$iconsizematch[1]));
  1404. $filenames = array($mimeinfo['xxx']['icon']);
  1405. if ($filetype != 'xxx' && isset($mimeinfo[$filetype]['icon'])) {
  1406. array_unshift($filenames, $mimeinfo[$filetype]['icon']);
  1407. }
  1408. // find the file with the closest size, first search for specific icon then for default icon
  1409. foreach ($filenames as $filename) {
  1410. foreach ($iconpostfixes as $size => $postfix) {
  1411. $fullname = $CFG->dirroot.'/pix/f/'.$filename.$postfix;
  1412. if ($iconsize >= $size && (file_exists($fullname.'.png') || file_exists($fullname.'.gif'))) {
  1413. return $filename.$postfix;
  1414. }
  1415. }
  1416. }
  1417. } else if (isset($mimeinfo[$filetype][$element])) {
  1418. return $mimeinfo[$filetype][$element];
  1419. } else if (isset($mimeinfo['xxx'][$element])) {
  1420. return $mimeinfo['xxx'][$element]; // By default
  1421. } else {
  1422. return null;
  1423. }
  1424. }
  1425. /**
  1426. * Obtains information about a filetype based on the MIME type rather than
  1427. * the other way around.
  1428. *
  1429. * @category files
  1430. * @param string $element Desired information ('extension', 'icon', 'icon-24', etc.)
  1431. * @param string $mimetype MIME type we're looking up
  1432. * @return string Requested piece of information from array
  1433. */
  1434. function mimeinfo_from_type($element, $mimetype) {
  1435. /* array of cached mimetype->extension associations */
  1436. static $cached = array();
  1437. $mimeinfo = & get_mimetypes_array();
  1438. if (!array_key_exists($mimetype, $cached)) {
  1439. $cached[$mimetype] = null;
  1440. foreach($mimeinfo as $filetype => $values) {
  1441. if ($values['type'] == $mimetype) {
  1442. if ($cached[$mimetype] === null) {
  1443. $cached[$mimetype] = '.'.$filetype;
  1444. }
  1445. if (!empty($values['defaulticon'])) {
  1446. $cached[$mimetype] = '.'.$filetype;
  1447. break;
  1448. }
  1449. }
  1450. }
  1451. if (empty($cached[$mimetype])) {
  1452. $cached[$mimetype] = '.xxx';
  1453. }
  1454. }
  1455. if ($element === 'extension') {
  1456. return $cached[$mimetype];
  1457. } else {
  1458. return mimeinfo($element, $cached[$mimetype]);
  1459. }
  1460. }
  1461. /**
  1462. * Return the relative icon path for a given file
  1463. *
  1464. * Usage:
  1465. * <code>
  1466. * // $file - instance of stored_file or file_info
  1467. * $icon = $OUTPUT->pix_url(file_file_icon($file))->out();
  1468. * echo html_writer::empty_tag('img', array('src' => $icon, 'alt' => get_mimetype_description($file)));
  1469. * </code>
  1470. * or
  1471. * <code>
  1472. * echo $OUTPUT->pix_icon(file_file_icon($file), get_mimetype_description($file));
  1473. * </code>
  1474. *
  1475. * @param stored_file|file_info|stdClass|array $file (in case of object attributes $file->filename
  1476. * and $file->mimetype are expected)
  1477. * @param int $size The size of the icon. Defaults to 16 can also be 24, 32, 64, 128, 256
  1478. * @return string
  1479. */
  1480. function file_file_icon($file, $size = null) {
  1481. if (!is_object($file)) {
  1482. $file = (object)$file;
  1483. }
  1484. if (isset($file->filename)) {
  1485. $filename = $file->filename;
  1486. } else if (method_exists($file, 'get_filename')) {
  1487. $filename = $file->get_filename();
  1488. } else if (method_exists($file, 'get_visible_name')) {
  1489. $filename = $file->get_visible_name();
  1490. } else {
  1491. $filename = '';
  1492. }
  1493. if (isset($file->mimetype)) {
  1494. $mimetype = $file->mimetype;
  1495. } else if (method_exists($file, 'get_mimetype')) {
  1496. $mimetype = $file->get_mimetype();
  1497. } else {
  1498. $mimetype = '';
  1499. }
  1500. $mimetypes = &get_mimetypes_array();
  1501. if ($filename) {
  1502. $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
  1503. if ($extension && !empty($mimetypes[$extension])) {
  1504. // if file name has known extension, return icon for this extension
  1505. return file_extension_icon($filename, $size);
  1506. }
  1507. }
  1508. return file_mimetype_icon($mimetype, $size);
  1509. }
  1510. /**
  1511. * Return the relative icon path for a folder image
  1512. *
  1513. * Usage:
  1514. * <code>
  1515. * $icon = $OUTPUT->pix_url(file_folder_icon())->out();
  1516. * echo html_writer::empty_tag('img', array('src' => $icon));
  1517. * </code>
  1518. * or
  1519. * <code>
  1520. * echo $OUTPUT->pix_icon(file_folder_icon(32));
  1521. * </code>
  1522. *
  1523. * @param int $iconsize The size of the icon. Defaults to 16 can also be 24, 32, 48, 64, 72, 80, 96, 128, 256
  1524. * @return string
  1525. */
  1526. function file_folder_icon($iconsize = null) {
  1527. global $CFG;
  1528. static $iconpostfixes = array(256=>'-256', 128=>'-128', 96=>'-96', 80=>'-80', 72=>'-72', 64=>'-64', 48=>'-48', 32=>'-32', 24=>'-24', 16=>'');
  1529. static $cached = array();
  1530. $iconsize = max(array(16, (int)$iconsize));
  1531. if (!array_key_exists($iconsize, $cached)) {
  1532. foreach ($iconpostfixes as $size => $postfix) {
  1533. $fullname = $CFG->dirroot.'/pix/f/folder'.$postfix;
  1534. if ($iconsize >= $size && (file_exists($fullname.'.png') || file_exists($fullname.'.gif'))) {
  1535. $cached[$iconsize] = 'f/folder'.$postfix;
  1536. break;
  1537. }
  1538. }
  1539. }
  1540. return $cached[$iconsize];
  1541. }
  1542. /**
  1543. * Returns the relative icon path for a given mime type
  1544. *
  1545. * This function should be used in conjunction with $OUTPUT->pix_url to produce
  1546. * a return the full path to an icon.
  1547. *
  1548. * <code>
  1549. * $mimetype = 'image/jpg';
  1550. * $icon = $OUTPUT->pix_url(file_mimetype_icon($mimetype))->out();
  1551. * echo html_writer::empty_tag('img', array('src' => $icon, 'alt' => get_mimetype_description($mimetype)));
  1552. * </code>
  1553. *
  1554. * @category files
  1555. * @todo MDL-31074 When an $OUTPUT->icon method is available this function should be altered
  1556. * to conform with that.
  1557. * @param string $mimetype The mimetype to fetch an icon for
  1558. * @param int $size The size of the icon. Defaults to 16 can also be 24, 32, 64, 128, 256
  1559. * @return string The relative path to the icon
  1560. */
  1561. function file_mimetype_icon($mimetype, $size = NULL) {
  1562. return 'f/'.mimeinfo_from_type('icon'.$size, $mimetype);
  1563. }
  1564. /**
  1565. * Returns the relative icon path for a given file name
  1566. *
  1567. * This function should be used in conjunction with $OUTPUT->pix_url to produce
  1568. * a return the full path to an icon.
  1569. *
  1570. * <code>
  1571. * $filename = '.jpg';
  1572. * $icon = $OUTPUT->pix_url(file_extension_icon($filename))->out();
  1573. * echo html_writer::empty_tag('img', array('src' => $icon, 'alt' => '...'));
  1574. * </code>
  1575. *
  1576. * @todo MDL-31074 When an $OUTPUT->icon method is available this function should be altered
  1577. * to conform with that.
  1578. * @todo MDL-31074 Implement $size
  1579. * @category files
  1580. * @param string $filename The filename to get the icon for
  1581. * @param int $size The size of the icon. Defaults to 16 can also be 24, 32, 64, 128, 256
  1582. * @return string
  1583. */
  1584. function file_extension_icon($filename, $size = NULL) {
  1585. return 'f/'.mimeinfo('icon'.$size, $filename);
  1586. }
  1587. /**
  1588. * Obtains descriptions for file types (e.g. 'Microsoft Word document') from the
  1589. * mimetypes.php language file.
  1590. *
  1591. * @param mixed $obj - instance of stored_file or file_info or array/stdClass with field
  1592. * 'filename' and 'mimetype', or just a string with mimetype (though it is recommended to
  1593. * have filename); In case of array/stdClass the field 'mimetype' is optional.
  1594. * @param bool $capitalise If true, capitalises first character of result
  1595. * @return string Text description
  1596. */
  1597. function get_mimetype_description($obj, $capitalise=false) {
  1598. $filename = $mimetype = '';
  1599. if (is_object($obj) && method_exists($obj, 'get_filename') && method_exists($obj, 'get_mimetype')) {
  1600. // this is an instance of stored_file
  1601. $mimetype = $obj->get_mimetype();
  1602. $filename = $obj->get_filename();
  1603. } else if (is_object($obj) && method_exists($obj, 'get_visible_name') && method_exists($obj, 'get_mimetype')) {
  1604. // this is an instance of file_info
  1605. $mimetype = $obj->get_mimetype();
  1606. $filename = $obj->get_visible_name();
  1607. } else if (is_array($obj) || is_object ($obj)) {
  1608. $obj = (array)$obj;
  1609. if (!empty($obj['filename'])) {
  1610. $filename = $obj['filename'];
  1611. }
  1612. if (!empty($obj['mimetype'])) {
  1613. $mimetype = $obj['mimetype'];
  1614. }
  1615. } else {
  1616. $mimetype = $obj;
  1617. }
  1618. $mimetypefromext = mimeinfo('type', $filename);
  1619. if (empty($mimetype) || $mimetypefromext !== 'document/unknown') {
  1620. // if file has a known extension, overwrite the specified mimetype
  1621. $mimetype = $mimetypefromext;
  1622. }
  1623. $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
  1624. if (empty($extension)) {
  1625. $mimetypestr = mimeinfo_from_type('string', $mimetype);
  1626. $extension = str_replace('.', '', mimeinfo_from_type('extension', $mimetype));
  1627. } else {
  1628. $mimetypestr = mimeinfo('string', $filename);
  1629. }
  1630. $chunks = explode('/', $mimetype, 2);
  1631. $chunks[] = '';
  1632. $attr = array(
  1633. 'mimetype' => $mimetype,
  1634. 'ext' => $extension,
  1635. 'mimetype1' => $chunks[0],
  1636. 'mimetype2' => $chunks[1],
  1637. );
  1638. $a = array();
  1639. foreach ($attr as $key => $value) {
  1640. $a[$key] = $value;
  1641. $a[strtoupper($key)] = strtoupper($value);
  1642. $a[ucfirst($key)] = ucfirst($value);
  1643. }
  1644. if (get_string_manager()->string_exists($mimetype, 'mimetypes')) {
  1645. $result = get_string($mimetype, 'mimetypes', (object)$a);
  1646. } else if (get_string_manager()->string_exists($mimetypestr, 'mimetypes')) {
  1647. $result = get_string($mimetypestr, 'mimetypes', (object)$a);
  1648. } else if (get_string_manager()->string_exists('default', 'mimetypes')) {
  1649. $result = get_string('default', 'mimetypes', (object)$a);
  1650. } else {
  1651. $result = $mimetype;
  1652. }
  1653. if ($capitalise) {
  1654. $result=ucfirst($result);
  1655. }
  1656. return $result;
  1657. }
  1658. /**
  1659. * Returns array of elements of type $element in type group(s)
  1660. *
  1661. * @param string $element name of the element we are interested in, usually 'type' or 'extension'
  1662. * @param string|array $groups one group or array of groups/extensions/mimetypes
  1663. * @return array
  1664. */
  1665. function file_get_typegroup($element, $groups) {
  1666. static $cached = array();
  1667. if (!is_array($groups)) {
  1668. $groups = array($groups);
  1669. }
  1670. if (!array_key_exists($element, $cached)) {
  1671. $cached[$element] = array();
  1672. }
  1673. $result = array();
  1674. foreach ($groups as $group) {
  1675. if (!array_key_exists($group, $cached[$element])) {
  1676. // retrieive and cache all elements of type $element for group $group
  1677. $mimeinfo = & get_mimetypes_array();
  1678. $cached[$element][$group] = array();
  1679. foreach ($mimeinfo as $extension => $value) {
  1680. $value['extension'] = '.'.$extension;
  1681. if (empty($value[$element])) {
  1682. continue;
  1683. }
  1684. if (($group === '.'.$extension || $group === $value['type'] ||
  1685. (!empty($value['groups']) && in_array($group, $value['groups']))) &&
  1686. !in_array($value[$element], $cached[$element][$group])) {
  1687. $cached[$element][$group][] = $value[$element];
  1688. }
  1689. }
  1690. }
  1691. $result = array_merge($result, $cached[$element][$group]);
  1692. }
  1693. return array_unique($result);
  1694. }
  1695. /**
  1696. * Checks if file with name $filename has one of the extensions in groups $groups
  1697. *
  1698. * @see get_mimetypes_array()
  1699. * @param string $filename name of the file to check
  1700. * @param string|array $groups one group or array of groups to check
  1701. * @param bool $checktype if true and extension check fails, find the mimetype and check if
  1702. * file mimetype is in mimetypes in groups $groups
  1703. * @return bool
  1704. */
  1705. function file_extension_in_typegroup($filename, $groups, $checktype = false) {
  1706. $extension = pathinfo($filename, PATHINFO_EXTENSION);
  1707. if (!empty($extension) && in_array('.'.strtolower($extension), file_get_typegroup('extension', $groups))) {
  1708. return true;
  1709. }
  1710. return $checktype && file_mimetype_in_typegroup(mimeinfo('type', $filename), $groups);
  1711. }
  1712. /**
  1713. * Checks if mimetype $mimetype belongs to one of the groups $groups
  1714. *
  1715. * @see get_mimetypes_array()
  1716. * @param string $mimetype
  1717. * @param string|array $groups one group or array of groups to check
  1718. * @return bool
  1719. */
  1720. function file_mimetype_in_typegroup($mimetype, $groups) {
  1721. return !empty($mimetype) && in_array($mimetype, file_get_typegroup('type', $groups));
  1722. }
  1723. /**
  1724. * Requested file is not found or not accessible, does not return, terminates script
  1725. *
  1726. * @global stdClass $CFG
  1727. * @global stdClass $COURSE
  1728. */
  1729. function send_file_not_found() {
  1730. global $CFG, $COURSE;
  1731. send_header_404();
  1732. print_error('filenotfound', 'error', $CFG->wwwroot.'/course/view.php?id='.$COURSE->id); //this is not displayed on IIS??
  1733. }
  1734. /**
  1735. * Helper function to send correct 404 for server.
  1736. */
  1737. function send_header_404() {
  1738. if (substr(php_sapi_name(), 0, 3) == 'cgi') {
  1739. header("Status: 404 Not Found");
  1740. } else {
  1741. header('HTTP/1.0 404 not found');
  1742. }
  1743. }
  1744. /**
  1745. * Enhanced readfile() with optional acceleration.
  1746. * @param string|stored_file $file
  1747. * @param string $mimetype
  1748. * @param bool $accelerate
  1749. * @return void
  1750. */
  1751. function readfile_accel($file, $mimetype, $accelerate) {
  1752. global $CFG;
  1753. if ($mimetype === 'text/plain') {
  1754. // there is no encoding specified in text files, we need something consistent
  1755. header('Content-Type: text/plain; charset=utf-8');
  1756. } else {
  1757. header('Content-Type: '.$mimetype);
  1758. }
  1759. $lastmodified = is_object($file) ? $file->get_timemodified() : filemtime($file);
  1760. header('Last-Modified: '. gmdate('D, d M Y H:i:s', $lastmodified) .' GMT');
  1761. if (is_object($file)) {
  1762. header('ETag: ' . $file->get_contenthash());
  1763. if (isset($_SERVER['HTTP_IF_NONE_MATCH']) and $_SERVER['HTTP_IF_NONE_MATCH'] === $file->get_contenthash()) {
  1764. header('HTTP/1.1 304 Not Modified');
  1765. return;
  1766. }
  1767. }
  1768. // if etag present for stored file rely on it exclusively
  1769. if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) and (empty($_SERVER['HTTP_IF_NONE_MATCH']) or !is_object($file))) {
  1770. // get unixtime of request header; clip extra junk off first
  1771. $since = strtotime(preg_replace('/;.*$/', '', $_SERVER["HTTP_IF_MODIFIED_SINCE"]));
  1772. if ($since && $since >= $lastmodified) {
  1773. header('HTTP/1.1 304 Not Modified');
  1774. return;
  1775. }
  1776. }
  1777. if ($accelerate and !empty($CFG->xsendfile)) {
  1778. if (empty($CFG->disablebyteserving) and $mimetype !== 'text/plain') {
  1779. header('Accept-Ranges: bytes');
  1780. } else {
  1781. header('Accept-Ranges: none');
  1782. }
  1783. if (is_object($file)) {
  1784. $fs = get_file_storage();
  1785. if ($fs->xsendfile($file->get_contenthash())) {
  1786. return;
  1787. }
  1788. } else {
  1789. require_once("$CFG->libdir/xsendfilelib.php");
  1790. if (xsendfile($file)) {
  1791. return;
  1792. }
  1793. }
  1794. }
  1795. $filesize = is_object($file) ? $file->get_filesize() : filesize($file);
  1796. header('Last-Modified: '. gmdate('D, d M Y H:i:s', $lastmodified) .' GMT');
  1797. if ($accelerate and empty($CFG->disablebyteserving) and $mimetype !== 'text/plain') {
  1798. header('Accept-Ranges: bytes');
  1799. if (!empty($_SERVER['HTTP_RANGE']) and strpos($_SERVER['HTTP_RANGE'],'bytes=') !== FALSE) {
  1800. // byteserving stuff - for acrobat reader and download accelerators
  1801. // see: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35
  1802. // inspired by: http://www.coneural.org/florian/papers/04_byteserving.php
  1803. $ranges = false;
  1804. if (preg_match_all('/(\d*)-(\d*)/', $_SERVER['HTTP_RANGE'], $ranges, PREG_SET_ORDER)) {
  1805. foreach ($ranges as $key=>$value) {
  1806. if ($ranges[$key][1] == '') {
  1807. //suffix case
  1808. $ranges[$key][1] = $filesize - $ranges[$key][2];
  1809. $ranges[$key][2] = $filesize - 1;
  1810. } else if ($ranges[$key][2] == '' || $ranges[$key][2] > $filesize - 1) {
  1811. //fix range length
  1812. $ranges[$key][2] = $filesize - 1;
  1813. }
  1814. if ($ranges[$key][2] != '' && $ranges[$key][2] < $ranges[$key][1]) {
  1815. //invalid byte-range ==> ignore header
  1816. $ranges = false;
  1817. break;
  1818. }
  1819. //prepare multipart header
  1820. $ranges[$key][0] = "\r\n--".BYTESERVING_BOUNDARY."\r\nContent-Type: $mimetype\r\n";
  1821. $ranges[$key][0] .= "Content-Range: bytes {$ranges[$key][1]}-{$ranges[$key][2]}/$filesize\r\n\r\n";
  1822. }
  1823. } else {
  1824. $ranges = false;
  1825. }
  1826. if ($ranges) {
  1827. if (is_object($file)) {
  1828. $handle = $file->get_content_file_handle();
  1829. } else {
  1830. $handle = fopen($file, 'rb');
  1831. }
  1832. byteserving_send_file($handle, $mimetype, $ranges, $filesize);
  1833. }
  1834. }
  1835. } else {
  1836. // Do not byteserve
  1837. header('Accept-Ranges: none');
  1838. }
  1839. header('Content-Length: '.$filesize);
  1840. if ($filesize > 10000000) {
  1841. // for large files try to flush and close all buffers to conserve memory
  1842. while(@ob_get_level()) {
  1843. if (!@ob_end_flush()) {
  1844. break;
  1845. }
  1846. }
  1847. }
  1848. // send the whole file content
  1849. if (is_object($file)) {
  1850. $file->readfile();
  1851. } else {
  1852. readfile($file);
  1853. }
  1854. }
  1855. /**
  1856. * Similar to readfile_accel() but designed for strings.
  1857. * @param string $string
  1858. * @param string $mimetype
  1859. * @param bool $accelerate
  1860. * @return void
  1861. */
  1862. function readstring_accel($string, $mimetype, $accelerate) {
  1863. global $CFG;
  1864. if ($mimetype === 'text/plain') {
  1865. // there is no encoding specified in text files, we need something consistent
  1866. header('Content-Type: text/plain; charset=utf-8');
  1867. } else {
  1868. header('Content-Type: '.$mimetype);
  1869. }
  1870. header('Last-Modified: '. gmdate('D, d M Y H:i:s', time()) .' GMT');
  1871. header('Accept-Ranges: none');
  1872. if ($accelerate and !empty($CFG->xsendfile)) {
  1873. $fs = get_file_storage();
  1874. if ($fs->xsendfile(sha1($string))) {
  1875. return;
  1876. }
  1877. }
  1878. header('Content-Length: '.strlen($string));
  1879. echo $string;
  1880. }
  1881. /**
  1882. * Handles the sending of temporary file to user, download is forced.
  1883. * File is deleted after abort or successful sending, does not return, script terminated
  1884. *
  1885. * @param string $path path to file, preferably from moodledata/temp/something; or content of file itself
  1886. * @param string $filename proposed file name when saving file
  1887. * @param bool $pathisstring If the path is string
  1888. */
  1889. function send_temp_file($path, $filename, $pathisstring=false) {
  1890. global $CFG;
  1891. if (check_browser_version('Firefox', '1.5')) {
  1892. // only FF is known to correctly save to disk before opening...
  1893. $mimetype = mimeinfo('type', $filename);
  1894. } else {
  1895. $mimetype = 'application/x-forcedownload';
  1896. }
  1897. // close session - not needed anymore
  1898. session_get_instance()->write_close();
  1899. if (!$pathisstring) {
  1900. if (!file_exists($path)) {
  1901. send_header_404();
  1902. print_error('filenotfound', 'error', $CFG->wwwroot.'/');
  1903. }
  1904. // executed after normal finish or abort
  1905. @register_shutdown_function('send_temp_file_finished', $path);
  1906. }
  1907. // if user is using IE, urlencode the filename so that multibyte file name will show up correctly on popup
  1908. if (check_browser_version('MSIE')) {
  1909. $filename = urlencode($filename);
  1910. }
  1911. header('Content-Disposition: attachment; filename="'.$filename.'"');
  1912. if (strpos($CFG->wwwroot, 'https://') === 0) { //https sites - watch out for IE! KB812935 and KB316431
  1913. header('Cache-Control: max-age=10');
  1914. header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
  1915. header('Pragma: ');
  1916. } else { //normal http - prevent caching at all cost
  1917. header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0');
  1918. header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
  1919. header('Pragma: no-cache');
  1920. }
  1921. // send the contents - we can not accelerate this because the file will be deleted asap
  1922. if ($pathisstring) {
  1923. readstring_accel($path, $mimetype, false);
  1924. } else {
  1925. readfile_accel($path, $mimetype, false);
  1926. @unlink($path);
  1927. }
  1928. die; //no more chars to output
  1929. }
  1930. /**
  1931. * Internal callback function used by send_temp_file()
  1932. *
  1933. * @param string $path
  1934. */
  1935. function send_temp_file_finished($path) {
  1936. if (file_exists($path)) {
  1937. @unlink($path);
  1938. }
  1939. }
  1940. /**
  1941. * Handles the sending of file data to the user's browser, including support for
  1942. * byteranges etc.
  1943. *
  1944. * @category files
  1945. * @param string $path Path of file on disk (including real filename), or actual content of file as string
  1946. * @param string $filename Filename to send
  1947. * @param int $lifetime Number of seconds before the file should expire from caches (default 24 hours)
  1948. * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only
  1949. * @param bool $pathisstring If true (default false), $path is the content to send and not the pathname
  1950. * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin
  1951. * @param string $mimetype Include to specify the MIME type; leave blank to have it guess the type from $filename
  1952. * @param bool $dontdie - return control to caller afterwards. this is not recommended and only used for cleanup tasks.
  1953. * if this is passed as true, ignore_user_abort is called. if you don't want your processing to continue on cancel,
  1954. * you must detect this case when control is returned using connection_aborted. Please not that session is closed
  1955. * and should not be reopened.
  1956. * @return null script execution stopped unless $dontdie is true
  1957. */
  1958. function send_file($path, $filename, $lifetime = 'default' , $filter=0, $pathisstring=false, $forcedownload=false, $mimetype='', $dontdie=false) {
  1959. global $CFG, $COURSE;
  1960. if ($dontdie) {
  1961. ignore_user_abort(true);
  1962. }
  1963. // MDL-11789, apply $CFG->filelifetime here
  1964. if ($lifetime === 'default') {
  1965. if (!empty($CFG->filelifetime)) {
  1966. $lifetime = $CFG->filelifetime;
  1967. } else {
  1968. $lifetime = 86400;
  1969. }
  1970. }
  1971. session_get_instance()->write_close(); // unlock session during fileserving
  1972. // Use given MIME type if specified, otherwise guess it using mimeinfo.
  1973. // IE, Konqueror and Opera open html file directly in browser from web even when directed to save it to disk :-O
  1974. // only Firefox saves all files locally before opening when content-disposition: attachment stated
  1975. $isFF = check_browser_version('Firefox', '1.5'); // only FF > 1.5 properly tested
  1976. $mimetype = ($forcedownload and !$isFF) ? 'application/x-forcedownload' :
  1977. ($mimetype ? $mimetype : mimeinfo('type', $filename));
  1978. // if user is using IE, urlencode the filename so that multibyte file name will show up correctly on popup
  1979. if (check_browser_version('MSIE')) {
  1980. $filename = rawurlencode($filename);
  1981. }
  1982. if ($forcedownload) {
  1983. header('Content-Disposition: attachment; filename="'.$filename.'"');
  1984. } else {
  1985. header('Content-Disposition: inline; filename="'.$filename.'"');
  1986. }
  1987. if ($lifetime > 0) {
  1988. $nobyteserving = false;
  1989. header('Cache-Control: max-age='.$lifetime);
  1990. header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
  1991. header('Pragma: ');
  1992. } else { // Do not cache files in proxies and browsers
  1993. $nobyteserving = true;
  1994. if (strpos($CFG->wwwroot, 'https://') === 0) { //https sites - watch out for IE! KB812935 and KB316431
  1995. header('Cache-Control: max-age=10');
  1996. header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
  1997. header('Pragma: ');
  1998. } else { //normal http - prevent caching at all cost
  1999. header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0');
  2000. header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
  2001. header('Pragma: no-cache');
  2002. }
  2003. }
  2004. if (empty($filter)) {
  2005. // send the contents
  2006. if ($pathisstring) {
  2007. readstring_accel($path, $mimetype, !$dontdie);
  2008. } else {
  2009. readfile_accel($path, $mimetype, !$dontdie);
  2010. }
  2011. } else {
  2012. // Try to put the file through filters
  2013. if ($mimetype == 'text/html') {
  2014. $options = new stdClass();
  2015. $options->noclean = true;
  2016. $options->nocache = true; // temporary workaround for MDL-5136
  2017. $text = $pathisstring ? $path : implode('', file($path));
  2018. $text = file_modify_html_header($text);
  2019. $output = format_text($text, FORMAT_HTML, $options, $COURSE->id);
  2020. readstring_accel($output, $mimetype, false);
  2021. } else if (($mimetype == 'text/plain') and ($filter == 1)) {
  2022. // only filter text if filter all files is selected
  2023. $options = new stdClass();
  2024. $options->newlines = false;
  2025. $options->noclean = true;
  2026. $text = htmlentities($pathisstring ? $path : implode('', file($path)));
  2027. $output = '<pre>'. format_text($text, FORMAT_MOODLE, $options, $COURSE->id) .'</pre>';
  2028. readstring_accel($output, $mimetype, false);
  2029. } else {
  2030. // send the contents
  2031. if ($pathisstring) {
  2032. readstring_accel($path, $mimetype, !$dontdie);
  2033. } else {
  2034. readfile_accel($path, $mimetype, !$dontdie);
  2035. }
  2036. }
  2037. }
  2038. if ($dontdie) {
  2039. return;
  2040. }
  2041. die; //no more chars to output!!!
  2042. }
  2043. /**
  2044. * Handles the sending of file data to the user's browser, including support for
  2045. * byteranges etc.
  2046. *
  2047. * The $options parameter supports the following keys:
  2048. * (string|null) preview - send the preview of the file (e.g. "thumb" for a thumbnail)
  2049. * (string|null) filename - overrides the implicit filename
  2050. * (bool) dontdie - return control to caller afterwards. this is not recommended and only used for cleanup tasks.
  2051. * if this is passed as true, ignore_user_abort is called. if you don't want your processing to continue on cancel,
  2052. * you must detect this case when control is returned using connection_aborted. Please not that session is closed
  2053. * and should not be reopened.
  2054. *
  2055. * @category files
  2056. * @param stored_file $stored_file local file object
  2057. * @param int $lifetime Number of seconds before the file should expire from caches (default 24 hours)
  2058. * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only
  2059. * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin
  2060. * @param array $options additional options affecting the file serving
  2061. * @return null script execution stopped unless $options['dontdie'] is true
  2062. */
  2063. function send_stored_file($stored_file, $lifetime=86400 , $filter=0, $forcedownload=false, array $options=array()) {
  2064. global $CFG, $COURSE;
  2065. if (empty($options['filename'])) {
  2066. $filename = null;
  2067. } else {
  2068. $filename = $options['filename'];
  2069. }
  2070. if (empty($options['dontdie'])) {
  2071. $dontdie = false;
  2072. } else {
  2073. $dontdie = true;
  2074. }
  2075. if (!empty($options['preview'])) {
  2076. // replace the file with its preview
  2077. $fs = get_file_storage();
  2078. $preview_file = $fs->get_file_preview($stored_file, $options['preview']);
  2079. if (!$preview_file) {
  2080. // unable to create a preview of the file, send its default mime icon instead
  2081. if ($options['preview'] === 'tinyicon') {
  2082. $size = 24;
  2083. } else if ($options['preview'] === 'thumb') {
  2084. $size = 90;
  2085. } else {
  2086. $size = 256;
  2087. }
  2088. $fileicon = file_file_icon($stored_file, $size);
  2089. send_file($CFG->dirroot.'/pix/'.$fileicon.'.png', basename($fileicon).'.png');
  2090. } else {
  2091. // preview images have fixed cache lifetime and they ignore forced download
  2092. // (they are generated by GD and therefore they are considered reasonably safe).
  2093. $stored_file = $preview_file;
  2094. $lifetime = DAYSECS;
  2095. $filter = 0;
  2096. $forcedownload = false;
  2097. }
  2098. }
  2099. // handle external resource
  2100. if ($stored_file && $stored_file->is_external_file()) {
  2101. $stored_file->send_file($lifetime, $filter, $forcedownload, $options);
  2102. die;
  2103. }
  2104. if (!$stored_file or $stored_file->is_directory()) {
  2105. // nothing to serve
  2106. if ($dontdie) {
  2107. return;
  2108. }
  2109. die;
  2110. }
  2111. if ($dontdie) {
  2112. ignore_user_abort(true);
  2113. }
  2114. session_get_instance()->write_close(); // unlock session during fileserving
  2115. // Use given MIME type if specified, otherwise guess it using mimeinfo.
  2116. // IE, Konqueror and Opera open html file directly in browser from web even when directed to save it to disk :-O
  2117. // only Firefox saves all files locally before opening when content-disposition: attachment stated
  2118. $filename = is_null($filename) ? $stored_file->get_filename() : $filename;
  2119. $isFF = check_browser_version('Firefox', '1.5'); // only FF > 1.5 properly tested
  2120. $mimetype = ($forcedownload and !$isFF) ? 'application/x-forcedownload' :
  2121. ($stored_file->get_mimetype() ? $stored_file->get_mimetype() : mimeinfo('type', $filename));
  2122. // if user is using IE, urlencode the filename so that multibyte file name will show up correctly on popup
  2123. if (check_browser_version('MSIE')) {
  2124. $filename = rawurlencode($filename);
  2125. }
  2126. if ($forcedownload) {
  2127. header('Content-Disposition: attachment; filename="'.$filename.'"');
  2128. } else {
  2129. header('Content-Disposition: inline; filename="'.$filename.'"');
  2130. }
  2131. if ($lifetime > 0) {
  2132. header('Cache-Control: max-age='.$lifetime);
  2133. header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
  2134. header('Pragma: ');
  2135. } else { // Do not cache files in proxies and browsers
  2136. if (strpos($CFG->wwwroot, 'https://') === 0) { //https sites - watch out for IE! KB812935 and KB316431
  2137. header('Cache-Control: max-age=10');
  2138. header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
  2139. header('Pragma: ');
  2140. } else { //normal http - prevent caching at all cost
  2141. header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0');
  2142. header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
  2143. header('Pragma: no-cache');
  2144. }
  2145. }
  2146. if (empty($filter)) {
  2147. // send the contents
  2148. readfile_accel($stored_file, $mimetype, !$dontdie);
  2149. } else { // Try to put the file through filters
  2150. if ($mimetype == 'text/html') {
  2151. $options = new stdClass();
  2152. $options->noclean = true;
  2153. $options->nocache = true; // temporary workaround for MDL-5136
  2154. $text = $stored_file->get_content();
  2155. $text = file_modify_html_header($text);
  2156. $output = format_text($text, FORMAT_HTML, $options, $COURSE->id);
  2157. readstring_accel($output, $mimetype, false);
  2158. } else if (($mimetype == 'text/plain') and ($filter == 1)) {
  2159. // only filter text if filter all files is selected
  2160. $options = new stdClass();
  2161. $options->newlines = false;
  2162. $options->noclean = true;
  2163. $text = $stored_file->get_content();
  2164. $output = '<pre>'. format_text($text, FORMAT_MOODLE, $options, $COURSE->id) .'</pre>';
  2165. readstring_accel($output, $mimetype, false);
  2166. } else { // Just send it out raw
  2167. readfile_accel($stored_file, $mimetype, !$dontdie);
  2168. }
  2169. }
  2170. if ($dontdie) {
  2171. return;
  2172. }
  2173. die; //no more chars to output!!!
  2174. }
  2175. /**
  2176. * Retrieves an array of records from a CSV file and places
  2177. * them into a given table structure
  2178. *
  2179. * @global stdClass $CFG
  2180. * @global moodle_database $DB
  2181. * @param string $file The path to a CSV file
  2182. * @param string $table The table to retrieve columns from
  2183. * @return bool|array Returns an array of CSV records or false
  2184. */
  2185. function get_records_csv($file, $table) {
  2186. global $CFG, $DB;
  2187. if (!$metacolumns = $DB->get_columns($table)) {
  2188. return false;
  2189. }
  2190. if(!($handle = @fopen($file, 'r'))) {
  2191. print_error('get_records_csv failed to open '.$file);
  2192. }
  2193. $fieldnames = fgetcsv($handle, 4096);
  2194. if(empty($fieldnames)) {
  2195. fclose($handle);
  2196. return false;
  2197. }
  2198. $columns = array();
  2199. foreach($metacolumns as $metacolumn) {
  2200. $ord = array_search($metacolumn->name, $fieldnames);
  2201. if(is_int($ord)) {
  2202. $columns[$metacolumn->name] = $ord;
  2203. }
  2204. }
  2205. $rows = array();
  2206. while (($data = fgetcsv($handle, 4096)) !== false) {
  2207. $item = new stdClass;
  2208. foreach($columns as $name => $ord) {
  2209. $item->$name = $data[$ord];
  2210. }
  2211. $rows[] = $item;
  2212. }
  2213. fclose($handle);
  2214. return $rows;
  2215. }
  2216. /**
  2217. * Create a file with CSV contents
  2218. *
  2219. * @global stdClass $CFG
  2220. * @global moodle_database $DB
  2221. * @param string $file The file to put the CSV content into
  2222. * @param array $records An array of records to write to a CSV file
  2223. * @param string $table The table to get columns from
  2224. * @return bool success
  2225. */
  2226. function put_records_csv($file, $records, $table = NULL) {
  2227. global $CFG, $DB;
  2228. if (empty($records)) {
  2229. return true;
  2230. }
  2231. $metacolumns = NULL;
  2232. if ($table !== NULL && !$metacolumns = $DB->get_columns($table)) {
  2233. return false;
  2234. }
  2235. echo "x";
  2236. if(!($fp = @fopen($CFG->tempdir.'/'.$file, 'w'))) {
  2237. print_error('put_records_csv failed to open '.$file);
  2238. }
  2239. $proto = reset($records);
  2240. if(is_object($proto)) {
  2241. $fields_records = array_keys(get_object_vars($proto));
  2242. }
  2243. else if(is_array($proto)) {
  2244. $fields_records = array_keys($proto);
  2245. }
  2246. else {
  2247. return false;
  2248. }
  2249. echo "x";
  2250. if(!empty($metacolumns)) {
  2251. $fields_table = array_map(create_function('$a', 'return $a->name;'), $metacolumns);
  2252. $fields = array_intersect($fields_records, $fields_table);
  2253. }
  2254. else {
  2255. $fields = $fields_records;
  2256. }
  2257. fwrite($fp, implode(',', $fields));
  2258. fwrite($fp, "\r\n");
  2259. foreach($records as $record) {
  2260. $array = (array)$record;
  2261. $values = array();
  2262. foreach($fields as $field) {
  2263. if(strpos($array[$field], ',')) {
  2264. $values[] = '"'.str_replace('"', '\"', $array[$field]).'"';
  2265. }
  2266. else {
  2267. $values[] = $array[$field];
  2268. }
  2269. }
  2270. fwrite($fp, implode(',', $values)."\r\n");
  2271. }
  2272. fclose($fp);
  2273. return true;
  2274. }
  2275. /**
  2276. * Recursively delete the file or folder with path $location. That is,
  2277. * if it is a file delete it. If it is a folder, delete all its content
  2278. * then delete it. If $location does not exist to start, that is not
  2279. * considered an error.
  2280. *
  2281. * @param string $location the path to remove.
  2282. * @return bool
  2283. */
  2284. function fulldelete($location) {
  2285. if (empty($location)) {
  2286. // extra safety against wrong param
  2287. return false;
  2288. }
  2289. if (is_dir($location)) {
  2290. if (!$currdir = opendir($location)) {
  2291. return false;
  2292. }
  2293. while (false !== ($file = readdir($currdir))) {
  2294. if ($file <> ".." && $file <> ".") {
  2295. $fullfile = $location."/".$file;
  2296. if (is_dir($fullfile)) {
  2297. if (!fulldelete($fullfile)) {
  2298. return false;
  2299. }
  2300. } else {
  2301. if (!unlink($fullfile)) {
  2302. return false;
  2303. }
  2304. }
  2305. }
  2306. }
  2307. closedir($currdir);
  2308. if (! rmdir($location)) {
  2309. return false;
  2310. }
  2311. } else if (file_exists($location)) {
  2312. if (!unlink($location)) {
  2313. return false;
  2314. }
  2315. }
  2316. return true;
  2317. }
  2318. /**
  2319. * Send requested byterange of file.
  2320. *
  2321. * @param resource $handle A file handle
  2322. * @param string $mimetype The mimetype for the output
  2323. * @param array $ranges An array of ranges to send
  2324. * @param string $filesize The size of the content if only one range is used
  2325. */
  2326. function byteserving_send_file($handle, $mimetype, $ranges, $filesize) {
  2327. // better turn off any kind of compression and buffering
  2328. @ini_set('zlib.output_compression', 'Off');
  2329. $chunksize = 1*(1024*1024); // 1MB chunks - must be less than 2MB!
  2330. if ($handle === false) {
  2331. die;
  2332. }
  2333. if (count($ranges) == 1) { //only one range requested
  2334. $length = $ranges[0][2] - $ranges[0][1] + 1;
  2335. header('HTTP/1.1 206 Partial content');
  2336. header('Content-Length: '.$length);
  2337. header('Content-Range: bytes '.$ranges[0][1].'-'.$ranges[0][2].'/'.$filesize);
  2338. header('Content-Type: '.$mimetype);
  2339. while(@ob_get_level()) {
  2340. if (!@ob_end_flush()) {
  2341. break;
  2342. }
  2343. }
  2344. fseek($handle, $ranges[0][1]);
  2345. while (!feof($handle) && $length > 0) {
  2346. @set_time_limit(60*60); //reset time limit to 60 min - should be enough for 1 MB chunk
  2347. $buffer = fread($handle, ($chunksize < $length ? $chunksize : $length));
  2348. echo $buffer;
  2349. flush();
  2350. $length -= strlen($buffer);
  2351. }
  2352. fclose($handle);
  2353. die;
  2354. } else { // multiple ranges requested - not tested much
  2355. $totallength = 0;
  2356. foreach($ranges as $range) {
  2357. $totallength += strlen($range[0]) + $range[2] - $range[1] + 1;
  2358. }
  2359. $totallength += strlen("\r\n--".BYTESERVING_BOUNDARY."--\r\n");
  2360. header('HTTP/1.1 206 Partial content');
  2361. header('Content-Length: '.$totallength);
  2362. header('Content-Type: multipart/byteranges; boundary='.BYTESERVING_BOUNDARY);
  2363. while(@ob_get_level()) {
  2364. if (!@ob_end_flush()) {
  2365. break;
  2366. }
  2367. }
  2368. foreach($ranges as $range) {
  2369. $length = $range[2] - $range[1] + 1;
  2370. echo $range[0];
  2371. fseek($handle, $range[1]);
  2372. while (!feof($handle) && $length > 0) {
  2373. @set_time_limit(60*60); //reset time limit to 60 min - should be enough for 1 MB chunk
  2374. $buffer = fread($handle, ($chunksize < $length ? $chunksize : $length));
  2375. echo $buffer;
  2376. flush();
  2377. $length -= strlen($buffer);
  2378. }
  2379. }
  2380. echo "\r\n--".BYTESERVING_BOUNDARY."--\r\n";
  2381. fclose($handle);
  2382. die;
  2383. }
  2384. }
  2385. /**
  2386. * add includes (js and css) into uploaded files
  2387. * before returning them, useful for themes and utf.js includes
  2388. *
  2389. * @global stdClass $CFG
  2390. * @param string $text text to search and replace
  2391. * @return string text with added head includes
  2392. * @todo MDL-21120
  2393. */
  2394. function file_modify_html_header($text) {
  2395. // first look for <head> tag
  2396. global $CFG;
  2397. $stylesheetshtml = '';
  2398. /* foreach ($CFG->stylesheets as $stylesheet) {
  2399. //TODO: MDL-21120
  2400. $stylesheetshtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
  2401. }*/
  2402. $ufo = '';
  2403. if (filter_is_enabled('filter/mediaplugin')) {
  2404. // this script is needed by most media filter plugins.
  2405. $attributes = array('type'=>'text/javascript', 'src'=>$CFG->httpswwwroot . '/lib/ufo.js');
  2406. $ufo = html_writer::tag('script', '', $attributes) . "\n";
  2407. }
  2408. preg_match('/\<head\>|\<HEAD\>/', $text, $matches);
  2409. if ($matches) {
  2410. $replacement = '<head>'.$ufo.$stylesheetshtml;
  2411. $text = preg_replace('/\<head\>|\<HEAD\>/', $replacement, $text, 1);
  2412. return $text;
  2413. }
  2414. // if not, look for <html> tag, and stick <head> right after
  2415. preg_match('/\<html\>|\<HTML\>/', $text, $matches);
  2416. if ($matches) {
  2417. // replace <html> tag with <html><head>includes</head>
  2418. $replacement = '<html>'."\n".'<head>'.$ufo.$stylesheetshtml.'</head>';
  2419. $text = preg_replace('/\<html\>|\<HTML\>/', $replacement, $text, 1);
  2420. return $text;
  2421. }
  2422. // if not, look for <body> tag, and stick <head> before body
  2423. preg_match('/\<body\>|\<BODY\>/', $text, $matches);
  2424. if ($matches) {
  2425. $replacement = '<head>'.$ufo.$stylesheetshtml.'</head>'."\n".'<body>';
  2426. $text = preg_replace('/\<body\>|\<BODY\>/', $replacement, $text, 1);
  2427. return $text;
  2428. }
  2429. // if not, just stick a <head> tag at the beginning
  2430. $text = '<head>'.$ufo.$stylesheetshtml.'</head>'."\n".$text;
  2431. return $text;
  2432. }
  2433. /**
  2434. * RESTful cURL class
  2435. *
  2436. * This is a wrapper class for curl, it is quite easy to use:
  2437. * <code>
  2438. * $c = new curl;
  2439. * // enable cache
  2440. * $c = new curl(array('cache'=>true));
  2441. * // enable cookie
  2442. * $c = new curl(array('cookie'=>true));
  2443. * // enable proxy
  2444. * $c = new curl(array('proxy'=>true));
  2445. *
  2446. * // HTTP GET Method
  2447. * $html = $c->get('http://example.com');
  2448. * // HTTP POST Method
  2449. * $html = $c->post('http://example.com/', array('q'=>'words', 'name'=>'moodle'));
  2450. * // HTTP PUT Method
  2451. * $html = $c->put('http://example.com/', array('file'=>'/var/www/test.txt');
  2452. * </code>
  2453. *
  2454. * @package core_files
  2455. * @category files
  2456. * @copyright Dongsheng Cai <dongsheng@moodle.com>
  2457. * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
  2458. */
  2459. class curl {
  2460. /** @var bool Caches http request contents */
  2461. public $cache = false;
  2462. /** @var bool Uses proxy */
  2463. public $proxy = false;
  2464. /** @var string library version */
  2465. public $version = '0.4 dev';
  2466. /** @var array http's response */
  2467. public $response = array();
  2468. /** @var array http header */
  2469. public $header = array();
  2470. /** @var string cURL information */
  2471. public $info;
  2472. /** @var string error */
  2473. public $error;
  2474. /** @var array cURL options */
  2475. private $options;
  2476. /** @var string Proxy host */
  2477. private $proxy_host = '';
  2478. /** @var string Proxy auth */
  2479. private $proxy_auth = '';
  2480. /** @var string Proxy type */
  2481. private $proxy_type = '';
  2482. /** @var bool Debug mode on */
  2483. private $debug = false;
  2484. /** @var bool|string Path to cookie file */
  2485. private $cookie = false;
  2486. /**
  2487. * Constructor
  2488. *
  2489. * @global stdClass $CFG
  2490. * @param array $options
  2491. */
  2492. public function __construct($options = array()){
  2493. global $CFG;
  2494. if (!function_exists('curl_init')) {
  2495. $this->error = 'cURL module must be enabled!';
  2496. trigger_error($this->error, E_USER_ERROR);
  2497. return false;
  2498. }
  2499. // the options of curl should be init here.
  2500. $this->resetopt();
  2501. if (!empty($options['debug'])) {
  2502. $this->debug = true;
  2503. }
  2504. if(!empty($options['cookie'])) {
  2505. if($options['cookie'] === true) {
  2506. $this->cookie = $CFG->dataroot.'/curl_cookie.txt';
  2507. } else {
  2508. $this->cookie = $options['cookie'];
  2509. }
  2510. }
  2511. if (!empty($options['cache'])) {
  2512. if (class_exists('curl_cache')) {
  2513. if (!empty($options['module_cache'])) {
  2514. $this->cache = new curl_cache($options['module_cache']);
  2515. } else {
  2516. $this->cache = new curl_cache('misc');
  2517. }
  2518. }
  2519. }
  2520. if (!empty($CFG->proxyhost)) {
  2521. if (empty($CFG->proxyport)) {
  2522. $this->proxy_host = $CFG->proxyhost;
  2523. } else {
  2524. $this->proxy_host = $CFG->proxyhost.':'.$CFG->proxyport;
  2525. }
  2526. if (!empty($CFG->proxyuser) and !empty($CFG->proxypassword)) {
  2527. $this->proxy_auth = $CFG->proxyuser.':'.$CFG->proxypassword;
  2528. $this->setopt(array(
  2529. 'proxyauth'=> CURLAUTH_BASIC | CURLAUTH_NTLM,
  2530. 'proxyuserpwd'=>$this->proxy_auth));
  2531. }
  2532. if (!empty($CFG->proxytype)) {
  2533. if ($CFG->proxytype == 'SOCKS5') {
  2534. $this->proxy_type = CURLPROXY_SOCKS5;
  2535. } else {
  2536. $this->proxy_type = CURLPROXY_HTTP;
  2537. $this->setopt(array('httpproxytunnel'=>false));
  2538. }
  2539. $this->setopt(array('proxytype'=>$this->proxy_type));
  2540. }
  2541. }
  2542. if (!empty($this->proxy_host)) {
  2543. $this->proxy = array('proxy'=>$this->proxy_host);
  2544. }
  2545. }
  2546. /**
  2547. * Resets the CURL options that have already been set
  2548. */
  2549. public function resetopt(){
  2550. $this->options = array();
  2551. $this->options['CURLOPT_USERAGENT'] = 'MoodleBot/1.0';
  2552. // True to include the header in the output
  2553. $this->options['CURLOPT_HEADER'] = 0;
  2554. // True to Exclude the body from the output
  2555. $this->options['CURLOPT_NOBODY'] = 0;
  2556. // TRUE to follow any "Location: " header that the server
  2557. // sends as part of the HTTP header (note this is recursive,
  2558. // PHP will follow as many "Location: " headers that it is sent,
  2559. // unless CURLOPT_MAXREDIRS is set).
  2560. //$this->options['CURLOPT_FOLLOWLOCATION'] = 1;
  2561. $this->options['CURLOPT_MAXREDIRS'] = 10;
  2562. $this->options['CURLOPT_ENCODING'] = '';
  2563. // TRUE to return the transfer as a string of the return
  2564. // value of curl_exec() instead of outputting it out directly.
  2565. $this->options['CURLOPT_RETURNTRANSFER'] = 1;
  2566. $this->options['CURLOPT_BINARYTRANSFER'] = 0;
  2567. $this->options['CURLOPT_SSL_VERIFYPEER'] = 0;
  2568. $this->options['CURLOPT_SSL_VERIFYHOST'] = 2;
  2569. $this->options['CURLOPT_CONNECTTIMEOUT'] = 30;
  2570. }
  2571. /**
  2572. * Reset Cookie
  2573. */
  2574. public function resetcookie() {
  2575. if (!empty($this->cookie)) {
  2576. if (is_file($this->cookie)) {
  2577. $fp = fopen($this->cookie, 'w');
  2578. if (!empty($fp)) {
  2579. fwrite($fp, '');
  2580. fclose($fp);
  2581. }
  2582. }
  2583. }
  2584. }
  2585. /**
  2586. * Set curl options
  2587. *
  2588. * @param array $options If array is null, this function will
  2589. * reset the options to default value.
  2590. */
  2591. public function setopt($options = array()) {
  2592. if (is_array($options)) {
  2593. foreach($options as $name => $val){
  2594. if (stripos($name, 'CURLOPT_') === false) {
  2595. $name = strtoupper('CURLOPT_'.$name);
  2596. }
  2597. $this->options[$name] = $val;
  2598. }
  2599. }
  2600. }
  2601. /**
  2602. * Reset http method
  2603. */
  2604. public function cleanopt(){
  2605. unset($this->options['CURLOPT_HTTPGET']);
  2606. unset($this->options['CURLOPT_POST']);
  2607. unset($this->options['CURLOPT_POSTFIELDS']);
  2608. unset($this->options['CURLOPT_PUT']);
  2609. unset($this->options['CURLOPT_INFILE']);
  2610. unset($this->options['CURLOPT_INFILESIZE']);
  2611. unset($this->options['CURLOPT_CUSTOMREQUEST']);
  2612. }
  2613. /**
  2614. * Set HTTP Request Header
  2615. *
  2616. * @param array $header
  2617. */
  2618. public function setHeader($header) {
  2619. if (is_array($header)){
  2620. foreach ($header as $v) {
  2621. $this->setHeader($v);
  2622. }
  2623. } else {
  2624. $this->header[] = $header;
  2625. }
  2626. }
  2627. /**
  2628. * Set HTTP Response Header
  2629. *
  2630. */
  2631. public function getResponse(){
  2632. return $this->response;
  2633. }
  2634. /**
  2635. * private callback function
  2636. * Formatting HTTP Response Header
  2637. *
  2638. * @param resource $ch Apparently not used
  2639. * @param string $header
  2640. * @return int The strlen of the header
  2641. */
  2642. private function formatHeader($ch, $header)
  2643. {
  2644. $this->count++;
  2645. if (strlen($header) > 2) {
  2646. list($key, $value) = explode(" ", rtrim($header, "\r\n"), 2);
  2647. $key = rtrim($key, ':');
  2648. if (!empty($this->response[$key])) {
  2649. if (is_array($this->response[$key])){
  2650. $this->response[$key][] = $value;
  2651. } else {
  2652. $tmp = $this->response[$key];
  2653. $this->response[$key] = array();
  2654. $this->response[$key][] = $tmp;
  2655. $this->response[$key][] = $value;
  2656. }
  2657. } else {
  2658. $this->response[$key] = $value;
  2659. }
  2660. }
  2661. return strlen($header);
  2662. }
  2663. /**
  2664. * Set options for individual curl instance
  2665. *
  2666. * @param resource $curl A curl handle
  2667. * @param array $options
  2668. * @return resource The curl handle
  2669. */
  2670. private function apply_opt($curl, $options) {
  2671. // Clean up
  2672. $this->cleanopt();
  2673. // set cookie
  2674. if (!empty($this->cookie) || !empty($options['cookie'])) {
  2675. $this->setopt(array('cookiejar'=>$this->cookie,
  2676. 'cookiefile'=>$this->cookie
  2677. ));
  2678. }
  2679. // set proxy
  2680. if (!empty($this->proxy) || !empty($options['proxy'])) {
  2681. $this->setopt($this->proxy);
  2682. }
  2683. $this->setopt($options);
  2684. // reset before set options
  2685. curl_setopt($curl, CURLOPT_HEADERFUNCTION, array(&$this,'formatHeader'));
  2686. // set headers
  2687. if (empty($this->header)){
  2688. $this->setHeader(array(
  2689. 'User-Agent: MoodleBot/1.0',
  2690. 'Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7',
  2691. 'Connection: keep-alive'
  2692. ));
  2693. }
  2694. curl_setopt($curl, CURLOPT_HTTPHEADER, $this->header);
  2695. if ($this->debug){
  2696. echo '<h1>Options</h1>';
  2697. var_dump($this->options);
  2698. echo '<h1>Header</h1>';
  2699. var_dump($this->header);
  2700. }
  2701. // set options
  2702. foreach($this->options as $name => $val) {
  2703. if (is_string($name)) {
  2704. $name = constant(strtoupper($name));
  2705. }
  2706. curl_setopt($curl, $name, $val);
  2707. }
  2708. return $curl;
  2709. }
  2710. /**
  2711. * Download multiple files in parallel
  2712. *
  2713. * Calls {@link multi()} with specific download headers
  2714. *
  2715. * <code>
  2716. * $c = new curl;
  2717. * $c->download(array(
  2718. * array('url'=>'http://localhost/', 'file'=>fopen('a', 'wb')),
  2719. * array('url'=>'http://localhost/20/', 'file'=>fopen('b', 'wb'))
  2720. * ));
  2721. * </code>
  2722. *
  2723. * @param array $requests An array of files to request
  2724. * @param array $options An array of options to set
  2725. * @return array An array of results
  2726. */
  2727. public function download($requests, $options = array()) {
  2728. $options['CURLOPT_BINARYTRANSFER'] = 1;
  2729. $options['RETURNTRANSFER'] = false;
  2730. return $this->multi($requests, $options);
  2731. }
  2732. /**
  2733. * Mulit HTTP Requests
  2734. * This function could run multi-requests in parallel.
  2735. *
  2736. * @param array $requests An array of files to request
  2737. * @param array $options An array of options to set
  2738. * @return array An array of results
  2739. */
  2740. protected function multi($requests, $options = array()) {
  2741. $count = count($requests);
  2742. $handles = array();
  2743. $results = array();
  2744. $main = curl_multi_init();
  2745. for ($i = 0; $i < $count; $i++) {
  2746. $url = $requests[$i];
  2747. foreach($url as $n=>$v){
  2748. $options[$n] = $url[$n];
  2749. }
  2750. $handles[$i] = curl_init($url['url']);
  2751. $this->apply_opt($handles[$i], $options);
  2752. curl_multi_add_handle($main, $handles[$i]);
  2753. }
  2754. $running = 0;
  2755. do {
  2756. curl_multi_exec($main, $running);
  2757. } while($running > 0);
  2758. for ($i = 0; $i < $count; $i++) {
  2759. if (!empty($options['CURLOPT_RETURNTRANSFER'])) {
  2760. $results[] = true;
  2761. } else {
  2762. $results[] = curl_multi_getcontent($handles[$i]);
  2763. }
  2764. curl_multi_remove_handle($main, $handles[$i]);
  2765. }
  2766. curl_multi_close($main);
  2767. return $results;
  2768. }
  2769. /**
  2770. * Single HTTP Request
  2771. *
  2772. * @param string $url The URL to request
  2773. * @param array $options
  2774. * @return bool
  2775. */
  2776. protected function request($url, $options = array()){
  2777. // create curl instance
  2778. $curl = curl_init($url);
  2779. $options['url'] = $url;
  2780. $this->apply_opt($curl, $options);
  2781. if ($this->cache && $ret = $this->cache->get($this->options)) {
  2782. return $ret;
  2783. } else {
  2784. $ret = curl_exec($curl);
  2785. if ($this->cache) {
  2786. $this->cache->set($this->options, $ret);
  2787. }
  2788. }
  2789. $this->info = curl_getinfo($curl);
  2790. $this->error = curl_error($curl);
  2791. if ($this->debug){
  2792. echo '<h1>Return Data</h1>';
  2793. var_dump($ret);
  2794. echo '<h1>Info</h1>';
  2795. var_dump($this->info);
  2796. echo '<h1>Error</h1>';
  2797. var_dump($this->error);
  2798. }
  2799. curl_close($curl);
  2800. if (empty($this->error)){
  2801. return $ret;
  2802. } else {
  2803. return $this->error;
  2804. // exception is not ajax friendly
  2805. //throw new moodle_exception($this->error, 'curl');
  2806. }
  2807. }
  2808. /**
  2809. * HTTP HEAD method
  2810. *
  2811. * @see request()
  2812. *
  2813. * @param string $url
  2814. * @param array $options
  2815. * @return bool
  2816. */
  2817. public function head($url, $options = array()){
  2818. $options['CURLOPT_HTTPGET'] = 0;
  2819. $options['CURLOPT_HEADER'] = 1;
  2820. $options['CURLOPT_NOBODY'] = 1;
  2821. return $this->request($url, $options);
  2822. }
  2823. /**
  2824. * HTTP POST method
  2825. *
  2826. * @param string $url
  2827. * @param array|string $params
  2828. * @param array $options
  2829. * @return bool
  2830. */
  2831. public function post($url, $params = '', $options = array()){
  2832. $options['CURLOPT_POST'] = 1;
  2833. if (is_array($params)) {
  2834. $this->_tmp_file_post_params = array();
  2835. foreach ($params as $key => $value) {
  2836. if ($value instanceof stored_file) {
  2837. $value->add_to_curl_request($this, $key);
  2838. } else {
  2839. $this->_tmp_file_post_params[$key] = $value;
  2840. }
  2841. }
  2842. $options['CURLOPT_POSTFIELDS'] = $this->_tmp_file_post_params;
  2843. unset($this->_tmp_file_post_params);
  2844. } else {
  2845. // $params is the raw post data
  2846. $options['CURLOPT_POSTFIELDS'] = $params;
  2847. }
  2848. return $this->request($url, $options);
  2849. }
  2850. /**
  2851. * HTTP GET method
  2852. *
  2853. * @param string $url
  2854. * @param array $params
  2855. * @param array $options
  2856. * @return bool
  2857. */
  2858. public function get($url, $params = array(), $options = array()){
  2859. $options['CURLOPT_HTTPGET'] = 1;
  2860. if (!empty($params)){
  2861. $url .= (stripos($url, '?') !== false) ? '&' : '?';
  2862. $url .= http_build_query($params, '', '&');
  2863. }
  2864. return $this->request($url, $options);
  2865. }
  2866. /**
  2867. * HTTP PUT method
  2868. *
  2869. * @param string $url
  2870. * @param array $params
  2871. * @param array $options
  2872. * @return bool
  2873. */
  2874. public function put($url, $params = array(), $options = array()){
  2875. $file = $params['file'];
  2876. if (!is_file($file)){
  2877. return null;
  2878. }
  2879. $fp = fopen($file, 'r');
  2880. $size = filesize($file);
  2881. $options['CURLOPT_PUT'] = 1;
  2882. $options['CURLOPT_INFILESIZE'] = $size;
  2883. $options['CURLOPT_INFILE'] = $fp;
  2884. if (!isset($this->options['CURLOPT_USERPWD'])){
  2885. $this->setopt(array('CURLOPT_USERPWD'=>'anonymous: noreply@moodle.org'));
  2886. }
  2887. $ret = $this->request($url, $options);
  2888. fclose($fp);
  2889. return $ret;
  2890. }
  2891. /**
  2892. * HTTP DELETE method
  2893. *
  2894. * @param string $url
  2895. * @param array $param
  2896. * @param array $options
  2897. * @return bool
  2898. */
  2899. public function delete($url, $param = array(), $options = array()){
  2900. $options['CURLOPT_CUSTOMREQUEST'] = 'DELETE';
  2901. if (!isset($options['CURLOPT_USERPWD'])) {
  2902. $options['CURLOPT_USERPWD'] = 'anonymous: noreply@moodle.org';
  2903. }
  2904. $ret = $this->request($url, $options);
  2905. return $ret;
  2906. }
  2907. /**
  2908. * HTTP TRACE method
  2909. *
  2910. * @param string $url
  2911. * @param array $options
  2912. * @return bool
  2913. */
  2914. public function trace($url, $options = array()){
  2915. $options['CURLOPT_CUSTOMREQUEST'] = 'TRACE';
  2916. $ret = $this->request($url, $options);
  2917. return $ret;
  2918. }
  2919. /**
  2920. * HTTP OPTIONS method
  2921. *
  2922. * @param string $url
  2923. * @param array $options
  2924. * @return bool
  2925. */
  2926. public function options($url, $options = array()){
  2927. $options['CURLOPT_CUSTOMREQUEST'] = 'OPTIONS';
  2928. $ret = $this->request($url, $options);
  2929. return $ret;
  2930. }
  2931. /**
  2932. * Get curl information
  2933. *
  2934. * @return string
  2935. */
  2936. public function get_info() {
  2937. return $this->info;
  2938. }
  2939. }
  2940. /**
  2941. * This class is used by cURL class, use case:
  2942. *
  2943. * <code>
  2944. * $CFG->repositorycacheexpire = 120;
  2945. * $CFG->curlcache = 120;
  2946. *
  2947. * $c = new curl(array('cache'=>true), 'module_cache'=>'repository');
  2948. * $ret = $c->get('http://www.google.com');
  2949. * </code>
  2950. *
  2951. * @package core_files
  2952. * @copyright Dongsheng Cai <dongsheng@moodle.com>
  2953. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  2954. */
  2955. class curl_cache {
  2956. /** @var string Path to cache directory */
  2957. public $dir = '';
  2958. /**
  2959. * Constructor
  2960. *
  2961. * @global stdClass $CFG
  2962. * @param string $module which module is using curl_cache
  2963. */
  2964. public function __construct($module = 'repository') {
  2965. global $CFG;
  2966. if (!empty($module)) {
  2967. $this->dir = $CFG->cachedir.'/'.$module.'/';
  2968. } else {
  2969. $this->dir = $CFG->cachedir.'/misc/';
  2970. }
  2971. if (!file_exists($this->dir)) {
  2972. mkdir($this->dir, $CFG->directorypermissions, true);
  2973. }
  2974. if ($module == 'repository') {
  2975. if (empty($CFG->repositorycacheexpire)) {
  2976. $CFG->repositorycacheexpire = 120;
  2977. }
  2978. $this->ttl = $CFG->repositorycacheexpire;
  2979. } else {
  2980. if (empty($CFG->curlcache)) {
  2981. $CFG->curlcache = 120;
  2982. }
  2983. $this->ttl = $CFG->curlcache;
  2984. }
  2985. }
  2986. /**
  2987. * Get cached value
  2988. *
  2989. * @global stdClass $CFG
  2990. * @global stdClass $USER
  2991. * @param mixed $param
  2992. * @return bool|string
  2993. */
  2994. public function get($param) {
  2995. global $CFG, $USER;
  2996. $this->cleanup($this->ttl);
  2997. $filename = 'u'.$USER->id.'_'.md5(serialize($param));
  2998. if(file_exists($this->dir.$filename)) {
  2999. $lasttime = filemtime($this->dir.$filename);
  3000. if (time()-$lasttime > $this->ttl) {
  3001. return false;
  3002. } else {
  3003. $fp = fopen($this->dir.$filename, 'r');
  3004. $size = filesize($this->dir.$filename);
  3005. $content = fread($fp, $size);
  3006. return unserialize($content);
  3007. }
  3008. }
  3009. return false;
  3010. }
  3011. /**
  3012. * Set cache value
  3013. *
  3014. * @global object $CFG
  3015. * @global object $USER
  3016. * @param mixed $param
  3017. * @param mixed $val
  3018. */
  3019. public function set($param, $val) {
  3020. global $CFG, $USER;
  3021. $filename = 'u'.$USER->id.'_'.md5(serialize($param));
  3022. $fp = fopen($this->dir.$filename, 'w');
  3023. fwrite($fp, serialize($val));
  3024. fclose($fp);
  3025. }
  3026. /**
  3027. * Remove cache files
  3028. *
  3029. * @param int $expire The number of seconds before expiry
  3030. */
  3031. public function cleanup($expire) {
  3032. if ($dir = opendir($this->dir)) {
  3033. while (false !== ($file = readdir($dir))) {
  3034. if(!is_dir($file) && $file != '.' && $file != '..') {
  3035. $lasttime = @filemtime($this->dir.$file);
  3036. if (time() - $lasttime > $expire) {
  3037. @unlink($this->dir.$file);
  3038. }
  3039. }
  3040. }
  3041. closedir($dir);
  3042. }
  3043. }
  3044. /**
  3045. * delete current user's cache file
  3046. *
  3047. * @global object $CFG
  3048. * @global object $USER
  3049. */
  3050. public function refresh() {
  3051. global $CFG, $USER;
  3052. if ($dir = opendir($this->dir)) {
  3053. while (false !== ($file = readdir($dir))) {
  3054. if (!is_dir($file) && $file != '.' && $file != '..') {
  3055. if (strpos($file, 'u'.$USER->id.'_') !== false) {
  3056. @unlink($this->dir.$file);
  3057. }
  3058. }
  3059. }
  3060. }
  3061. }
  3062. }
  3063. /**
  3064. * This function delegates file serving to individual plugins
  3065. *
  3066. * @param string $relativepath
  3067. * @param bool $forcedownload
  3068. * @param null|string $preview the preview mode, defaults to serving the original file
  3069. * @todo MDL-31088 file serving improments
  3070. */
  3071. function file_pluginfile($relativepath, $forcedownload, $preview = null) {
  3072. global $DB, $CFG, $USER;
  3073. // relative path must start with '/'
  3074. if (!$relativepath) {
  3075. print_error('invalidargorconf');
  3076. } else if ($relativepath[0] != '/') {
  3077. print_error('pathdoesnotstartslash');
  3078. }
  3079. // extract relative path components
  3080. $args = explode('/', ltrim($relativepath, '/'));
  3081. if (count($args) < 3) { // always at least context, component and filearea
  3082. print_error('invalidarguments');
  3083. }
  3084. $contextid = (int)array_shift($args);
  3085. $component = clean_param(array_shift($args), PARAM_COMPONENT);
  3086. $filearea = clean_param(array_shift($args), PARAM_AREA);
  3087. list($context, $course, $cm) = get_context_info_array($contextid);
  3088. $fs = get_file_storage();
  3089. // ========================================================================================================================
  3090. if ($component === 'blog') {
  3091. // Blog file serving
  3092. if ($context->contextlevel != CONTEXT_SYSTEM) {
  3093. send_file_not_found();
  3094. }
  3095. if ($filearea !== 'attachment' and $filearea !== 'post') {
  3096. send_file_not_found();
  3097. }
  3098. if (empty($CFG->bloglevel)) {
  3099. print_error('siteblogdisable', 'blog');
  3100. }
  3101. $entryid = (int)array_shift($args);
  3102. if (!$entry = $DB->get_record('post', array('module'=>'blog', 'id'=>$entryid))) {
  3103. send_file_not_found();
  3104. }
  3105. if ($CFG->bloglevel < BLOG_GLOBAL_LEVEL) {
  3106. require_login();
  3107. if (isguestuser()) {
  3108. print_error('noguest');
  3109. }
  3110. if ($CFG->bloglevel == BLOG_USER_LEVEL) {
  3111. if ($USER->id != $entry->userid) {
  3112. send_file_not_found();
  3113. }
  3114. }
  3115. }
  3116. if ('publishstate' === 'public') {
  3117. if ($CFG->forcelogin) {
  3118. require_login();
  3119. }
  3120. } else if ('publishstate' === 'site') {
  3121. require_login();
  3122. //ok
  3123. } else if ('publishstate' === 'draft') {
  3124. require_login();
  3125. if ($USER->id != $entry->userid) {
  3126. send_file_not_found();
  3127. }
  3128. }
  3129. $filename = array_pop($args);
  3130. $filepath = $args ? '/'.implode('/', $args).'/' : '/';
  3131. if (!$file = $fs->get_file($context->id, $component, $filearea, $entryid, $filepath, $filename) or $file->is_directory()) {
  3132. send_file_not_found();
  3133. }
  3134. send_stored_file($file, 10*60, 0, true, array('preview' => $preview)); // download MUST be forced - security!
  3135. // ========================================================================================================================
  3136. } else if ($component === 'grade') {
  3137. if (($filearea === 'outcome' or $filearea === 'scale') and $context->contextlevel == CONTEXT_SYSTEM) {
  3138. // Global gradebook files
  3139. if ($CFG->forcelogin) {
  3140. require_login();
  3141. }
  3142. $fullpath = "/$context->id/$component/$filearea/".implode('/', $args);
  3143. if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
  3144. send_file_not_found();
  3145. }
  3146. session_get_instance()->write_close(); // unlock session during fileserving
  3147. send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
  3148. } else if ($filearea === 'feedback' and $context->contextlevel == CONTEXT_COURSE) {
  3149. //TODO: nobody implemented this yet in grade edit form!!
  3150. send_file_not_found();
  3151. if ($CFG->forcelogin || $course->id != SITEID) {
  3152. require_login($course);
  3153. }
  3154. $fullpath = "/$context->id/$component/$filearea/".implode('/', $args);
  3155. if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
  3156. send_file_not_found();
  3157. }
  3158. session_get_instance()->write_close(); // unlock session during fileserving
  3159. send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
  3160. } else {
  3161. send_file_not_found();
  3162. }
  3163. // ========================================================================================================================
  3164. } else if ($component === 'tag') {
  3165. if ($filearea === 'description' and $context->contextlevel == CONTEXT_SYSTEM) {
  3166. // All tag descriptions are going to be public but we still need to respect forcelogin
  3167. if ($CFG->forcelogin) {
  3168. require_login();
  3169. }
  3170. $fullpath = "/$context->id/tag/description/".implode('/', $args);
  3171. if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
  3172. send_file_not_found();
  3173. }
  3174. session_get_instance()->write_close(); // unlock session during fileserving
  3175. send_stored_file($file, 60*60, 0, true, array('preview' => $preview));
  3176. } else {
  3177. send_file_not_found();
  3178. }
  3179. // ========================================================================================================================
  3180. } else if ($component === 'calendar') {
  3181. if ($filearea === 'event_description' and $context->contextlevel == CONTEXT_SYSTEM) {
  3182. // All events here are public the one requirement is that we respect forcelogin
  3183. if ($CFG->forcelogin) {
  3184. require_login();
  3185. }
  3186. // Get the event if from the args array
  3187. $eventid = array_shift($args);
  3188. // Load the event from the database
  3189. if (!$event = $DB->get_record('event', array('id'=>(int)$eventid, 'eventtype'=>'site'))) {
  3190. send_file_not_found();
  3191. }
  3192. // Check that we got an event and that it's userid is that of the user
  3193. // Get the file and serve if successful
  3194. $filename = array_pop($args);
  3195. $filepath = $args ? '/'.implode('/', $args).'/' : '/';
  3196. if (!$file = $fs->get_file($context->id, $component, $filearea, $eventid, $filepath, $filename) or $file->is_directory()) {
  3197. send_file_not_found();
  3198. }
  3199. session_get_instance()->write_close(); // unlock session during fileserving
  3200. send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
  3201. } else if ($filearea === 'event_description' and $context->contextlevel == CONTEXT_USER) {
  3202. // Must be logged in, if they are not then they obviously can't be this user
  3203. require_login();
  3204. // Don't want guests here, potentially saves a DB call
  3205. if (isguestuser()) {
  3206. send_file_not_found();
  3207. }
  3208. // Get the event if from the args array
  3209. $eventid = array_shift($args);
  3210. // Load the event from the database - user id must match
  3211. if (!$event = $DB->get_record('event', array('id'=>(int)$eventid, 'userid'=>$USER->id, 'eventtype'=>'user'))) {
  3212. send_file_not_found();
  3213. }
  3214. // Get the file and serve if successful
  3215. $filename = array_pop($args);
  3216. $filepath = $args ? '/'.implode('/', $args).'/' : '/';
  3217. if (!$file = $fs->get_file($context->id, $component, $filearea, $eventid, $filepath, $filename) or $file->is_directory()) {
  3218. send_file_not_found();
  3219. }
  3220. session_get_instance()->write_close(); // unlock session during fileserving
  3221. send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
  3222. } else if ($filearea === 'event_description' and $context->contextlevel == CONTEXT_COURSE) {
  3223. // Respect forcelogin and require login unless this is the site.... it probably
  3224. // should NEVER be the site
  3225. if ($CFG->forcelogin || $course->id != SITEID) {
  3226. require_login($course);
  3227. }
  3228. // Must be able to at least view the course
  3229. if (!is_enrolled($context) and !is_viewing($context)) {
  3230. //TODO: hmm, do we really want to block guests here?
  3231. send_file_not_found();
  3232. }
  3233. // Get the event id
  3234. $eventid = array_shift($args);
  3235. // Load the event from the database we need to check whether it is
  3236. // a) valid course event
  3237. // b) a group event
  3238. // Group events use the course context (there is no group context)
  3239. if (!$event = $DB->get_record('event', array('id'=>(int)$eventid, 'courseid'=>$course->id))) {
  3240. send_file_not_found();
  3241. }
  3242. // If its a group event require either membership of view all groups capability
  3243. if ($event->eventtype === 'group') {
  3244. if (!has_capability('moodle/site:accessallgroups', $context) && !groups_is_member($event->groupid, $USER->id)) {
  3245. send_file_not_found();
  3246. }
  3247. } else if ($event->eventtype === 'course') {
  3248. //ok
  3249. } else {
  3250. // some other type
  3251. send_file_not_found();
  3252. }
  3253. // If we get this far we can serve the file
  3254. $filename = array_pop($args);
  3255. $filepath = $args ? '/'.implode('/', $args).'/' : '/';
  3256. if (!$file = $fs->get_file($context->id, $component, $filearea, $eventid, $filepath, $filename) or $file->is_directory()) {
  3257. send_file_not_found();
  3258. }
  3259. session_get_instance()->write_close(); // unlock session during fileserving
  3260. send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
  3261. } else {
  3262. send_file_not_found();
  3263. }
  3264. // ========================================================================================================================
  3265. } else if ($component === 'user') {
  3266. if ($filearea === 'icon' and $context->contextlevel == CONTEXT_USER) {
  3267. if (count($args) == 1) {
  3268. $themename = theme_config::DEFAULT_THEME;
  3269. $filename = array_shift($args);
  3270. } else {
  3271. $themename = array_shift($args);
  3272. $filename = array_shift($args);
  3273. }
  3274. // fix file name automatically
  3275. if ($filename !== 'f1' and $filename !== 'f2' and $filename !== 'f3') {
  3276. $filename = 'f1';
  3277. }
  3278. if ((!empty($CFG->forcelogin) and !isloggedin()) ||
  3279. (!empty($CFG->forceloginforprofileimage) && (!isloggedin() || isguestuser()))) {
  3280. // protect images if login required and not logged in;
  3281. // also if login is required for profile images and is not logged in or guest
  3282. // do not use require_login() because it is expensive and not suitable here anyway
  3283. $theme = theme_config::load($themename);
  3284. redirect($theme->pix_url('u/'.$filename, 'moodle')); // intentionally not cached
  3285. }
  3286. if (!$file = $fs->get_file($context->id, 'user', 'icon', 0, '/', $filename.'.png')) {
  3287. if (!$file = $fs->get_file($context->id, 'user', 'icon', 0, '/', $filename.'.jpg')) {
  3288. if ($filename === 'f3') {
  3289. // f3 512x512px was introduced in 2.3, there might be only the smaller version.
  3290. if (!$file = $fs->get_file($context->id, 'user', 'icon', 0, '/', 'f1.png')) {
  3291. $file = $fs->get_file($context->id, 'user', 'icon', 0, '/', 'f1.jpg');
  3292. }
  3293. }
  3294. }
  3295. }
  3296. if (!$file) {
  3297. // bad reference - try to prevent future retries as hard as possible!
  3298. if ($user = $DB->get_record('user', array('id'=>$context->instanceid), 'id, picture')) {
  3299. if ($user->picture > 0) {
  3300. $DB->set_field('user', 'picture', 0, array('id'=>$user->id));
  3301. }
  3302. }
  3303. // no redirect here because it is not cached
  3304. $theme = theme_config::load($themename);
  3305. $imagefile = $theme->resolve_image_location('u/'.$filename, 'moodle');
  3306. send_file($imagefile, basename($imagefile), 60*60*24*14);
  3307. }
  3308. send_stored_file($file, 60*60*24*365, 0, false, array('preview' => $preview)); // enable long caching, there are many images on each page
  3309. } else if ($filearea === 'private' and $context->contextlevel == CONTEXT_USER) {
  3310. require_login();
  3311. if (isguestuser()) {
  3312. send_file_not_found();
  3313. }
  3314. if ($USER->id !== $context->instanceid) {
  3315. send_file_not_found();
  3316. }
  3317. $filename = array_pop($args);
  3318. $filepath = $args ? '/'.implode('/', $args).'/' : '/';
  3319. if (!$file = $fs->get_file($context->id, $component, $filearea, 0, $filepath, $filename) or $file->is_directory()) {
  3320. send_file_not_found();
  3321. }
  3322. session_get_instance()->write_close(); // unlock session during fileserving
  3323. send_stored_file($file, 0, 0, true, array('preview' => $preview)); // must force download - security!
  3324. } else if ($filearea === 'profile' and $context->contextlevel == CONTEXT_USER) {
  3325. if ($CFG->forcelogin) {
  3326. require_login();
  3327. }
  3328. $userid = $context->instanceid;
  3329. if ($USER->id == $userid) {
  3330. // always can access own
  3331. } else if (!empty($CFG->forceloginforprofiles)) {
  3332. require_login();
  3333. if (isguestuser()) {
  3334. send_file_not_found();
  3335. }
  3336. // we allow access to site profile of all course contacts (usually teachers)
  3337. if (!has_coursecontact_role($userid) && !has_capability('moodle/user:viewdetails', $context)) {
  3338. send_file_not_found();
  3339. }
  3340. $canview = false;
  3341. if (has_capability('moodle/user:viewdetails', $context)) {
  3342. $canview = true;
  3343. } else {
  3344. $courses = enrol_get_my_courses();
  3345. }
  3346. while (!$canview && count($courses) > 0) {
  3347. $course = array_shift($courses);
  3348. if (has_capability('moodle/user:viewdetails', get_context_instance(CONTEXT_COURSE, $course->id))) {
  3349. $canview = true;
  3350. }
  3351. }
  3352. }
  3353. $filename = array_pop($args);
  3354. $filepath = $args ? '/'.implode('/', $args).'/' : '/';
  3355. if (!$file = $fs->get_file($context->id, $component, $filearea, 0, $filepath, $filename) or $file->is_directory()) {
  3356. send_file_not_found();
  3357. }
  3358. session_get_instance()->write_close(); // unlock session during fileserving
  3359. send_stored_file($file, 0, 0, true, array('preview' => $preview)); // must force download - security!
  3360. } else if ($filearea === 'profile' and $context->contextlevel == CONTEXT_COURSE) {
  3361. $userid = (int)array_shift($args);
  3362. $usercontext = get_context_instance(CONTEXT_USER, $userid);
  3363. if ($CFG->forcelogin) {
  3364. require_login();
  3365. }
  3366. if (!empty($CFG->forceloginforprofiles)) {
  3367. require_login();
  3368. if (isguestuser()) {
  3369. print_error('noguest');
  3370. }
  3371. //TODO: review this logic of user profile access prevention
  3372. if (!has_coursecontact_role($userid) and !has_capability('moodle/user:viewdetails', $usercontext)) {
  3373. print_error('usernotavailable');
  3374. }
  3375. if (!has_capability('moodle/user:viewdetails', $context) && !has_capability('moodle/user:viewdetails', $usercontext)) {
  3376. print_error('cannotviewprofile');
  3377. }
  3378. if (!is_enrolled($context, $userid)) {
  3379. print_error('notenrolledprofile');
  3380. }
  3381. if (groups_get_course_groupmode($course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
  3382. print_error('groupnotamember');
  3383. }
  3384. }
  3385. $filename = array_pop($args);
  3386. $filepath = $args ? '/'.implode('/', $args).'/' : '/';
  3387. if (!$file = $fs->get_file($usercontext->id, 'user', 'profile', 0, $filepath, $filename) or $file->is_directory()) {
  3388. send_file_not_found();
  3389. }
  3390. session_get_instance()->write_close(); // unlock session during fileserving
  3391. send_stored_file($file, 0, 0, true, array('preview' => $preview)); // must force download - security!
  3392. } else if ($filearea === 'backup' and $context->contextlevel == CONTEXT_USER) {
  3393. require_login();
  3394. if (isguestuser()) {
  3395. send_file_not_found();
  3396. }
  3397. $userid = $context->instanceid;
  3398. if ($USER->id != $userid) {
  3399. send_file_not_found();
  3400. }
  3401. $filename = array_pop($args);
  3402. $filepath = $args ? '/'.implode('/', $args).'/' : '/';
  3403. if (!$file = $fs->get_file($context->id, 'user', 'backup', 0, $filepath, $filename) or $file->is_directory()) {
  3404. send_file_not_found();
  3405. }
  3406. session_get_instance()->write_close(); // unlock session during fileserving
  3407. send_stored_file($file, 0, 0, true, array('preview' => $preview)); // must force download - security!
  3408. } else {
  3409. send_file_not_found();
  3410. }
  3411. // ========================================================================================================================
  3412. } else if ($component === 'coursecat') {
  3413. if ($context->contextlevel != CONTEXT_COURSECAT) {
  3414. send_file_not_found();
  3415. }
  3416. if ($filearea === 'description') {
  3417. if ($CFG->forcelogin) {
  3418. // no login necessary - unless login forced everywhere
  3419. require_login();
  3420. }
  3421. $filename = array_pop($args);
  3422. $filepath = $args ? '/'.implode('/', $args).'/' : '/';
  3423. if (!$file = $fs->get_file($context->id, 'coursecat', 'description', 0, $filepath, $filename) or $file->is_directory()) {
  3424. send_file_not_found();
  3425. }
  3426. session_get_instance()->write_close(); // unlock session during fileserving
  3427. send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
  3428. } else {
  3429. send_file_not_found();
  3430. }
  3431. // ========================================================================================================================
  3432. } else if ($component === 'course') {
  3433. if ($context->contextlevel != CONTEXT_COURSE) {
  3434. send_file_not_found();
  3435. }
  3436. if ($filearea === 'summary') {
  3437. if ($CFG->forcelogin) {
  3438. require_login();
  3439. }
  3440. $filename = array_pop($args);
  3441. $filepath = $args ? '/'.implode('/', $args).'/' : '/';
  3442. if (!$file = $fs->get_file($context->id, 'course', 'summary', 0, $filepath, $filename) or $file->is_directory()) {
  3443. send_file_not_found();
  3444. }
  3445. session_get_instance()->write_close(); // unlock session during fileserving
  3446. send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
  3447. } else if ($filearea === 'section') {
  3448. if ($CFG->forcelogin) {
  3449. require_login($course);
  3450. } else if ($course->id != SITEID) {
  3451. require_login($course);
  3452. }
  3453. $sectionid = (int)array_shift($args);
  3454. if (!$section = $DB->get_record('course_sections', array('id'=>$sectionid, 'course'=>$course->id))) {
  3455. send_file_not_found();
  3456. }
  3457. if ($course->numsections < $section->section) {
  3458. if (!has_capability('moodle/course:update', $context)) {
  3459. // block access to unavailable sections if can not edit course
  3460. send_file_not_found();
  3461. }
  3462. }
  3463. $filename = array_pop($args);
  3464. $filepath = $args ? '/'.implode('/', $args).'/' : '/';
  3465. if (!$file = $fs->get_file($context->id, 'course', 'section', $sectionid, $filepath, $filename) or $file->is_directory()) {
  3466. send_file_not_found();
  3467. }
  3468. session_get_instance()->write_close(); // unlock session during fileserving
  3469. send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
  3470. } else {
  3471. send_file_not_found();
  3472. }
  3473. } else if ($component === 'group') {
  3474. if ($context->contextlevel != CONTEXT_COURSE) {
  3475. send_file_not_found();
  3476. }
  3477. require_course_login($course, true, null, false);
  3478. $groupid = (int)array_shift($args);
  3479. $group = $DB->get_record('groups', array('id'=>$groupid, 'courseid'=>$course->id), '*', MUST_EXIST);
  3480. if (($course->groupmodeforce and $course->groupmode == SEPARATEGROUPS) and !has_capability('moodle/site:accessallgroups', $context) and !groups_is_member($group->id, $USER->id)) {
  3481. // do not allow access to separate group info if not member or teacher
  3482. send_file_not_found();
  3483. }
  3484. if ($filearea === 'description') {
  3485. require_login($course);
  3486. $filename = array_pop($args);
  3487. $filepath = $args ? '/'.implode('/', $args).'/' : '/';
  3488. if (!$file = $fs->get_file($context->id, 'group', 'description', $group->id, $filepath, $filename) or $file->is_directory()) {
  3489. send_file_not_found();
  3490. }
  3491. session_get_instance()->write_close(); // unlock session during fileserving
  3492. send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
  3493. } else if ($filearea === 'icon') {
  3494. $filename = array_pop($args);
  3495. if ($filename !== 'f1' and $filename !== 'f2') {
  3496. send_file_not_found();
  3497. }
  3498. if (!$file = $fs->get_file($context->id, 'group', 'icon', $group->id, '/', $filename.'.png')) {
  3499. if (!$file = $fs->get_file($context->id, 'group', 'icon', $group->id, '/', $filename.'.jpg')) {
  3500. send_file_not_found();
  3501. }
  3502. }
  3503. session_get_instance()->write_close(); // unlock session during fileserving
  3504. send_stored_file($file, 60*60, 0, false, array('preview' => $preview));
  3505. } else {
  3506. send_file_not_found();
  3507. }
  3508. } else if ($component === 'grouping') {
  3509. if ($context->contextlevel != CONTEXT_COURSE) {
  3510. send_file_not_found();
  3511. }
  3512. require_login($course);
  3513. $groupingid = (int)array_shift($args);
  3514. // note: everybody has access to grouping desc images for now
  3515. if ($filearea === 'description') {
  3516. $filename = array_pop($args);
  3517. $filepath = $args ? '/'.implode('/', $args).'/' : '/';
  3518. if (!$file = $fs->get_file($context->id, 'grouping', 'description', $groupingid, $filepath, $filename) or $file->is_directory()) {
  3519. send_file_not_found();
  3520. }
  3521. session_get_instance()->write_close(); // unlock session during fileserving
  3522. send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
  3523. } else {
  3524. send_file_not_found();
  3525. }
  3526. // ========================================================================================================================
  3527. } else if ($component === 'backup') {
  3528. if ($filearea === 'course' and $context->contextlevel == CONTEXT_COURSE) {
  3529. require_login($course);
  3530. require_capability('moodle/backup:downloadfile', $context);
  3531. $filename = array_pop($args);
  3532. $filepath = $args ? '/'.implode('/', $args).'/' : '/';
  3533. if (!$file = $fs->get_file($context->id, 'backup', 'course', 0, $filepath, $filename) or $file->is_directory()) {
  3534. send_file_not_found();
  3535. }
  3536. session_get_instance()->write_close(); // unlock session during fileserving
  3537. send_stored_file($file, 0, 0, $forcedownload, array('preview' => $preview));
  3538. } else if ($filearea === 'section' and $context->contextlevel == CONTEXT_COURSE) {
  3539. require_login($course);
  3540. require_capability('moodle/backup:downloadfile', $context);
  3541. $sectionid = (int)array_shift($args);
  3542. $filename = array_pop($args);
  3543. $filepath = $args ? '/'.implode('/', $args).'/' : '/';
  3544. if (!$file = $fs->get_file($context->id, 'backup', 'section', $sectionid, $filepath, $filename) or $file->is_directory()) {
  3545. send_file_not_found();
  3546. }
  3547. session_get_instance()->write_close();
  3548. send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
  3549. } else if ($filearea === 'activity' and $context->contextlevel == CONTEXT_MODULE) {
  3550. require_login($course, false, $cm);
  3551. require_capability('moodle/backup:downloadfile', $context);
  3552. $filename = array_pop($args);
  3553. $filepath = $args ? '/'.implode('/', $args).'/' : '/';
  3554. if (!$file = $fs->get_file($context->id, 'backup', 'activity', 0, $filepath, $filename) or $file->is_directory()) {
  3555. send_file_not_found();
  3556. }
  3557. session_get_instance()->write_close();
  3558. send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
  3559. } else if ($filearea === 'automated' and $context->contextlevel == CONTEXT_COURSE) {
  3560. // Backup files that were generated by the automated backup systems.
  3561. require_login($course);
  3562. require_capability('moodle/site:config', $context);
  3563. $filename = array_pop($args);
  3564. $filepath = $args ? '/'.implode('/', $args).'/' : '/';
  3565. if (!$file = $fs->get_file($context->id, 'backup', 'automated', 0, $filepath, $filename) or $file->is_directory()) {
  3566. send_file_not_found();
  3567. }
  3568. session_get_instance()->write_close(); // unlock session during fileserving
  3569. send_stored_file($file, 0, 0, $forcedownload, array('preview' => $preview));
  3570. } else {
  3571. send_file_not_found();
  3572. }
  3573. // ========================================================================================================================
  3574. } else if ($component === 'question') {
  3575. require_once($CFG->libdir . '/questionlib.php');
  3576. question_pluginfile($course, $context, 'question', $filearea, $args, $forcedownload);
  3577. send_file_not_found();
  3578. // ========================================================================================================================
  3579. } else if ($component === 'grading') {
  3580. if ($filearea === 'description') {
  3581. // files embedded into the form definition description
  3582. if ($context->contextlevel == CONTEXT_SYSTEM) {
  3583. require_login();
  3584. } else if ($context->contextlevel >= CONTEXT_COURSE) {
  3585. require_login($course, false, $cm);
  3586. } else {
  3587. send_file_not_found();
  3588. }
  3589. $formid = (int)array_shift($args);
  3590. $sql = "SELECT ga.id
  3591. FROM {grading_areas} ga
  3592. JOIN {grading_definitions} gd ON (gd.areaid = ga.id)
  3593. WHERE gd.id = ? AND ga.contextid = ?";
  3594. $areaid = $DB->get_field_sql($sql, array($formid, $context->id), IGNORE_MISSING);
  3595. if (!$areaid) {
  3596. send_file_not_found();
  3597. }
  3598. $fullpath = "/$context->id/$component/$filearea/$formid/".implode('/', $args);
  3599. if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
  3600. send_file_not_found();
  3601. }
  3602. session_get_instance()->write_close(); // unlock session during fileserving
  3603. send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
  3604. }
  3605. // ========================================================================================================================
  3606. } else if (strpos($component, 'mod_') === 0) {
  3607. $modname = substr($component, 4);
  3608. if (!file_exists("$CFG->dirroot/mod/$modname/lib.php")) {
  3609. send_file_not_found();
  3610. }
  3611. require_once("$CFG->dirroot/mod/$modname/lib.php");
  3612. if ($context->contextlevel == CONTEXT_MODULE) {
  3613. if ($cm->modname !== $modname) {
  3614. // somebody tries to gain illegal access, cm type must match the component!
  3615. send_file_not_found();
  3616. }
  3617. }
  3618. if ($filearea === 'intro') {
  3619. if (!plugin_supports('mod', $modname, FEATURE_MOD_INTRO, true)) {
  3620. send_file_not_found();
  3621. }
  3622. require_course_login($course, true, $cm);
  3623. // all users may access it
  3624. $filename = array_pop($args);
  3625. $filepath = $args ? '/'.implode('/', $args).'/' : '/';
  3626. if (!$file = $fs->get_file($context->id, 'mod_'.$modname, 'intro', 0, $filepath, $filename) or $file->is_directory()) {
  3627. send_file_not_found();
  3628. }
  3629. $lifetime = isset($CFG->filelifetime) ? $CFG->filelifetime : 86400;
  3630. // finally send the file
  3631. send_stored_file($file, $lifetime, 0, false, array('preview' => $preview));
  3632. }
  3633. $filefunction = $component.'_pluginfile';
  3634. $filefunctionold = $modname.'_pluginfile';
  3635. if (function_exists($filefunction)) {
  3636. // if the function exists, it must send the file and terminate. Whatever it returns leads to "not found"
  3637. $filefunction($course, $cm, $context, $filearea, $args, $forcedownload, array('preview' => $preview));
  3638. } else if (function_exists($filefunctionold)) {
  3639. // if the function exists, it must send the file and terminate. Whatever it returns leads to "not found"
  3640. $filefunctionold($course, $cm, $context, $filearea, $args, $forcedownload, array('preview' => $preview));
  3641. }
  3642. send_file_not_found();
  3643. // ========================================================================================================================
  3644. } else if (strpos($component, 'block_') === 0) {
  3645. $blockname = substr($component, 6);
  3646. // note: no more class methods in blocks please, that is ....
  3647. if (!file_exists("$CFG->dirroot/blocks/$blockname/lib.php")) {
  3648. send_file_not_found();
  3649. }
  3650. require_once("$CFG->dirroot/blocks/$blockname/lib.php");
  3651. if ($context->contextlevel == CONTEXT_BLOCK) {
  3652. $birecord = $DB->get_record('block_instances', array('id'=>$context->instanceid), '*',MUST_EXIST);
  3653. if ($birecord->blockname !== $blockname) {
  3654. // somebody tries to gain illegal access, cm type must match the component!
  3655. send_file_not_found();
  3656. }
  3657. $bprecord = $DB->get_record('block_positions', array('blockinstanceid' => $context->instanceid), 'visible');
  3658. // User can't access file, if block is hidden or doesn't have block:view capability
  3659. if (($bprecord && !$bprecord->visible) || !has_capability('moodle/block:view', $context)) {
  3660. send_file_not_found();
  3661. }
  3662. } else {
  3663. $birecord = null;
  3664. }
  3665. $filefunction = $component.'_pluginfile';
  3666. if (function_exists($filefunction)) {
  3667. // if the function exists, it must send the file and terminate. Whatever it returns leads to "not found"
  3668. $filefunction($course, $birecord, $context, $filearea, $args, $forcedownload, array('preview' => $preview));
  3669. }
  3670. send_file_not_found();
  3671. // ========================================================================================================================
  3672. } else if (strpos($component, '_') === false) {
  3673. // all core subsystems have to be specified above, no more guessing here!
  3674. send_file_not_found();
  3675. } else {
  3676. // try to serve general plugin file in arbitrary context
  3677. $dir = get_component_directory($component);
  3678. if (!file_exists("$dir/lib.php")) {
  3679. send_file_not_found();
  3680. }
  3681. include_once("$dir/lib.php");
  3682. $filefunction = $component.'_pluginfile';
  3683. if (function_exists($filefunction)) {
  3684. // if the function exists, it must send the file and terminate. Whatever it returns leads to "not found"
  3685. $filefunction($course, $cm, $context, $filearea, $args, $forcedownload, array('preview' => $preview));
  3686. }
  3687. send_file_not_found();
  3688. }
  3689. }
  3690. /**
  3691. * Universe file cacheing class
  3692. *
  3693. * @package core_files
  3694. * @category files
  3695. * @copyright 2012 Dongsheng Cai {@link http://dongsheng.org}
  3696. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  3697. */
  3698. class cache_file {
  3699. /** @var string */
  3700. public $cachedir = '';
  3701. /**
  3702. * static method to create cache_file class instance
  3703. *
  3704. * @param array $options caching ooptions
  3705. */
  3706. public static function get_instance($options = array()) {
  3707. return new cache_file($options);
  3708. }
  3709. /**
  3710. * Constructor
  3711. *
  3712. * @param array $options
  3713. */
  3714. private function __construct($options = array()) {
  3715. global $CFG;
  3716. // Path to file caches.
  3717. if (isset($options['cachedir'])) {
  3718. $this->cachedir = $options['cachedir'];
  3719. } else {
  3720. $this->cachedir = $CFG->cachedir . '/filedir';
  3721. }
  3722. // Create cache directory.
  3723. if (!file_exists($this->cachedir)) {
  3724. mkdir($this->cachedir, $CFG->directorypermissions, true);
  3725. }
  3726. // When use cache_file::get, it will check ttl.
  3727. if (isset($options['ttl']) && is_numeric($options['ttl'])) {
  3728. $this->ttl = $options['ttl'];
  3729. } else {
  3730. // One day.
  3731. $this->ttl = 60 * 60 * 24;
  3732. }
  3733. }
  3734. /**
  3735. * Get cached file, false if file expires
  3736. *
  3737. * @param mixed $param
  3738. * @param array $options caching options
  3739. * @return bool|string
  3740. */
  3741. public static function get($param, $options = array()) {
  3742. $instance = self::get_instance($options);
  3743. $filepath = $instance->generate_filepath($param);
  3744. if (file_exists($filepath)) {
  3745. $lasttime = filemtime($filepath);
  3746. if (time() - $lasttime > $instance->ttl) {
  3747. // Remove cache file.
  3748. unlink($filepath);
  3749. return false;
  3750. } else {
  3751. return $filepath;
  3752. }
  3753. } else {
  3754. return false;
  3755. }
  3756. }
  3757. /**
  3758. * Static method to create cache from a file
  3759. *
  3760. * @param mixed $ref
  3761. * @param string $srcfile
  3762. * @param array $options
  3763. * @return string cached file path
  3764. */
  3765. public static function create_from_file($ref, $srcfile, $options = array()) {
  3766. $instance = self::get_instance($options);
  3767. $cachedfilepath = $instance->generate_filepath($ref);
  3768. copy($srcfile, $cachedfilepath);
  3769. return $cachedfilepath;
  3770. }
  3771. /**
  3772. * Static method to create cache from url
  3773. *
  3774. * @param mixed $ref file reference
  3775. * @param string $url file url
  3776. * @param array $options options
  3777. * @return string cached file path
  3778. */
  3779. public static function create_from_url($ref, $url, $options = array()) {
  3780. $instance = self::get_instance($options);
  3781. $cachedfilepath = $instance->generate_filepath($ref);
  3782. $fp = fopen($cachedfilepath, 'w');
  3783. $curl = new curl;
  3784. $curl->download(array(array('url'=>$url, 'file'=>$fp)));
  3785. // Must close file handler.
  3786. fclose($fp);
  3787. return $cachedfilepath;
  3788. }
  3789. /**
  3790. * Static method to create cache from string
  3791. *
  3792. * @param mixed $ref file reference
  3793. * @param string $url file url
  3794. * @param array $options options
  3795. * @return string cached file path
  3796. */
  3797. public static function create_from_string($ref, $string, $options = array()) {
  3798. $instance = self::get_instance($options);
  3799. $cachedfilepath = $instance->generate_filepath($ref);
  3800. $fp = fopen($cachedfilepath, 'w');
  3801. fwrite($fp, $string);
  3802. // Must close file handler.
  3803. fclose($fp);
  3804. return $cachedfilepath;
  3805. }
  3806. /**
  3807. * Build path to cache file
  3808. *
  3809. * @param mixed $ref
  3810. * @return string
  3811. */
  3812. private function generate_filepath($ref) {
  3813. global $CFG;
  3814. $hash = sha1(serialize($ref));
  3815. $l1 = $hash[0].$hash[1];
  3816. $l2 = $hash[2].$hash[3];
  3817. $dir = $this->cachedir . "/$l1/$l2";
  3818. if (!file_exists($dir)) {
  3819. mkdir($dir, $CFG->directorypermissions, true);
  3820. }
  3821. return "$dir/$hash";
  3822. }
  3823. /**
  3824. * Remove cache files
  3825. *
  3826. * @param array $options options
  3827. * @param int $expire The number of seconds before expiry
  3828. */
  3829. public static function cleanup($options = array(), $expire) {
  3830. global $CFG;
  3831. $instance = self::get_instance($options);
  3832. if ($dir = opendir($instance->cachedir)) {
  3833. while (($file = readdir($dir)) !== false) {
  3834. if (!is_dir($file) && $file != '.' && $file != '..') {
  3835. $lasttime = @filemtime($instance->cachedir . $file);
  3836. if(time() - $lasttime > $expire){
  3837. @unlink($instance->cachedir . $file);
  3838. }
  3839. }
  3840. }
  3841. closedir($dir);
  3842. }
  3843. }
  3844. }