PageRenderTime 61ms CodeModel.GetById 24ms 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

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

  1. <?php
  2. // This file is part of Moodle - http://moodle.org/
  3. //
  4. // Moodle is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // Moodle is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  16. /**
  17. * 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. } el

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