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

/lib/filelib.php

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