PageRenderTime 41ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/filelib.php

https://bitbucket.org/kudutest1/moodlegit
PHP | 1291 lines | 828 code | 118 blank | 345 comment | 191 complexity | a87baa13404f6c22d2fbb4e7e8a5a6a7 MD5 | raw 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. /**
  29. * Unlimited area size constant
  30. */
  31. define('FILE_AREA_MAX_BYTES_UNLIMITED', -1);
  32. require_once("$CFG->libdir/filestorage/file_exceptions.php");
  33. require_once("$CFG->libdir/filestorage/file_storage.php");
  34. require_once("$CFG->libdir/filestorage/zip_packer.php");
  35. require_once("$CFG->libdir/filebrowser/file_browser.php");
  36. /**
  37. * Encodes file serving url
  38. *
  39. * @deprecated use moodle_url factory methods instead
  40. *
  41. * @todo MDL-31071 deprecate this function
  42. * @global stdClass $CFG
  43. * @param string $urlbase
  44. * @param string $path /filearea/itemid/dir/dir/file.exe
  45. * @param bool $forcedownload
  46. * @param bool $https https url required
  47. * @return string encoded file url
  48. */
  49. function file_encode_url($urlbase, $path, $forcedownload=false, $https=false) {
  50. global $CFG;
  51. //TODO: deprecate this
  52. if ($CFG->slasharguments) {
  53. $parts = explode('/', $path);
  54. $parts = array_map('rawurlencode', $parts);
  55. $path = implode('/', $parts);
  56. $return = $urlbase.$path;
  57. if ($forcedownload) {
  58. $return .= '?forcedownload=1';
  59. }
  60. } else {
  61. $path = rawurlencode($path);
  62. $return = $urlbase.'?file='.$path;
  63. if ($forcedownload) {
  64. $return .= '&amp;forcedownload=1';
  65. }
  66. }
  67. if ($https) {
  68. $return = str_replace('http://', 'https://', $return);
  69. }
  70. return $return;
  71. }
  72. /**
  73. * Prepares 'editor' formslib element from data in database
  74. *
  75. * The passed $data record must contain field foobar, foobarformat and optionally foobartrust. This
  76. * function then copies the embedded files into draft area (assigning itemids automatically),
  77. * creates the form element foobar_editor and rewrites the URLs so the embedded images can be
  78. * displayed.
  79. * In your mform definition, you must have an 'editor' element called foobar_editor. Then you call
  80. * your mform's set_data() supplying the object returned by this function.
  81. *
  82. * @category files
  83. * @param stdClass $data database field that holds the html text with embedded media
  84. * @param string $field the name of the database field that holds the html text with embedded media
  85. * @param array $options editor options (like maxifiles, maxbytes etc.)
  86. * @param stdClass $context context of the editor
  87. * @param string $component
  88. * @param string $filearea file area name
  89. * @param int $itemid item id, required if item exists
  90. * @return stdClass modified data object
  91. */
  92. function file_prepare_standard_editor($data, $field, array $options, $context=null, $component=null, $filearea=null, $itemid=null) {
  93. $options = (array)$options;
  94. if (!isset($options['trusttext'])) {
  95. $options['trusttext'] = false;
  96. }
  97. if (!isset($options['forcehttps'])) {
  98. $options['forcehttps'] = false;
  99. }
  100. if (!isset($options['subdirs'])) {
  101. $options['subdirs'] = false;
  102. }
  103. if (!isset($options['maxfiles'])) {
  104. $options['maxfiles'] = 0; // no files by default
  105. }
  106. if (!isset($options['noclean'])) {
  107. $options['noclean'] = false;
  108. }
  109. //sanity check for passed context. This function doesn't expect $option['context'] to be set
  110. //But this function is called before creating editor hence, this is one of the best places to check
  111. //if context is used properly. This check notify developer that they missed passing context to editor.
  112. if (isset($context) && !isset($options['context'])) {
  113. //if $context is not null then make sure $option['context'] is also set.
  114. debugging('Context for editor is not set in editoroptions. Hence editor will not respect editor filters', DEBUG_DEVELOPER);
  115. } else if (isset($options['context']) && isset($context)) {
  116. //If both are passed then they should be equal.
  117. if ($options['context']->id != $context->id) {
  118. $exceptionmsg = 'Editor context ['.$options['context']->id.'] is not equal to passed context ['.$context->id.']';
  119. throw new coding_exception($exceptionmsg);
  120. }
  121. }
  122. if (is_null($itemid) or is_null($context)) {
  123. $contextid = null;
  124. $itemid = null;
  125. if (!isset($data)) {
  126. $data = new stdClass();
  127. }
  128. if (!isset($data->{$field})) {
  129. $data->{$field} = '';
  130. }
  131. if (!isset($data->{$field.'format'})) {
  132. $data->{$field.'format'} = editors_get_preferred_format();
  133. }
  134. if (!$options['noclean']) {
  135. $data->{$field} = clean_text($data->{$field}, $data->{$field.'format'});
  136. }
  137. } else {
  138. if ($options['trusttext']) {
  139. // noclean ignored if trusttext enabled
  140. if (!isset($data->{$field.'trust'})) {
  141. $data->{$field.'trust'} = 0;
  142. }
  143. $data = trusttext_pre_edit($data, $field, $context);
  144. } else {
  145. if (!$options['noclean']) {
  146. $data->{$field} = clean_text($data->{$field}, $data->{$field.'format'});
  147. }
  148. }
  149. $contextid = $context->id;
  150. }
  151. if ($options['maxfiles'] != 0) {
  152. $draftid_editor = file_get_submitted_draft_itemid($field);
  153. $currenttext = file_prepare_draft_area($draftid_editor, $contextid, $component, $filearea, $itemid, $options, $data->{$field});
  154. $data->{$field.'_editor'} = array('text'=>$currenttext, 'format'=>$data->{$field.'format'}, 'itemid'=>$draftid_editor);
  155. } else {
  156. $data->{$field.'_editor'} = array('text'=>$data->{$field}, 'format'=>$data->{$field.'format'}, 'itemid'=>0);
  157. }
  158. return $data;
  159. }
  160. /**
  161. * Prepares the content of the 'editor' form element with embedded media files to be saved in database
  162. *
  163. * This function moves files from draft area to the destination area and
  164. * encodes URLs to the draft files so they can be safely saved into DB. The
  165. * form has to contain the 'editor' element named foobar_editor, where 'foobar'
  166. * is the name of the database field to hold the wysiwyg editor content. The
  167. * editor data comes as an array with text, format and itemid properties. This
  168. * function automatically adds $data properties foobar, foobarformat and
  169. * foobartrust, where foobar has URL to embedded files encoded.
  170. *
  171. * @category files
  172. * @param stdClass $data raw data submitted by the form
  173. * @param string $field name of the database field containing the html with embedded media files
  174. * @param array $options editor options (trusttext, subdirs, maxfiles, maxbytes etc.)
  175. * @param stdClass $context context, required for existing data
  176. * @param string $component file component
  177. * @param string $filearea file area name
  178. * @param int $itemid item id, required if item exists
  179. * @return stdClass modified data object
  180. */
  181. function file_postupdate_standard_editor($data, $field, array $options, $context, $component=null, $filearea=null, $itemid=null) {
  182. $options = (array)$options;
  183. if (!isset($options['trusttext'])) {
  184. $options['trusttext'] = false;
  185. }
  186. if (!isset($options['forcehttps'])) {
  187. $options['forcehttps'] = false;
  188. }
  189. if (!isset($options['subdirs'])) {
  190. $options['subdirs'] = false;
  191. }
  192. if (!isset($options['maxfiles'])) {
  193. $options['maxfiles'] = 0; // no files by default
  194. }
  195. if (!isset($options['maxbytes'])) {
  196. $options['maxbytes'] = 0; // unlimited
  197. }
  198. if ($options['trusttext']) {
  199. $data->{$field.'trust'} = trusttext_trusted($context);
  200. } else {
  201. $data->{$field.'trust'} = 0;
  202. }
  203. $editor = $data->{$field.'_editor'};
  204. if ($options['maxfiles'] == 0 or is_null($filearea) or is_null($itemid) or empty($editor['itemid'])) {
  205. $data->{$field} = $editor['text'];
  206. } else {
  207. $data->{$field} = file_save_draft_area_files($editor['itemid'], $context->id, $component, $filearea, $itemid, $options, $editor['text'], $options['forcehttps']);
  208. }
  209. $data->{$field.'format'} = $editor['format'];
  210. return $data;
  211. }
  212. /**
  213. * Saves text and files modified by Editor formslib element
  214. *
  215. * @category files
  216. * @param stdClass $data $database entry field
  217. * @param string $field name of data field
  218. * @param array $options various options
  219. * @param stdClass $context context - must already exist
  220. * @param string $component
  221. * @param string $filearea file area name
  222. * @param int $itemid must already exist, usually means data is in db
  223. * @return stdClass modified data obejct
  224. */
  225. function file_prepare_standard_filemanager($data, $field, array $options, $context=null, $component=null, $filearea=null, $itemid=null) {
  226. $options = (array)$options;
  227. if (!isset($options['subdirs'])) {
  228. $options['subdirs'] = false;
  229. }
  230. if (is_null($itemid) or is_null($context)) {
  231. $itemid = null;
  232. $contextid = null;
  233. } else {
  234. $contextid = $context->id;
  235. }
  236. $draftid_editor = file_get_submitted_draft_itemid($field.'_filemanager');
  237. file_prepare_draft_area($draftid_editor, $contextid, $component, $filearea, $itemid, $options);
  238. $data->{$field.'_filemanager'} = $draftid_editor;
  239. return $data;
  240. }
  241. /**
  242. * Saves files modified by File manager formslib element
  243. *
  244. * @todo MDL-31073 review this function
  245. * @category files
  246. * @param stdClass $data $database entry field
  247. * @param string $field name of data field
  248. * @param array $options various options
  249. * @param stdClass $context context - must already exist
  250. * @param string $component
  251. * @param string $filearea file area name
  252. * @param int $itemid must already exist, usually means data is in db
  253. * @return stdClass modified data obejct
  254. */
  255. function file_postupdate_standard_filemanager($data, $field, array $options, $context, $component, $filearea, $itemid) {
  256. $options = (array)$options;
  257. if (!isset($options['subdirs'])) {
  258. $options['subdirs'] = false;
  259. }
  260. if (!isset($options['maxfiles'])) {
  261. $options['maxfiles'] = -1; // unlimited
  262. }
  263. if (!isset($options['maxbytes'])) {
  264. $options['maxbytes'] = 0; // unlimited
  265. }
  266. if (empty($data->{$field.'_filemanager'})) {
  267. $data->$field = '';
  268. } else {
  269. file_save_draft_area_files($data->{$field.'_filemanager'}, $context->id, $component, $filearea, $itemid, $options);
  270. $fs = get_file_storage();
  271. if ($fs->get_area_files($context->id, $component, $filearea, $itemid)) {
  272. $data->$field = '1'; // TODO: this is an ugly hack (skodak)
  273. } else {
  274. $data->$field = '';
  275. }
  276. }
  277. return $data;
  278. }
  279. /**
  280. * Generate a draft itemid
  281. *
  282. * @category files
  283. * @global moodle_database $DB
  284. * @global stdClass $USER
  285. * @return int a random but available draft itemid that can be used to create a new draft
  286. * file area.
  287. */
  288. function file_get_unused_draft_itemid() {
  289. global $DB, $USER;
  290. if (isguestuser() or !isloggedin()) {
  291. // guests and not-logged-in users can not be allowed to upload anything!!!!!!
  292. print_error('noguest');
  293. }
  294. $contextid = context_user::instance($USER->id)->id;
  295. $fs = get_file_storage();
  296. $draftitemid = rand(1, 999999999);
  297. while ($files = $fs->get_area_files($contextid, 'user', 'draft', $draftitemid)) {
  298. $draftitemid = rand(1, 999999999);
  299. }
  300. return $draftitemid;
  301. }
  302. /**
  303. * Initialise a draft file area from a real one by copying the files. A draft
  304. * area will be created if one does not already exist. Normally you should
  305. * get $draftitemid by calling file_get_submitted_draft_itemid('elementname');
  306. *
  307. * @category files
  308. * @global stdClass $CFG
  309. * @global stdClass $USER
  310. * @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.
  311. * @param int $contextid This parameter and the next two identify the file area to copy files from.
  312. * @param string $component
  313. * @param string $filearea helps indentify the file area.
  314. * @param int $itemid helps identify the file area. Can be null if there are no files yet.
  315. * @param array $options text and file options ('subdirs'=>false, 'forcehttps'=>false)
  316. * @param string $text some html content that needs to have embedded links rewritten to point to the draft area.
  317. * @return string|null returns string if $text was passed in, the rewritten $text is returned. Otherwise NULL.
  318. */
  319. function file_prepare_draft_area(&$draftitemid, $contextid, $component, $filearea, $itemid, array $options=null, $text=null) {
  320. global $CFG, $USER, $CFG;
  321. $options = (array)$options;
  322. if (!isset($options['subdirs'])) {
  323. $options['subdirs'] = false;
  324. }
  325. if (!isset($options['forcehttps'])) {
  326. $options['forcehttps'] = false;
  327. }
  328. $usercontext = context_user::instance($USER->id);
  329. $fs = get_file_storage();
  330. if (empty($draftitemid)) {
  331. // create a new area and copy existing files into
  332. $draftitemid = file_get_unused_draft_itemid();
  333. $file_record = array('contextid'=>$usercontext->id, 'component'=>'user', 'filearea'=>'draft', 'itemid'=>$draftitemid);
  334. if (!is_null($itemid) and $files = $fs->get_area_files($contextid, $component, $filearea, $itemid)) {
  335. foreach ($files as $file) {
  336. if ($file->is_directory() and $file->get_filepath() === '/') {
  337. // we need a way to mark the age of each draft area,
  338. // by not copying the root dir we force it to be created automatically with current timestamp
  339. continue;
  340. }
  341. if (!$options['subdirs'] and ($file->is_directory() or $file->get_filepath() !== '/')) {
  342. continue;
  343. }
  344. $draftfile = $fs->create_file_from_storedfile($file_record, $file);
  345. // XXX: This is a hack for file manager (MDL-28666)
  346. // File manager needs to know the original file information before copying
  347. // to draft area, so we append these information in mdl_files.source field
  348. // {@link file_storage::search_references()}
  349. // {@link file_storage::search_references_count()}
  350. $sourcefield = $file->get_source();
  351. $newsourcefield = new stdClass;
  352. $newsourcefield->source = $sourcefield;
  353. $original = new stdClass;
  354. $original->contextid = $contextid;
  355. $original->component = $component;
  356. $original->filearea = $filearea;
  357. $original->itemid = $itemid;
  358. $original->filename = $file->get_filename();
  359. $original->filepath = $file->get_filepath();
  360. $newsourcefield->original = file_storage::pack_reference($original);
  361. $draftfile->set_source(serialize($newsourcefield));
  362. // End of file manager hack
  363. }
  364. }
  365. if (!is_null($text)) {
  366. // at this point there should not be any draftfile links yet,
  367. // because this is a new text from database that should still contain the @@pluginfile@@ links
  368. // this happens when developers forget to post process the text
  369. $text = str_replace("\"$CFG->httpswwwroot/draftfile.php", "\"$CFG->httpswwwroot/brokenfile.php#", $text);
  370. }
  371. } else {
  372. // nothing to do
  373. }
  374. if (is_null($text)) {
  375. return null;
  376. }
  377. // relink embedded files - editor can not handle @@PLUGINFILE@@ !
  378. return file_rewrite_pluginfile_urls($text, 'draftfile.php', $usercontext->id, 'user', 'draft', $draftitemid, $options);
  379. }
  380. /**
  381. * Convert encoded URLs in $text from the @@PLUGINFILE@@/... form to an actual URL.
  382. *
  383. * @category files
  384. * @global stdClass $CFG
  385. * @param string $text The content that may contain ULRs in need of rewriting.
  386. * @param string $file The script that should be used to serve these files. pluginfile.php, draftfile.php, etc.
  387. * @param int $contextid This parameter and the next two identify the file area to use.
  388. * @param string $component
  389. * @param string $filearea helps identify the file area.
  390. * @param int $itemid helps identify the file area.
  391. * @param array $options text and file options ('forcehttps'=>false)
  392. * @return string the processed text.
  393. */
  394. function file_rewrite_pluginfile_urls($text, $file, $contextid, $component, $filearea, $itemid, array $options=null) {
  395. global $CFG;
  396. $options = (array)$options;
  397. if (!isset($options['forcehttps'])) {
  398. $options['forcehttps'] = false;
  399. }
  400. if (!$CFG->slasharguments) {
  401. $file = $file . '?file=';
  402. }
  403. $baseurl = "$CFG->wwwroot/$file/$contextid/$component/$filearea/";
  404. if ($itemid !== null) {
  405. $baseurl .= "$itemid/";
  406. }
  407. if ($options['forcehttps']) {
  408. $baseurl = str_replace('http://', 'https://', $baseurl);
  409. }
  410. return str_replace('@@PLUGINFILE@@/', $baseurl, $text);
  411. }
  412. /**
  413. * Returns information about files in a draft area.
  414. *
  415. * @global stdClass $CFG
  416. * @global stdClass $USER
  417. * @param int $draftitemid the draft area item id.
  418. * @param string $filepath path to the directory from which the information have to be retrieved.
  419. * @return array with the following entries:
  420. * 'filecount' => number of files in the draft area.
  421. * 'filesize' => total size of the files in the draft area.
  422. * 'foldercount' => number of folders in the draft area.
  423. * 'filesize_without_references' => total size of the area excluding file references.
  424. * (more information will be added as needed).
  425. */
  426. function file_get_draft_area_info($draftitemid, $filepath = '/') {
  427. global $CFG, $USER;
  428. $usercontext = context_user::instance($USER->id);
  429. $fs = get_file_storage();
  430. $results = array(
  431. 'filecount' => 0,
  432. 'foldercount' => 0,
  433. 'filesize' => 0,
  434. 'filesize_without_references' => 0
  435. );
  436. if ($filepath != '/') {
  437. $draftfiles = $fs->get_directory_files($usercontext->id, 'user', 'draft', $draftitemid, $filepath, true, true);
  438. } else {
  439. $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid, 'id', true);
  440. }
  441. foreach ($draftfiles as $file) {
  442. if ($file->is_directory()) {
  443. $results['foldercount'] += 1;
  444. } else {
  445. $results['filecount'] += 1;
  446. }
  447. $filesize = $file->get_filesize();
  448. $results['filesize'] += $filesize;
  449. if (!$file->is_external_file()) {
  450. $results['filesize_without_references'] += $filesize;
  451. }
  452. }
  453. return $results;
  454. }
  455. /**
  456. * Returns whether a draft area has exceeded/will exceed its size limit.
  457. *
  458. * Please note that the unlimited value for $areamaxbytes is -1 {@link FILE_AREA_MAX_BYTES_UNLIMITED}, not 0.
  459. *
  460. * @param int $draftitemid the draft area item id.
  461. * @param int $areamaxbytes the maximum size allowed in this draft area.
  462. * @param int $newfilesize the size that would be added to the current area.
  463. * @param bool $includereferences true to include the size of the references in the area size.
  464. * @return bool true if the area will/has exceeded its limit.
  465. * @since 2.4
  466. */
  467. function file_is_draft_area_limit_reached($draftitemid, $areamaxbytes, $newfilesize = 0, $includereferences = false) {
  468. if ($areamaxbytes != FILE_AREA_MAX_BYTES_UNLIMITED) {
  469. $draftinfo = file_get_draft_area_info($draftitemid);
  470. $areasize = $draftinfo['filesize_without_references'];
  471. if ($includereferences) {
  472. $areasize = $draftinfo['filesize'];
  473. }
  474. if ($areasize + $newfilesize > $areamaxbytes) {
  475. return true;
  476. }
  477. }
  478. return false;
  479. }
  480. /**
  481. * Get used space of files
  482. * @global moodle_database $DB
  483. * @global stdClass $USER
  484. * @return int total bytes
  485. */
  486. function file_get_user_used_space() {
  487. global $DB, $USER;
  488. $usercontext = context_user::instance($USER->id);
  489. $sql = "SELECT SUM(files1.filesize) AS totalbytes FROM {files} files1
  490. JOIN (SELECT contenthash, filename, MAX(id) AS id
  491. FROM {files}
  492. WHERE contextid = ? AND component = ? AND filearea != ?
  493. GROUP BY contenthash, filename) files2 ON files1.id = files2.id";
  494. $params = array('contextid'=>$usercontext->id, 'component'=>'user', 'filearea'=>'draft');
  495. $record = $DB->get_record_sql($sql, $params);
  496. return (int)$record->totalbytes;
  497. }
  498. /**
  499. * Convert any string to a valid filepath
  500. * @todo review this function
  501. * @param string $str
  502. * @return string path
  503. */
  504. function file_correct_filepath($str) { //TODO: what is this? (skodak) - No idea (Fred)
  505. if ($str == '/' or empty($str)) {
  506. return '/';
  507. } else {
  508. return '/'.trim($str, '/').'/';
  509. }
  510. }
  511. /**
  512. * Generate a folder tree of draft area of current USER recursively
  513. *
  514. * @todo MDL-31073 use normal return value instead, this does not fit the rest of api here (skodak)
  515. * @param int $draftitemid
  516. * @param string $filepath
  517. * @param mixed $data
  518. */
  519. function file_get_drafarea_folders($draftitemid, $filepath, &$data) {
  520. global $USER, $OUTPUT, $CFG;
  521. $data->children = array();
  522. $context = context_user::instance($USER->id);
  523. $fs = get_file_storage();
  524. if ($files = $fs->get_directory_files($context->id, 'user', 'draft', $draftitemid, $filepath, false)) {
  525. foreach ($files as $file) {
  526. if ($file->is_directory()) {
  527. $item = new stdClass();
  528. $item->sortorder = $file->get_sortorder();
  529. $item->filepath = $file->get_filepath();
  530. $foldername = explode('/', trim($item->filepath, '/'));
  531. $item->fullname = trim(array_pop($foldername), '/');
  532. $item->id = uniqid();
  533. file_get_drafarea_folders($draftitemid, $item->filepath, $item);
  534. $data->children[] = $item;
  535. } else {
  536. continue;
  537. }
  538. }
  539. }
  540. }
  541. /**
  542. * Listing all files (including folders) in current path (draft area)
  543. * used by file manager
  544. * @param int $draftitemid
  545. * @param string $filepath
  546. * @return stdClass
  547. */
  548. function file_get_drafarea_files($draftitemid, $filepath = '/') {
  549. global $USER, $OUTPUT, $CFG;
  550. $context = context_user::instance($USER->id);
  551. $fs = get_file_storage();
  552. $data = new stdClass();
  553. $data->path = array();
  554. $data->path[] = array('name'=>get_string('files'), 'path'=>'/');
  555. // will be used to build breadcrumb
  556. $trail = '/';
  557. if ($filepath !== '/') {
  558. $filepath = file_correct_filepath($filepath);
  559. $parts = explode('/', $filepath);
  560. foreach ($parts as $part) {
  561. if ($part != '' && $part != null) {
  562. $trail .= ($part.'/');
  563. $data->path[] = array('name'=>$part, 'path'=>$trail);
  564. }
  565. }
  566. }
  567. $list = array();
  568. $maxlength = 12;
  569. if ($files = $fs->get_directory_files($context->id, 'user', 'draft', $draftitemid, $filepath, false)) {
  570. foreach ($files as $file) {
  571. $item = new stdClass();
  572. $item->filename = $file->get_filename();
  573. $item->filepath = $file->get_filepath();
  574. $item->fullname = trim($item->filename, '/');
  575. $filesize = $file->get_filesize();
  576. $item->size = $filesize ? $filesize : null;
  577. $item->filesize = $filesize ? display_size($filesize) : '';
  578. $item->sortorder = $file->get_sortorder();
  579. $item->author = $file->get_author();
  580. $item->license = $file->get_license();
  581. $item->datemodified = $file->get_timemodified();
  582. $item->datecreated = $file->get_timecreated();
  583. $item->isref = $file->is_external_file();
  584. if ($item->isref && $file->get_status() == 666) {
  585. $item->originalmissing = true;
  586. }
  587. // find the file this draft file was created from and count all references in local
  588. // system pointing to that file
  589. $source = @unserialize($file->get_source());
  590. if (isset($source->original)) {
  591. $item->refcount = $fs->search_references_count($source->original);
  592. }
  593. if ($file->is_directory()) {
  594. $item->filesize = 0;
  595. $item->icon = $OUTPUT->pix_url(file_folder_icon(24))->out(false);
  596. $item->type = 'folder';
  597. $foldername = explode('/', trim($item->filepath, '/'));
  598. $item->fullname = trim(array_pop($foldername), '/');
  599. $item->thumbnail = $OUTPUT->pix_url(file_folder_icon(90))->out(false);
  600. } else {
  601. // do NOT use file browser here!
  602. $item->mimetype = get_mimetype_description($file);
  603. if (file_extension_in_typegroup($file->get_filename(), 'archive')) {
  604. $item->type = 'zip';
  605. } else {
  606. $item->type = 'file';
  607. }
  608. $itemurl = moodle_url::make_draftfile_url($draftitemid, $item->filepath, $item->filename);
  609. $item->url = $itemurl->out();
  610. $item->icon = $OUTPUT->pix_url(file_file_icon($file, 24))->out(false);
  611. $item->thumbnail = $OUTPUT->pix_url(file_file_icon($file, 90))->out(false);
  612. if ($imageinfo = $file->get_imageinfo()) {
  613. $item->realthumbnail = $itemurl->out(false, array('preview' => 'thumb', 'oid' => $file->get_timemodified()));
  614. $item->realicon = $itemurl->out(false, array('preview' => 'tinyicon', 'oid' => $file->get_timemodified()));
  615. $item->image_width = $imageinfo['width'];
  616. $item->image_height = $imageinfo['height'];
  617. }
  618. }
  619. $list[] = $item;
  620. }
  621. }
  622. $data->itemid = $draftitemid;
  623. $data->list = $list;
  624. return $data;
  625. }
  626. /**
  627. * Returns draft area itemid for a given element.
  628. *
  629. * @category files
  630. * @param string $elname name of formlib editor element, or a hidden form field that stores the draft area item id, etc.
  631. * @return int the itemid, or 0 if there is not one yet.
  632. */
  633. function file_get_submitted_draft_itemid($elname) {
  634. // this is a nasty hack, ideally all new elements should use arrays here or there should be a new parameter
  635. if (!isset($_REQUEST[$elname])) {
  636. return 0;
  637. }
  638. if (is_array($_REQUEST[$elname])) {
  639. $param = optional_param_array($elname, 0, PARAM_INT);
  640. if (!empty($param['itemid'])) {
  641. $param = $param['itemid'];
  642. } else {
  643. debugging('Missing itemid, maybe caused by unset maxfiles option', DEBUG_DEVELOPER);
  644. return false;
  645. }
  646. } else {
  647. $param = optional_param($elname, 0, PARAM_INT);
  648. }
  649. if ($param) {
  650. require_sesskey();
  651. }
  652. return $param;
  653. }
  654. /**
  655. * Restore the original source field from draft files
  656. *
  657. * @param stored_file $storedfile This only works with draft files
  658. * @return stored_file
  659. */
  660. function file_restore_source_field_from_draft_file($storedfile) {
  661. $source = @unserialize($storedfile->get_source());
  662. if (!empty($source)) {
  663. if (is_object($source)) {
  664. $restoredsource = $source->source;
  665. $storedfile->set_source($restoredsource);
  666. } else {
  667. throw new moodle_exception('invalidsourcefield', 'error');
  668. }
  669. }
  670. return $storedfile;
  671. }
  672. /**
  673. * Saves files from a draft file area to a real one (merging the list of files).
  674. * Can rewrite URLs in some content at the same time if desired.
  675. *
  676. * @category files
  677. * @global stdClass $USER
  678. * @param int $draftitemid the id of the draft area to use. Normally obtained
  679. * from file_get_submitted_draft_itemid('elementname') or similar.
  680. * @param int $contextid This parameter and the next two identify the file area to save to.
  681. * @param string $component
  682. * @param string $filearea indentifies the file area.
  683. * @param int $itemid helps identifies the file area.
  684. * @param array $options area options (subdirs=>false, maxfiles=-1, maxbytes=0)
  685. * @param string $text some html content that needs to have embedded links rewritten
  686. * to the @@PLUGINFILE@@ form for saving in the database.
  687. * @param bool $forcehttps force https urls.
  688. * @return string|null if $text was passed in, the rewritten $text is returned. Otherwise NULL.
  689. */
  690. function file_save_draft_area_files($draftitemid, $contextid, $component, $filearea, $itemid, array $options=null, $text=null, $forcehttps=false) {
  691. global $USER;
  692. $usercontext = context_user::instance($USER->id);
  693. $fs = get_file_storage();
  694. $options = (array)$options;
  695. if (!isset($options['subdirs'])) {
  696. $options['subdirs'] = false;
  697. }
  698. if (!isset($options['maxfiles'])) {
  699. $options['maxfiles'] = -1; // unlimited
  700. }
  701. if (!isset($options['maxbytes']) || $options['maxbytes'] == USER_CAN_IGNORE_FILE_SIZE_LIMITS) {
  702. $options['maxbytes'] = 0; // unlimited
  703. }
  704. if (!isset($options['areamaxbytes'])) {
  705. $options['areamaxbytes'] = FILE_AREA_MAX_BYTES_UNLIMITED; // Unlimited.
  706. }
  707. $allowreferences = true;
  708. if (isset($options['return_types']) && !($options['return_types'] & FILE_REFERENCE)) {
  709. // we assume that if $options['return_types'] is NOT specified, we DO allow references.
  710. // this is not exactly right. BUT there are many places in code where filemanager options
  711. // are not passed to file_save_draft_area_files()
  712. $allowreferences = false;
  713. }
  714. // Check if the draft area has exceeded the authorised limit. This should never happen as validation
  715. // should have taken place before, unless the user is doing something nauthly. If so, let's just not save
  716. // anything at all in the next area.
  717. if (file_is_draft_area_limit_reached($draftitemid, $options['areamaxbytes'])) {
  718. return null;
  719. }
  720. $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid, 'id');
  721. $oldfiles = $fs->get_area_files($contextid, $component, $filearea, $itemid, 'id');
  722. if (count($draftfiles) < 2) {
  723. // means there are no files - one file means root dir only ;-)
  724. $fs->delete_area_files($contextid, $component, $filearea, $itemid);
  725. } else if (count($oldfiles) < 2) {
  726. $filecount = 0;
  727. // there were no files before - one file means root dir only ;-)
  728. foreach ($draftfiles as $file) {
  729. $file_record = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea, 'itemid'=>$itemid);
  730. if (!$options['subdirs']) {
  731. if ($file->get_filepath() !== '/' or $file->is_directory()) {
  732. continue;
  733. }
  734. }
  735. if ($options['maxbytes'] and $options['maxbytes'] < $file->get_filesize()) {
  736. // oversized file - should not get here at all
  737. continue;
  738. }
  739. if ($options['maxfiles'] != -1 and $options['maxfiles'] <= $filecount) {
  740. // more files - should not get here at all
  741. break;
  742. }
  743. if (!$file->is_directory()) {
  744. $filecount++;
  745. }
  746. if ($file->is_external_file()) {
  747. if (!$allowreferences) {
  748. continue;
  749. }
  750. $repoid = $file->get_repository_id();
  751. if (!empty($repoid)) {
  752. $file_record['repositoryid'] = $repoid;
  753. $file_record['reference'] = $file->get_reference();
  754. }
  755. }
  756. file_restore_source_field_from_draft_file($file);
  757. $fs->create_file_from_storedfile($file_record, $file);
  758. }
  759. } else {
  760. // we have to merge old and new files - we want to keep file ids for files that were not changed
  761. // we change time modified for all new and changed files, we keep time created as is
  762. $newhashes = array();
  763. foreach ($draftfiles as $file) {
  764. $newhash = $fs->get_pathname_hash($contextid, $component, $filearea, $itemid, $file->get_filepath(), $file->get_filename());
  765. file_restore_source_field_from_draft_file($file);
  766. $newhashes[$newhash] = $file;
  767. }
  768. $filecount = 0;
  769. foreach ($oldfiles as $oldfile) {
  770. $oldhash = $oldfile->get_pathnamehash();
  771. if (!isset($newhashes[$oldhash])) {
  772. // delete files not needed any more - deleted by user
  773. $oldfile->delete();
  774. continue;
  775. }
  776. $newfile = $newhashes[$oldhash];
  777. // status changed, we delete old file, and create a new one
  778. if ($oldfile->get_status() != $newfile->get_status()) {
  779. // file was changed, use updated with new timemodified data
  780. $oldfile->delete();
  781. // This file will be added later
  782. continue;
  783. }
  784. // Updated author
  785. if ($oldfile->get_author() != $newfile->get_author()) {
  786. $oldfile->set_author($newfile->get_author());
  787. }
  788. // Updated license
  789. if ($oldfile->get_license() != $newfile->get_license()) {
  790. $oldfile->set_license($newfile->get_license());
  791. }
  792. // Updated file source
  793. if ($oldfile->get_source() != $newfile->get_source()) {
  794. $oldfile->set_source($newfile->get_source());
  795. }
  796. // Updated sort order
  797. if ($oldfile->get_sortorder() != $newfile->get_sortorder()) {
  798. $oldfile->set_sortorder($newfile->get_sortorder());
  799. }
  800. // Update file timemodified
  801. if ($oldfile->get_timemodified() != $newfile->get_timemodified()) {
  802. $oldfile->set_timemodified($newfile->get_timemodified());
  803. }
  804. // Replaced file content
  805. if ($oldfile->get_contenthash() != $newfile->get_contenthash() || $oldfile->get_filesize() != $newfile->get_filesize()) {
  806. $oldfile->replace_content_with($newfile);
  807. // push changes to all local files that are referencing this file
  808. $fs->update_references_to_storedfile($oldfile);
  809. }
  810. // unchanged file or directory - we keep it as is
  811. unset($newhashes[$oldhash]);
  812. if (!$oldfile->is_directory()) {
  813. $filecount++;
  814. }
  815. }
  816. // Add fresh file or the file which has changed status
  817. // the size and subdirectory tests are extra safety only, the UI should prevent it
  818. foreach ($newhashes as $file) {
  819. $file_record = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea, 'itemid'=>$itemid, 'timemodified'=>time());
  820. if (!$options['subdirs']) {
  821. if ($file->get_filepath() !== '/' or $file->is_directory()) {
  822. continue;
  823. }
  824. }
  825. if ($options['maxbytes'] and $options['maxbytes'] < $file->get_filesize()) {
  826. // oversized file - should not get here at all
  827. continue;
  828. }
  829. if ($options['maxfiles'] != -1 and $options['maxfiles'] <= $filecount) {
  830. // more files - should not get here at all
  831. break;
  832. }
  833. if (!$file->is_directory()) {
  834. $filecount++;
  835. }
  836. if ($file->is_external_file()) {
  837. if (!$allowreferences) {
  838. continue;
  839. }
  840. $repoid = $file->get_repository_id();
  841. if (!empty($repoid)) {
  842. $file_record['repositoryid'] = $repoid;
  843. $file_record['reference'] = $file->get_reference();
  844. }
  845. }
  846. $fs->create_file_from_storedfile($file_record, $file);
  847. }
  848. }
  849. // note: do not purge the draft area - we clean up areas later in cron,
  850. // the reason is that user might press submit twice and they would loose the files,
  851. // also sometimes we might want to use hacks that save files into two different areas
  852. if (is_null($text)) {
  853. return null;
  854. } else {
  855. return file_rewrite_urls_to_pluginfile($text, $draftitemid, $forcehttps);
  856. }
  857. }
  858. /**
  859. * Convert the draft file area URLs in some content to @@PLUGINFILE@@ tokens
  860. * ready to be saved in the database. Normally, this is done automatically by
  861. * {@link file_save_draft_area_files()}.
  862. *
  863. * @category files
  864. * @param string $text the content to process.
  865. * @param int $draftitemid the draft file area the content was using.
  866. * @param bool $forcehttps whether the content contains https URLs. Default false.
  867. * @return string the processed content.
  868. */
  869. function file_rewrite_urls_to_pluginfile($text, $draftitemid, $forcehttps = false) {
  870. global $CFG, $USER;
  871. $usercontext = context_user::instance($USER->id);
  872. $wwwroot = $CFG->wwwroot;
  873. if ($forcehttps) {
  874. $wwwroot = str_replace('http://', 'https://', $wwwroot);
  875. }
  876. // relink embedded files if text submitted - no absolute links allowed in database!
  877. $text = str_ireplace("$wwwroot/draftfile.php/$usercontext->id/user/draft/$draftitemid/", '@@PLUGINFILE@@/', $text);
  878. if (strpos($text, 'draftfile.php?file=') !== false) {
  879. $matches = array();
  880. preg_match_all("!$wwwroot/draftfile.php\?file=%2F{$usercontext->id}%2Fuser%2Fdraft%2F{$draftitemid}%2F[^'\",&<>|`\s:\\\\]+!iu", $text, $matches);
  881. if ($matches) {
  882. foreach ($matches[0] as $match) {
  883. $replace = str_ireplace('%2F', '/', $match);
  884. $text = str_replace($match, $replace, $text);
  885. }
  886. }
  887. $text = str_ireplace("$wwwroot/draftfile.php?file=/$usercontext->id/user/draft/$draftitemid/", '@@PLUGINFILE@@/', $text);
  888. }
  889. return $text;
  890. }
  891. /**
  892. * Set file sort order
  893. *
  894. * @global moodle_database $DB
  895. * @param int $contextid the context id
  896. * @param string $component file component
  897. * @param string $filearea file area.
  898. * @param int $itemid itemid.
  899. * @param string $filepath file path.
  900. * @param string $filename file name.
  901. * @param int $sortorder the sort order of file.
  902. * @return bool
  903. */
  904. function file_set_sortorder($contextid, $component, $filearea, $itemid, $filepath, $filename, $sortorder) {
  905. global $DB;
  906. $conditions = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea, 'itemid'=>$itemid, 'filepath'=>$filepath, 'filename'=>$filename);
  907. if ($file_record = $DB->get_record('files', $conditions)) {
  908. $sortorder = (int)$sortorder;
  909. $file_record->sortorder = $sortorder;
  910. $DB->update_record('files', $file_record);
  911. return true;
  912. }
  913. return false;
  914. }
  915. /**
  916. * reset file sort order number to 0
  917. * @global moodle_database $DB
  918. * @param int $contextid the context id
  919. * @param string $component
  920. * @param string $filearea file area.
  921. * @param int|bool $itemid itemid.
  922. * @return bool
  923. */
  924. function file_reset_sortorder($contextid, $component, $filearea, $itemid=false) {
  925. global $DB;
  926. $conditions = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea);
  927. if ($itemid !== false) {
  928. $conditions['itemid'] = $itemid;
  929. }
  930. $file_records = $DB->get_records('files', $conditions);
  931. foreach ($file_records as $file_record) {
  932. $file_record->sortorder = 0;
  933. $DB->update_record('files', $file_record);
  934. }
  935. return true;
  936. }
  937. /**
  938. * Returns description of upload error
  939. *
  940. * @param int $errorcode found in $_FILES['filename.ext']['error']
  941. * @return string error description string, '' if ok
  942. */
  943. function file_get_upload_error($errorcode) {
  944. switch ($errorcode) {
  945. case 0: // UPLOAD_ERR_OK - no error
  946. $errmessage = '';
  947. break;
  948. case 1: // UPLOAD_ERR_INI_SIZE
  949. $errmessage = get_string('uploadserverlimit');
  950. break;
  951. case 2: // UPLOAD_ERR_FORM_SIZE
  952. $errmessage = get_string('uploadformlimit');
  953. break;
  954. case 3: // UPLOAD_ERR_PARTIAL
  955. $errmessage = get_string('uploadpartialfile');
  956. break;
  957. case 4: // UPLOAD_ERR_NO_FILE
  958. $errmessage = get_string('uploadnofilefound');
  959. break;
  960. // Note: there is no error with a value of 5
  961. case 6: // UPLOAD_ERR_NO_TMP_DIR
  962. $errmessage = get_string('uploadnotempdir');
  963. break;
  964. case 7: // UPLOAD_ERR_CANT_WRITE
  965. $errmessage = get_string('uploadcantwrite');
  966. break;
  967. case 8: // UPLOAD_ERR_EXTENSION
  968. $errmessage = get_string('uploadextension');
  969. break;
  970. default:
  971. $errmessage = get_string('uploadproblem');
  972. }
  973. return $errmessage;
  974. }
  975. /**
  976. * Recursive function formating an array in POST parameter
  977. * @param array $arraydata - the array that we are going to format and add into &$data array
  978. * @param string $currentdata - a row of the final postdata array at instant T
  979. * when finish, it's assign to $data under this format: name[keyname][][]...[]='value'
  980. * @param array $data - the final data array containing all POST parameters : 1 row = 1 parameter
  981. */
  982. function format_array_postdata_for_curlcall($arraydata, $currentdata, &$data) {
  983. foreach ($arraydata as $k=>$v) {
  984. $newcurrentdata = $currentdata;
  985. if (is_array($v)) { //the value is an array, call the function recursively
  986. $newcurrentdata = $newcurrentdata.'['.urlencode($k).']';
  987. format_array_postdata_for_curlcall($v, $newcurrentdata, $data);
  988. } else { //add the POST parameter to the $data array
  989. $data[] = $newcurrentdata.'['.urlencode($k).']='.urlencode($v);
  990. }
  991. }
  992. }
  993. /**
  994. * Transform a PHP array into POST parameter
  995. * (see the recursive function format_array_postdata_for_curlcall)
  996. * @param array $postdata
  997. * @return array containing all POST parameters (1 row = 1 POST parameter)
  998. */
  999. function format_postdata_for_curlcall($postdata) {
  1000. $data = array();
  1001. foreach ($postdata as $k=>$v) {
  1002. if (is_array($v)) {
  1003. $currentdata = urlencode($k);
  1004. format_array_postdata_for_curlcall($v, $currentdata, $data);
  1005. } else {
  1006. $data[] = urlencode($k).'='.urlencode($v);
  1007. }
  1008. }
  1009. $convertedpostdata = implode('&', $data);
  1010. return $convertedpostdata;
  1011. }
  1012. /**
  1013. * Fetches content of file from Internet (using proxy if defined). Uses cURL extension if present.
  1014. * Due to security concerns only downloads from http(s) sources are supported.
  1015. *
  1016. * @todo MDL-31073 add version test for '7.10.5'
  1017. * @category files
  1018. * @param string $url file url starting with http(s)://
  1019. * @param array $headers http headers, null if none. If set, should be an
  1020. * associative array of header name => value pairs.
  1021. * @param array $postdata array means use POST request with given parameters
  1022. * @param bool $fullresponse return headers, responses, etc in a similar way snoopy does
  1023. * (if false, just returns content)
  1024. * @param int $timeout timeout for complete download process including all file transfer
  1025. * (default 5 minutes)
  1026. * @param int $connecttimeout timeout for connection to server; this is the timeout that
  1027. * usually happens if the remote server is completely down (default 20 seconds);
  1028. * may not work when using proxy
  1029. * @param bool $skipcertverify If true, the peer's SSL certificate will not be checked.
  1030. * Only use this when already in a trusted location.
  1031. * @param string $tofile store the downloaded content to file instead of returning it.
  1032. * @param bool $calctimeout false by default, true enables an extra head request to try and determine
  1033. * filesize and appropriately larger timeout based on $CFG->curltimeoutkbitrate
  1034. * @return mixed false if request failed or content of the file as string if ok. True if file downloaded into $tofile successfully.
  1035. */
  1036. function download_file_content($url, $headers=null, $postdata=null, $fullresponse=false, $timeout=300, $connecttimeout=20, $skipcertverify=false, $tofile=NULL, $calctimeout=false) {
  1037. global $CFG;
  1038. // some extra security
  1039. $newlines = array("\r", "\n");
  1040. if (is_array($headers) ) {
  1041. foreach ($headers as $key => $value) {
  1042. $headers[$key] = str_replace($newlines, '', $value);
  1043. }
  1044. }
  1045. $url = str_replace($newlines, '', $url);
  1046. if (!preg_match('|^https?://|i', $url)) {
  1047. if ($fullresponse) {
  1048. $response = new stdClass();
  1049. $response->status = 0;
  1050. $response->headers = array();
  1051. $response->response_code = 'Invalid protocol specified in url';
  1052. $response->results = '';
  1053. $response->error = 'Invalid protocol specified in url';
  1054. return $response;
  1055. } else {
  1056. return false;
  1057. }
  1058. }
  1059. // check if proxy (if used) should be bypassed for this url
  1060. $proxybypass = is_proxybypass($url);
  1061. if (!$ch = curl_init($url)) {
  1062. debugging('Can not init curl.');
  1063. return false;
  1064. }
  1065. // set extra headers
  1066. if (is_array($headers) ) {
  1067. $headers2 = array();
  1068. foreach ($headers as $key => $value) {
  1069. $headers2[] = "$key: $value";
  1070. }
  1071. curl_setopt($ch, CURLOPT_HTTPHEADER, $headers2);
  1072. }
  1073. if ($skipcertverify) {
  1074. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
  1075. }
  1076. // use POST if requested
  1077. if (is_array($postdata)) {
  1078. $postdata = format_postdata_for_curlcall($postdata);
  1079. curl_setopt($ch, CURLOPT_POST, true);
  1080. curl_setopt($ch, CURLOPT_POSTFIELDS, $postdata);
  1081. }
  1082. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  1083. curl_setopt($ch, CURLOPT_HEADER, false);
  1084. curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $connecttimeout);
  1085. if (!ini_get('open_basedir') and !ini_get('safe_mode')) {
  1086. // TODO: add version test for '7.10.5'
  1087. curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
  1088. curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
  1089. }
  1090. if (!empty($CFG->proxyhost) and !$proxybypass) {
  1091. // SOCKS supported in PHP5 only
  1092. if (!empty($CFG->proxytype) and ($CFG->proxytype == 'SOCKS5')) {
  1093. if (defined('CURLPROXY_SOCKS5')) {
  1094. curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
  1095. } else {
  1096. curl_close($ch);
  1097. if ($fullresponse) {
  1098. $response = new stdClass();
  1099. $response->status = '0';
  1100. $response->headers = array();
  1101. $response->response_code = 'SOCKS5 proxy is not supported in PHP4';
  1102. $response->results = '';
  1103. $response->error = 'SOCKS5 proxy is not supported in PHP4';
  1104. return $response;
  1105. } else {
  1106. debugging("SOCKS5 proxy is not supported in PHP4.", DEBUG_ALL);
  1107. return false;
  1108. }
  1109. }
  1110. }
  1111. curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, false);
  1112. if (empty($CFG->proxyport)) {
  1113. curl_setopt($ch, CURLOPT_PROXY, $CFG->proxyhost);
  1114. } else {
  1115. curl_setopt($ch, CURLOPT_PROXY, $CFG->proxyhost.':'.$CFG->proxyport);
  1116. }
  1117. if (!empty($CFG->proxyuser) and !empty($CFG->proxypassword)) {
  1118. curl_setopt($ch, CURLOPT_PROXYUSERPWD, $CFG->proxyuser.':'.$CFG->proxypassword);
  1119. if (defined('CURLOPT_PROXYAUTH')) {
  1120. // any proxy authentication if PHP 5.1
  1121. curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_BASIC | CURLAUTH_NTLM);
  1122. }
  1123. }
  1124. }
  1125. // set up header and content handlers
  1126. $received = new stdClass();
  1127. $received->headers = array(); // received headers array
  1128. $received->tofile = $tofile;
  1129. $received->fh = null;
  1130. curl_setopt($ch, CURLOPT_HEADERFUNCTION, partial('download_file_content_header_handler', $received));
  1131. if ($tofile) {
  1132. curl_setopt($ch, CURLOPT_WRITEFUNCTION, partial('download_file_content_write_handler', $received));
  1133. }
  1134. if (!isset($CFG->curltimeoutkbitrate)) {
  1135. //use very slow rate of 56kbps as a timeout speed when not set
  1136. $bitrate = 56;
  1137. } else {
  1138. $bitrate = $CFG->curltimeoutkbitrate;
  1139. }
  1140. // try to calculate the proper amount for timeout from remote file size.
  1141. // if disabled or zero, we won't do any checks nor head requests.
  1142. if ($calctimeout && $bitrate > 0) {
  1143. //setup header request only options
  1144. curl_setopt_array ($ch, array(
  1145. CURLOPT_RETURNTRANSFER => false,
  1146. CURLOPT_NOBODY => true)
  1147. );
  1148. curl_exec($ch);
  1149. $info = curl_getinfo($ch);
  1150. $err = curl_error($ch);
  1151. if ($err === '' && $info['download_content_length'] > 0) { //no curl errors
  1152. $timeout = max($timeout, ceil($info['download_content_length'] * 8 / ($bitrate * 1024))); //adjust for large files only - take max timeout.
  1153. }
  1154. //reinstate affected curl options
  1155. curl_setopt_array ($ch, a