Updated the HEAD build version to 20090525
[moodle.git] / lib / filelib.php
CommitLineData
64a19b38 1<?php
599f38f9 2
64a19b38 3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
edc0c493 17
18/**
19 * Functions for file handling.
20 *
64a19b38 21 * @package moodlecore
22 * @subpackage file
64a19b38 23 * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com)
33488ad6 24 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
edc0c493 25 */
26
27/** @var string unique string constant. */
28define('BYTESERVING_BOUNDARY', 's1k2o3d4a5k6s7');
4c8c65ec 29
172dd12c 30require_once("$CFG->libdir/file/file_exceptions.php");
31require_once("$CFG->libdir/file/file_storage.php");
32require_once("$CFG->libdir/file/file_browser.php");
0b0bfa93 33
34require_once("$CFG->libdir/packer/zip_packer.php");
172dd12c 35
509f67e3 36/**
37 * Given a physical path to a file, returns the URL through which it can be reached in Moodle.
38 * @param string $path Physical path to a file
39 * @param array $options associative array of GET variables to append to the URL
40 * @param string $type (questionfile|rssfile|user|usergroup|httpscoursefile|coursefile)
41 * @return string URL to file
42 */
74369ab5 43function get_file_url($path, $options=null, $type='coursefile') {
f86eb734 44 global $CFG, $HTTPSPAGEREQUIRED;
74369ab5 45
172dd12c 46 $path = str_replace('//', '/', $path);
74369ab5 47 $path = trim($path, '/'); // no leading and trailing slashes
48
49 // type of file
50 switch ($type) {
5a254a29 51 case 'questionfile':
52 $url = $CFG->wwwroot."/question/exportfile.php";
53 break;
54 case 'rssfile':
55 $url = $CFG->wwwroot."/rss/file.php";
56 break;
57 case 'user':
f86eb734 58 if (!empty($HTTPSPAGEREQUIRED)) {
59 $wwwroot = $CFG->httpswwwroot;
60 }
61 else {
62 $wwwroot = $CFG->wwwroot;
63 }
64 $url = $wwwroot."/user/pix.php";
5a254a29 65 break;
66 case 'usergroup':
11e7b506 67 $url = $CFG->wwwroot."/user/grouppix.php";
5a254a29 68 break;
69 case 'httpscoursefile':
70 $url = $CFG->httpswwwroot."/file.php";
71 break;
72 case 'coursefile':
74369ab5 73 default:
5a254a29 74 $url = $CFG->wwwroot."/file.php";
74369ab5 75 }
76
77 if ($CFG->slasharguments) {
78 $parts = explode('/', $path);
546fef5f 79 foreach ($parts as $key => $part) {
80 /// anchor dash character should not be encoded
81 $subparts = explode('#', $part);
82 $subparts = array_map('rawurlencode', $subparts);
83 $parts[$key] = implode('#', $subparts);
84 }
74369ab5 85 $path = implode('/', $parts);
5a254a29 86 $ffurl = $url.'/'.$path;
74369ab5 87 $separator = '?';
88 } else {
5a254a29 89 $path = rawurlencode('/'.$path);
90 $ffurl = $url.'?file='.$path;
74369ab5 91 $separator = '&amp;';
92 }
93
94 if ($options) {
95 foreach ($options as $name=>$value) {
96 $ffurl = $ffurl.$separator.$name.'='.$value;
97 $separator = '&amp;';
98 }
99 }
100
101 return $ffurl;
102}
103
4eef1399 104
105/**
106 * Encodes file serving url
107 * TODO: decide if we really need this
108 * @param string $urlbase
109 * @param string $path /filearea/itemid/dir/dir/file.exe
110 * @param bool $forcedownload
111 * @param bool $https https url required
112 * @return string encoded file url
113 */
114function file_encode_url($urlbase, $path, $forcedownload=false, $https=false) {
115 global $CFG;
116
117 if ($CFG->slasharguments) {
118 $parts = explode('/', $path);
119 $parts = array_map('rawurlencode', $parts);
120 $path = implode('/', $parts);
121 $return = $urlbase.$path;
122 if ($forcedownload) {
123 $return .= '?forcedownload=1';
124 }
125 } else {
126 $path = rawurlencode($path);
127 $return = $urlbase.'?file='.$path;
128 if ($forcedownload) {
129 $return .= '&amp;forcedownload=1';
130 }
131 }
132
133 if ($https) {
134 $return = str_replace('http://', 'https://', $return);
135 }
136
137 return $return;
138}
139
c94882ff 140/**
141 * Prepares standardised text field fro editing with Editor formslib element
142 * @param object $data $database entry field
143 * @param string $field name of data field
144 * @param array $options various options
145 * @param object $context context, required for existing data
146 * @param string $filearea file area name
147 * @param int $itemid item id, required if item exists
148 * @return object modified data object
149 */
150function file_prepare_standard_editor($data, $field, array $options, $context=null, $filearea=null, $itemid=null) {
151 $options = (array)$options;
152 if (!isset($options['trusttext'])) {
153 $options['trusttext'] = false;
154 }
155 if (!isset($options['forcehttps'])) {
156 $options['forcehttps'] = false;
157 }
158 if (!isset($options['subdirs'])) {
159 $options['subdirs'] = false;
160 }
989c16ee 161 if (!isset($options['maxfiles'])) {
162 $options['maxfiles'] = 0; // no files by default
163 }
b96ddb7d 164 if (!isset($options['noclean'])) {
165 $options['noclean'] = false;
166 }
c94882ff 167
168 if (empty($data->id) or empty($context)) {
169 $contextid = null;
170 $data->id = null;
171 if (!isset($data->{$field})) {
172 $data->{$field} = '';
173 }
174 if (!isset($data->{$field.'format'})) {
b96ddb7d 175 $data->{$field.'format'} = FORMAT_HTML; // TODO: use better default based on user preferences and browser capabilities
176 }
177 if (!$options['noclean']) {
178 $data->{$field} = clean_text($data->{$field}, $data->{$field.'format'});
c94882ff 179 }
c94882ff 180
181 } else {
182 if ($options['trusttext']) {
b96ddb7d 183 // noclean ignored if trusttext enabled
c94882ff 184 if (!isset($data->{$field.'trust'})) {
185 $data->{$field.'trust'} = 0;
186 }
187 $data = trusttext_pre_edit($data, $field, $context);
188 } else {
b96ddb7d 189 if (!$options['noclean']) {
190 $data->{$field} = clean_text($data->{$field}, $data->{$field.'format'});
191 }
c94882ff 192 }
193 $contextid = $context->id;
194 }
195
989c16ee 196 if ($options['maxfiles'] != 0) {
197 $draftid_editor = file_get_submitted_draft_itemid($field);
a19a06d0 198 $currenttext = file_prepare_draft_area($draftid_editor, $contextid, $filearea, $data->id, $options, $data->{$field});
989c16ee 199 $data->{$field.'_editor'} = array('text'=>$currenttext, 'format'=>$data->{$field.'format'}, 'itemid'=>$draftid_editor);
200 } else {
201 $data->{$field.'_editor'} = array('text'=>$data->{$field}, 'format'=>$data->{$field.'format'}, 0);
202 }
c94882ff 203
204 return $data;
205}
206
207/**
208 * Prepares editing with File manager formslib element
209 * @param object $data $database entry field
210 * @param string $field name of data field
211 * @param array $options various options
212 * @param object $context context, required for existing data
213 * @param string $filearea file area name
214 * @param int $itemid item id, required if item exists
215 * @return object modified data object
216 */
989c16ee 217function file_postupdate_standard_editor($data, $field, array $options, $context, $filearea=null, $itemid=null) {
c94882ff 218 $options = (array)$options;
219 if (!isset($options['trusttext'])) {
220 $options['trusttext'] = false;
221 }
222 if (!isset($options['forcehttps'])) {
223 $options['forcehttps'] = false;
224 }
225 if (!isset($options['subdirs'])) {
226 $options['subdirs'] = false;
227 }
228 if (!isset($options['maxfiles'])) {
85db95e7 229 $options['maxfiles'] = 0; // no files by default
c94882ff 230 }
231 if (!isset($options['maxbytes'])) {
232 $options['maxbytes'] = 0; // unlimited
233 }
234
235 if ($options['trusttext']) {
236 $data->definitiontrust = trusttext_trusted($context);
237 } else {
238 $data->definitiontrust = 0;
239 }
240
85db95e7 241 $editor = $data->{$field.'_editor'};
c94882ff 242
989c16ee 243 if ($options['maxfiles'] != 0 or is_null($filearea) or is_null($itemid)) {
244 $data->{$field} = file_save_draft_area_files($editor['itemid'], $context->id, $filearea, $itemid, $options, $editor['text'], $options['forcehttps']);
245 } else {
246 $data->{$field} = $editor['text'];
247 }
c94882ff 248 $data->{$field.'format'} = $editor['format'];
249
250 return $data;
251}
252
253/**
254 * Saves text and files modified by Editor formslib element
255 * @param object $data $database entry field
256 * @param string $field name of data field
257 * @param array $options various options
258 * @param object $context context - must already exist
259 * @param string $filearea file area name
260 * @param int $itemid must already exist, usually means data is in db
261 * @return object modified data obejct
262 */
263function file_prepare_standard_filemanager($data, $field, array $options, $context=null, $filearea=null, $itemid=null) {
264 $options = (array)$options;
265 if (!isset($options['subdirs'])) {
266 $options['subdirs'] = false;
267 }
268 if (empty($data->id) or empty($context)) {
269 $data->id = null;
270 $contextid = null;
271 } else {
272 $contextid = $context->id;
273 }
274
85db95e7 275 $draftid_editor = file_get_submitted_draft_itemid($field.'_filemanager');
a19a06d0 276 file_prepare_draft_area($draftid_editor, $contextid, $filearea, $data->id, $options);
85db95e7 277 $data->{$field.'_filemanager'} = $draftid_editor;
c94882ff 278
279 return $data;
280}
281
282/**
283 * Saves files modified by File manager formslib element
284 * @param object $data $database entry field
285 * @param string $field name of data field
286 * @param array $options various options
287 * @param object $context context - must already exist
288 * @param string $filearea file area name
289 * @param int $itemid must already exist, usually means data is in db
290 * @return object modified data obejct
291 */
292function file_postupdate_standard_filemanager($data, $field, array $options, $context, $filearea, $itemid) {
293 $options = (array)$options;
294 if (!isset($options['subdirs'])) {
295 $options['subdirs'] = false;
296 }
297 if (!isset($options['maxfiles'])) {
298 $options['maxfiles'] = -1; // unlimited
299 }
300 if (!isset($options['maxbytes'])) {
301 $options['maxbytes'] = 0; // unlimited
302 }
303
85db95e7 304 if (empty($data->{$field.'_filemanager'})) {
c94882ff 305 $data->$field = '';
306
307 } else {
85db95e7 308 file_save_draft_area_files($data->{$field.'_filemanager'}, $context->id, $filearea, $data->id, $options);
c94882ff 309 $fs = get_file_storage();
310
311 if ($fs->get_area_files($context->id, $filearea, $itemid)) {
312 $data->$field = '1'; // TODO: this is an ugly hack
313 } else {
314 $data->$field = '';
315 }
316 }
317
318 return $data;
319}
320
8546def3 321/**
509f67e3 322 * @return int a random but available draft itemid that can be used to create a new draft
edc0c493 323 * file area.
8546def3 324 */
edc0c493 325function file_get_unused_draft_itemid() {
8546def3 326 global $DB, $USER;
327
328 if (isguestuser() or !isloggedin()) {
85db95e7 329 // guests and not-logged-in users can not be allowed to upload anything!!!!!!
8546def3 330 print_error('noguest');
331 }
332
333 $contextid = get_context_instance(CONTEXT_USER, $USER->id)->id;
334 $filearea = 'user_draft';
335
336 $fs = get_file_storage();
337 $draftitemid = rand(1, 999999999);
338 while ($files = $fs->get_area_files($contextid, $filearea, $draftitemid)) {
339 $draftitemid = rand(1, 999999999);
340 }
341
b933a139 342 return $draftitemid;
8546def3 343}
344
7983d682 345/**
edc0c493 346 * Initialise a draft file area from a real one by copying the files. A draft
347 * area will be created if one does not already exist. Normally you should
348 * get $draftitemid by calling file_get_submitted_draft_itemid('elementname');
349 * @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.
350 * @param integer $contextid This parameter and the next two identify the file area to copy files from.
351 * @param string $filearea helps indentify the file area.
352 * @param integer $itemid helps identify the file area. Can be null if there are no files yet.
33374f76 353 * @param array $options text and file options ('subdirs'=>false, 'forcehttps'=>false)
edc0c493 354 * @param string $text some html content that needs to have embedded links rewritten to point to the draft area.
edc0c493 355 * @return string if $text was passed in, the rewritten $text is returned. Otherwise NULL.
7983d682 356 */
a19a06d0 357function file_prepare_draft_area(&$draftitemid, $contextid, $filearea, $itemid, array $options=null, $text=null) {
7983d682 358 global $CFG, $USER;
359
a19a06d0 360 $options = (array)$options;
361 if (!isset($options['subdirs'])) {
362 $options['subdirs'] = false;
363 }
33374f76 364 if (!isset($options['forcehttps'])) {
a19a06d0 365 $options['forcehttps'] = false;
366 }
367
8546def3 368 $usercontext = get_context_instance(CONTEXT_USER, $USER->id);
b933a139 369 $fs = get_file_storage();
370
371 if (empty($draftitemid)) {
372 // create a new area and copy existing files into
edc0c493 373 $draftitemid = file_get_unused_draft_itemid();
b933a139 374 $file_record = array('contextid'=>$usercontext->id, 'filearea'=>'user_draft', 'itemid'=>$draftitemid);
12fab708 375 if (!is_null($itemid) and $files = $fs->get_area_files($contextid, $filearea, $itemid)) {
b933a139 376 foreach ($files as $file) {
a19a06d0 377 if (!$options['subdirs'] and ($file->is_directory() or $file->get_filepath() !== '/')) {
b5b188ce 378 continue;
379 }
b933a139 380 $fs->create_file_from_storedfile($file_record, $file);
381 }
382 }
383 } else {
384 // nothing to do
385 }
386
387 if (is_null($text)) {
388 return null;
389 }
390
edc0c493 391 // relink embedded files - editor can not handle @@PLUGINFILE@@ !
45126617 392 return file_rewrite_pluginfile_urls($text, 'draftfile.php', $usercontext->id, 'user_draft', $draftitemid, $options);
644d506a 393}
394
395/**
edc0c493 396 * Convert encoded URLs in $text from the @@PLUGINFILE@@/... form to an actual URL.
397 * @param string $text The content that may contain ULRs in need of rewriting.
398 * @param string $file The script that should be used to serve these files. pluginfile.php, draftfile.php, etc.
399 * @param integer $contextid This parameter and the next two identify the file area to use.
400 * @param string $filearea helps indentify the file area.
401 * @param integer $itemid helps identify the file area.
33374f76 402 * @param array $options text and file options ('forcehttps'=>false)
edc0c493 403 * @return string the processed text.
644d506a 404 */
33374f76 405function file_rewrite_pluginfile_urls($text, $file, $contextid, $filearea, $itemid, array $options=null) {
644d506a 406 global $CFG;
b933a139 407
33374f76
MH
408 $options = (array)$options;
409 if (!isset($options['forcehttps'])) {
410 $options['forcehttps'] = false;
411 }
412
edc0c493 413 if (!$CFG->slasharguments) {
414 $file = $file . '?file=';
b933a139 415 }
8546def3 416
7d2948bd 417 $baseurl = "$CFG->wwwroot/$file/$contextid/$filearea/";
418
419 if ($itemid !== null) {
420 $baseurl .= "$itemid/";
421 }
edc0c493 422
33374f76 423 if ($options['forcehttps']) {
edc0c493 424 $baseurl = str_replace('http://', 'https://', $baseurl);
b933a139 425 }
426
edc0c493 427 return str_replace('@@PLUGINFILE@@/', $baseurl, $text);
b933a139 428}
429
12fab708 430/**
edc0c493 431 * Returns information about files in a draft area.
432 * @param integer $draftitemid the draft area item id.
433 * @return array with the following entries:
434 * 'filecount' => number of files in the draft area.
435 * (more information will be added as needed).
12fab708 436 */
edc0c493 437function file_get_draft_area_info($draftitemid) {
12fab708 438 global $CFG, $USER;
439
440 $usercontext = get_context_instance(CONTEXT_USER, $USER->id);
441 $fs = get_file_storage();
442
edc0c493 443 $results = array();
444
445 // The number of files
12fab708 446 $draftfiles = $fs->get_area_files($usercontext->id, 'user_draft', $draftitemid, 'id', false);
4f5521dd 447 $results['filecount'] = count($draftfiles);
12fab708 448
edc0c493 449 return $results;
12fab708 450}
451
3156b8ca 452/**
edc0c493 453 * Returns draft area itemid for a given element.
454 * @param string $elname name of formlib editor element, or a hidden form field that stores the draft area item id, etc.
455 * @return inteter the itemid, or 0 if there is not one yet.
3156b8ca 456 */
edc0c493 457function file_get_submitted_draft_itemid($elname) {
458 $param = optional_param($elname, 0, PARAM_INT);
459 if ($param) {
460 confirm_sesskey();
461 }
462 if (is_array($param)) {
463 $param = $param['itemid'];
3156b8ca 464 }
edc0c493 465 return $param;
3156b8ca 466}
467
b933a139 468/**
edc0c493 469 * Saves files from a draft file area to a real one (merging the list of files).
470 * Can rewrite URLs in some content at the same time if desired.
471 * @param int $draftitemid the id of the draft area to use. Normally obtained
472 * from file_get_submitted_draft_itemid('elementname') or similar.
473 * @param integer $contextid This parameter and the next two identify the file area to save to.
a08171c5 474 * @param string $filearea indentifies the file area.
475 * @param integer $itemid helps identifies the file area.
476 * @param array $optionss area options (subdirs=>false, maxfiles=-1, maxbytes=0)
edc0c493 477 * @param string $text some html content that needs to have embedded links rewritten
478 * to the @@PLUGINFILE@@ form for saving in the database.
479 * @param boolean $forcehttps force https urls.
480 * @return string if $text was passed in, the rewritten $text is returned. Otherwise NULL.
b933a139 481 */
a08171c5 482function file_save_draft_area_files($draftitemid, $contextid, $filearea, $itemid, array $options=null, $text=null, $forcehttps=false) {
b933a139 483 global $CFG, $USER;
484
485 $usercontext = get_context_instance(CONTEXT_USER, $USER->id);
8546def3 486 $fs = get_file_storage();
b933a139 487
a08171c5 488 $options = (array)$options;
489 if (!isset($options['subdirs'])) {
490 $options['subdirs'] = false;
491 }
492 if (!isset($options['maxfiles'])) {
493 $options['maxfiles'] = -1; // unlimited
494 }
495 if (!isset($options['maxbytes'])) {
496 $options['maxbytes'] = 0; // unlimited
497 }
498
b933a139 499 $draftfiles = $fs->get_area_files($usercontext->id, 'user_draft', $draftitemid, 'id');
500 $oldfiles = $fs->get_area_files($contextid, $filearea, $itemid, 'id');
501
502 if (count($draftfiles) < 2) {
503 // means there are no files - one file means root dir only ;-)
504 $fs->delete_area_files($contextid, $filearea, $itemid);
505
506 } else if (count($oldfiles) < 2) {
a08171c5 507 $filecount = 0;
b933a139 508 // there were no files before - one file means root dir only ;-)
8546def3 509 $file_record = array('contextid'=>$contextid, 'filearea'=>$filearea, 'itemid'=>$itemid);
b933a139 510 foreach ($draftfiles as $file) {
a08171c5 511 if (!$options['subdirs']) {
512 if ($file->get_filepath() !== '/' or $file->is_directory()) {
513 continue;
514 }
515 }
516 if ($options['maxbytes'] and $options['maxbytes'] < $file->get_filesize()) {
517 // oversized file - should not get here at all
b5b188ce 518 continue;
519 }
a08171c5 520 if ($options['maxfiles'] != -1 and $options['maxfiles'] <= $filecount) {
521 // more files - should not get here at all
522 break;
523 }
524 if (!$file->is_directory()) {
525 $filecount++;
526 }
b933a139 527 $fs->create_file_from_storedfile($file_record, $file);
528 }
529
530 } else {
531 // we have to merge old and new files - we want to keep file ids for files that were not changed
532 $file_record = array('contextid'=>$contextid, 'filearea'=>$filearea, 'itemid'=>$itemid);
a08171c5 533
534 $newhashes = array();
b933a139 535 foreach ($draftfiles as $file) {
536 $newhash = sha1($contextid.$filearea.$itemid.$file->get_filepath().$file->get_filename());
a08171c5 537 $newhashes[$newhash] = $file;
538 }
539 $filecount = 0;
540 foreach ($oldfiles as $file) {
541 $oldhash = $file->get_pathnamehash();
542 if (isset($newhashes[$oldhash])) {
b933a139 543 if (!$file->is_directory()) {
a08171c5 544 $filecount++;
b933a139 545 }
a08171c5 546 // unchanged file already there
547 unset($newhashes[$oldhash]);
548 } else {
549 // delete files not needed any more
550 $file->delete();
b933a139 551 }
b933a139 552 }
a08171c5 553
554 // now add new files
555 foreach ($newhashes as $file) {
556 if (!$options['subdirs']) {
557 if ($file->get_filepath() !== '/' or $file->is_directory()) {
558 continue;
559 }
560 }
561 if ($options['maxbytes'] and $options['maxbytes'] < $file->get_filesize()) {
562 // oversized file - should not get here at all
563 continue;
564 }
565 if ($options['maxfiles'] != -1 and $options['maxfiles'] <= $filecount) {
566 // more files - should not get here at all
567 break;
568 }
569 if (!$file->is_directory()) {
570 $filecount++;
571 }
572 $fs->create_file_from_storedfile($file_record, $file);
8546def3 573 }
574 }
575
b933a139 576 // purge the draft area
577 $fs->delete_area_files($usercontext->id, 'user_draft', $draftitemid);
578
8546def3 579 if (is_null($text)) {
580 return null;
581 }
582
edc0c493 583 // relink embedded files if text submitted - no absolute links allowed in database!
7983d682 584 if ($CFG->slasharguments) {
b5b188ce 585 $draftbase = "$CFG->wwwroot/draftfile.php/$usercontext->id/user_draft/$draftitemid/";
7983d682 586 } else {
b5b188ce 587 $draftbase = "$CFG->wwwroot/draftfile.php?file=/$usercontext->id/user_draft/$draftitemid/";
7983d682 588 }
589
b933a139 590 if ($forcehttps) {
7983d682 591 $draftbase = str_replace('http://', 'https://', $draftbase);
592 }
593
644d506a 594 $text = str_ireplace($draftbase, '@@PLUGINFILE@@/', $text);
7983d682 595
7983d682 596 return $text;
597}
598
a83ad946 599/**
600 * Returns description of upload error
601 * @param int $errorcode found in $_FILES['filename.ext']['error']
602 * @return error description string, '' if ok
603 */
604function file_get_upload_error($errorcode) {
a08171c5 605
a83ad946 606 switch ($errorcode) {
607 case 0: // UPLOAD_ERR_OK - no error
608 $errmessage = '';
609 break;
a08171c5 610
a83ad946 611 case 1: // UPLOAD_ERR_INI_SIZE
612 $errmessage = get_string('uploadserverlimit');
613 break;
a08171c5 614
a83ad946 615 case 2: // UPLOAD_ERR_FORM_SIZE
616 $errmessage = get_string('uploadformlimit');
617 break;
a08171c5 618
a83ad946 619 case 3: // UPLOAD_ERR_PARTIAL
620 $errmessage = get_string('uploadpartialfile');
621 break;
a08171c5 622
a83ad946 623 case 4: // UPLOAD_ERR_NO_FILE
624 $errmessage = get_string('uploadnofilefound');
625 break;
a08171c5 626
a83ad946 627 // Note: there is no error with a value of 5
628
629 case 6: // UPLOAD_ERR_NO_TMP_DIR
630 $errmessage = get_string('uploadnotempdir');
631 break;
632
633 case 7: // UPLOAD_ERR_CANT_WRITE
634 $errmessage = get_string('uploadcantwrite');
635 break;
636
637 case 8: // UPLOAD_ERR_EXTENSION
638 $errmessage = get_string('uploadextension');
639 break;
640
641 default:
642 $errmessage = get_string('uploadproblem');
643 }
644
645 return $errmessage;
646}
647
8ee88311 648/**
5f8bdc17 649 * Fetches content of file from Internet (using proxy if defined). Uses cURL extension if present.
599f06cf 650 * Due to security concerns only downloads from http(s) sources are supported.
651 *
652 * @param string $url file url starting with http(s)://
5ef082df 653 * @param array $headers http headers, null if none. If set, should be an
654 * associative array of header name => value pairs.
6bf55889 655 * @param array $postdata array means use POST request with given parameters
656 * @param bool $fullresponse return headers, responses, etc in a similar way snoopy does
5ef082df 657 * (if false, just returns content)
658 * @param int $timeout timeout for complete download process including all file transfer
44e02d79 659 * (default 5 minutes)
660 * @param int $connecttimeout timeout for connection to server; this is the timeout that
661 * usually happens if the remote server is completely down (default 20 seconds);
662 * may not work when using proxy
83947a36 663 * @param bool $skipcertverify If true, the peer's SSL certificate will not be checked. Only use this when already in a trusted location.
8ee88311 664 * @return mixed false if request failed or content of the file as string if ok.
665 */
83947a36 666function download_file_content($url, $headers=null, $postdata=null, $fullresponse=false, $timeout=300, $connecttimeout=20, $skipcertverify=false) {
e27f0765 667 global $CFG;
668
599f06cf 669 // some extra security
670 $newlines = array("\r", "\n");
671 if (is_array($headers) ) {
672 foreach ($headers as $key => $value) {
673 $headers[$key] = str_replace($newlines, '', $value);
674 }
675 }
676 $url = str_replace($newlines, '', $url);
677 if (!preg_match('|^https?://|i', $url)) {
678 if ($fullresponse) {
679 $response = new object();
680 $response->status = 0;
681 $response->headers = array();
682 $response->response_code = 'Invalid protocol specified in url';
683 $response->results = '';
684 $response->error = 'Invalid protocol specified in url';
685 return $response;
686 } else {
687 return false;
688 }
689 }
690
bb2c046d 691 // check if proxy (if used) should be bypassed for this url
aa944588 692 $proxybypass = is_proxybypass($url);
693
7fb90df0 694 if (!$ch = curl_init($url)) {
695 debugging('Can not init curl.');
696 return false;
697 }
599f06cf 698
599f06cf 699 // set extra headers
6bf55889 700 if (is_array($headers) ) {
701 $headers2 = array();
702 foreach ($headers as $key => $value) {
6bf55889 703 $headers2[] = "$key: $value";
704 }
705 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers2);
706 }
707
bb2c046d 708
83947a36 709 if ($skipcertverify) {
710 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
711 }
bb2c046d 712
6bf55889 713 // use POST if requested
714 if (is_array($postdata)) {
715 foreach ($postdata as $k=>$v) {
716 $postdata[$k] = urlencode($k).'='.urlencode($v);
5f8bdc17 717 }
6bf55889 718 $postdata = implode('&', $postdata);
719 curl_setopt($ch, CURLOPT_POST, true);
720 curl_setopt($ch, CURLOPT_POSTFIELDS, $postdata);
5f8bdc17 721 }
722
8ee88311 723 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
6bf55889 724 curl_setopt($ch, CURLOPT_HEADER, true);
44e02d79 725 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $connecttimeout);
726 curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
6bf55889 727 if (!ini_get('open_basedir') and !ini_get('safe_mode')) {
599f06cf 728 // TODO: add version test for '7.10.5'
6bf55889 729 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
730 curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
731 }
732
15c31560 733 if (!empty($CFG->proxyhost) and !$proxybypass) {
5f8bdc17 734 // SOCKS supported in PHP5 only
735 if (!empty($CFG->proxytype) and ($CFG->proxytype == 'SOCKS5')) {
736 if (defined('CURLPROXY_SOCKS5')) {
737 curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
738 } else {
5f8bdc17 739 curl_close($ch);
599f06cf 740 if ($fullresponse) {
741 $response = new object();
742 $response->status = '0';
743 $response->headers = array();
744 $response->response_code = 'SOCKS5 proxy is not supported in PHP4';
745 $response->results = '';
746 $response->error = 'SOCKS5 proxy is not supported in PHP4';
747 return $response;
748 } else {
749 debugging("SOCKS5 proxy is not supported in PHP4.", DEBUG_ALL);
750 return false;
751 }
5f8bdc17 752 }
753 }
754
08ec989f 755 curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, false);
756
8ee88311 757 if (empty($CFG->proxyport)) {
e27f0765 758 curl_setopt($ch, CURLOPT_PROXY, $CFG->proxyhost);
8ee88311 759 } else {
e27f0765 760 curl_setopt($ch, CURLOPT_PROXY, $CFG->proxyhost.':'.$CFG->proxyport);
8ee88311 761 }
5f8bdc17 762
763 if (!empty($CFG->proxyuser) and !empty($CFG->proxypassword)) {
8ee88311 764 curl_setopt($ch, CURLOPT_PROXYUSERPWD, $CFG->proxyuser.':'.$CFG->proxypassword);
5f8bdc17 765 if (defined('CURLOPT_PROXYAUTH')) {
766 // any proxy authentication if PHP 5.1
767 curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_BASIC | CURLAUTH_NTLM);
768 }
8ee88311 769 }
770 }
6bf55889 771
599f06cf 772 $data = curl_exec($ch);
08ec989f 773
599f06cf 774 // try to detect encoding problems
6bf55889 775 if ((curl_errno($ch) == 23 or curl_errno($ch) == 61) and defined('CURLOPT_ENCODING')) {
776 curl_setopt($ch, CURLOPT_ENCODING, 'none');
599f06cf 777 $data = curl_exec($ch);
6bf55889 778 }
779
08ec989f 780 if (curl_errno($ch)) {
6bf55889 781 $error = curl_error($ch);
782 $error_no = curl_errno($ch);
783 curl_close($ch);
784
785 if ($fullresponse) {
786 $response = new object();
787 if ($error_no == 28) {
788 $response->status = '-100'; // mimic snoopy
789 } else {
790 $response->status = '0';
791 }
792 $response->headers = array();
793 $response->response_code = $error;
794 $response->results = '';
795 $response->error = $error;
796 return $response;
797 } else {
599f06cf 798 debugging("cURL request for \"$url\" failed with: $error ($error_no)", DEBUG_ALL);
6bf55889 799 return false;
800 }
5f8bdc17 801
802 } else {
6bf55889 803 $info = curl_getinfo($ch);
804 curl_close($ch);
599f06cf 805
806 if (empty($info['http_code'])) {
807 // for security reasons we support only true http connections (Location: file:// exploit prevention)
808 $response = new object();
809 $response->status = '0';
810 $response->headers = array();
811 $response->response_code = 'Unknown cURL error';
812 $response->results = ''; // do NOT change this!
813 $response->error = 'Unknown cURL error';
814
815 } else {
816 // strip redirect headers and get headers array and content
817 $data = explode("\r\n\r\n", $data, $info['redirect_count'] + 2);
818 $results = array_pop($data);
819 $headers = array_pop($data);
820 $headers = explode("\r\n", trim($headers));
821
822 $response = new object();;
823 $response->status = (string)$info['http_code'];
824 $response->headers = $headers;
825 $response->response_code = $headers[0];
826 $response->results = $results;
827 $response->error = '';
828 }
6bf55889 829
830 if ($fullresponse) {
831 return $response;
832 } else if ($info['http_code'] != 200) {
599f06cf 833 debugging("cURL request for \"$url\" failed, HTTP response code: ".$response->response_code, DEBUG_ALL);
6bf55889 834 return false;
835 } else {
836 return $response->results;
5f8bdc17 837 }
08ec989f 838 }
8ee88311 839}
840
3ce73b14 841/**
76ca1ff1 842 * @return List of information about file types based on extensions.
3ce73b14 843 * Associative array of extension (lower-case) to associative array
844 * from 'element name' to data. Current element names are 'type' and 'icon'.
76ca1ff1 845 * Unknown types should use the 'xxx' entry which includes defaults.
3ce73b14 846 */
847function get_mimetypes_array() {
172dd12c 848 static $mimearray = array (
a370c895 849 'xxx' => array ('type'=>'document/unknown', 'icon'=>'unknown.gif'),
850 '3gp' => array ('type'=>'video/quicktime', 'icon'=>'video.gif'),
851 'ai' => array ('type'=>'application/postscript', 'icon'=>'image.gif'),
852 'aif' => array ('type'=>'audio/x-aiff', 'icon'=>'audio.gif'),
853 'aiff' => array ('type'=>'audio/x-aiff', 'icon'=>'audio.gif'),
854 'aifc' => array ('type'=>'audio/x-aiff', 'icon'=>'audio.gif'),
855 'applescript' => array ('type'=>'text/plain', 'icon'=>'text.gif'),
856 'asc' => array ('type'=>'text/plain', 'icon'=>'text.gif'),
18bf47ef 857 'asm' => array ('type'=>'text/plain', 'icon'=>'text.gif'),
a370c895 858 'au' => array ('type'=>'audio/au', 'icon'=>'audio.gif'),
859 'avi' => array ('type'=>'video/x-ms-wm', 'icon'=>'avi.gif'),
860 'bmp' => array ('type'=>'image/bmp', 'icon'=>'image.gif'),
18bf47ef 861 'c' => array ('type'=>'text/plain', 'icon'=>'text.gif'),
a370c895 862 'cct' => array ('type'=>'shockwave/director', 'icon'=>'flash.gif'),
18bf47ef 863 'cpp' => array ('type'=>'text/plain', 'icon'=>'text.gif'),
a370c895 864 'cs' => array ('type'=>'application/x-csh', 'icon'=>'text.gif'),
76ca1ff1 865 'css' => array ('type'=>'text/css', 'icon'=>'text.gif'),
6ae5e482 866 'csv' => array ('type'=>'text/csv', 'icon'=>'excel.gif'),
a370c895 867 'dv' => array ('type'=>'video/x-dv', 'icon'=>'video.gif'),
609d84e3 868 'dmg' => array ('type'=>'application/octet-stream', 'icon'=>'dmg.gif'),
13499032 869
a370c895 870 'doc' => array ('type'=>'application/msword', 'icon'=>'word.gif'),
13499032 871 'docx' => array ('type'=>'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'icon'=>'docx.gif'),
872 'docm' => array ('type'=>'application/vnd.ms-word.document.macroEnabled.12', 'icon'=>'docm.gif'),
873 'dotx' => array ('type'=>'application/vnd.openxmlformats-officedocument.wordprocessingml.template', 'icon'=>'dotx.gif'),
874 'dotm' => array ('type'=>'application/vnd.ms-word.template.macroEnabled.12', 'icon'=>'dotm.gif'),
875
a370c895 876 'dcr' => array ('type'=>'application/x-director', 'icon'=>'flash.gif'),
877 'dif' => array ('type'=>'video/x-dv', 'icon'=>'video.gif'),
878 'dir' => array ('type'=>'application/x-director', 'icon'=>'flash.gif'),
879 'dxr' => array ('type'=>'application/x-director', 'icon'=>'flash.gif'),
880 'eps' => array ('type'=>'application/postscript', 'icon'=>'pdf.gif'),
ee7f231d 881 'fdf' => array ('type'=>'application/pdf', 'icon'=>'pdf.gif'),
759bc3c8 882 'flv' => array ('type'=>'video/x-flv', 'icon'=>'video.gif'),
a370c895 883 'gif' => array ('type'=>'image/gif', 'icon'=>'image.gif'),
884 'gtar' => array ('type'=>'application/x-gtar', 'icon'=>'zip.gif'),
759bc3c8 885 'tgz' => array ('type'=>'application/g-zip', 'icon'=>'zip.gif'),
a370c895 886 'gz' => array ('type'=>'application/g-zip', 'icon'=>'zip.gif'),
887 'gzip' => array ('type'=>'application/g-zip', 'icon'=>'zip.gif'),
888 'h' => array ('type'=>'text/plain', 'icon'=>'text.gif'),
18bf47ef 889 'hpp' => array ('type'=>'text/plain', 'icon'=>'text.gif'),
a370c895 890 'hqx' => array ('type'=>'application/mac-binhex40', 'icon'=>'zip.gif'),
70ee2841 891 'htc' => array ('type'=>'text/x-component', 'icon'=>'text.gif'),
a370c895 892 'html' => array ('type'=>'text/html', 'icon'=>'html.gif'),
1659a998 893 'xhtml'=> array ('type'=>'application/xhtml+xml', 'icon'=>'html.gif'),
a370c895 894 'htm' => array ('type'=>'text/html', 'icon'=>'html.gif'),
08297dcb 895 'ico' => array ('type'=>'image/vnd.microsoft.icon', 'icon'=>'image.gif'),
4b270c4c 896 'ics' => array ('type'=>'text/calendar', 'icon'=>'text.gif'),
08297dcb 897 'isf' => array ('type'=>'application/inspiration', 'icon'=>'isf.gif'),
898 'ist' => array ('type'=>'application/inspiration.template', 'icon'=>'isf.gif'),
18bf47ef 899 'java' => array ('type'=>'text/plain', 'icon'=>'text.gif'),
a00420fb 900 'jcb' => array ('type'=>'text/xml', 'icon'=>'jcb.gif'),
901 'jcl' => array ('type'=>'text/xml', 'icon'=>'jcl.gif'),
902 'jcw' => array ('type'=>'text/xml', 'icon'=>'jcw.gif'),
903 'jmt' => array ('type'=>'text/xml', 'icon'=>'jmt.gif'),
904 'jmx' => array ('type'=>'text/xml', 'icon'=>'jmx.gif'),
a370c895 905 'jpe' => array ('type'=>'image/jpeg', 'icon'=>'image.gif'),
906 'jpeg' => array ('type'=>'image/jpeg', 'icon'=>'image.gif'),
907 'jpg' => array ('type'=>'image/jpeg', 'icon'=>'image.gif'),
a00420fb 908 'jqz' => array ('type'=>'text/xml', 'icon'=>'jqz.gif'),
a370c895 909 'js' => array ('type'=>'application/x-javascript', 'icon'=>'text.gif'),
910 'latex'=> array ('type'=>'application/x-latex', 'icon'=>'text.gif'),
911 'm' => array ('type'=>'text/plain', 'icon'=>'text.gif'),
912 'mov' => array ('type'=>'video/quicktime', 'icon'=>'video.gif'),
913 'movie'=> array ('type'=>'video/x-sgi-movie', 'icon'=>'video.gif'),
914 'm3u' => array ('type'=>'audio/x-mpegurl', 'icon'=>'audio.gif'),
915 'mp3' => array ('type'=>'audio/mp3', 'icon'=>'audio.gif'),
916 'mp4' => array ('type'=>'video/mp4', 'icon'=>'video.gif'),
917 'mpeg' => array ('type'=>'video/mpeg', 'icon'=>'video.gif'),
918 'mpe' => array ('type'=>'video/mpeg', 'icon'=>'video.gif'),
919 'mpg' => array ('type'=>'video/mpeg', 'icon'=>'video.gif'),
5395334d 920
921 'odt' => array ('type'=>'application/vnd.oasis.opendocument.text', 'icon'=>'odt.gif'),
922 'ott' => array ('type'=>'application/vnd.oasis.opendocument.text-template', 'icon'=>'odt.gif'),
923 'oth' => array ('type'=>'application/vnd.oasis.opendocument.text-web', 'icon'=>'odt.gif'),
e10bc440 924 'odm' => array ('type'=>'application/vnd.oasis.opendocument.text-master', 'icon'=>'odm.gif'),
925 'odg' => array ('type'=>'application/vnd.oasis.opendocument.graphics', 'icon'=>'odg.gif'),
926 'otg' => array ('type'=>'application/vnd.oasis.opendocument.graphics-template', 'icon'=>'odg.gif'),
927 'odp' => array ('type'=>'application/vnd.oasis.opendocument.presentation', 'icon'=>'odp.gif'),
928 'otp' => array ('type'=>'application/vnd.oasis.opendocument.presentation-template', 'icon'=>'odp.gif'),
929 'ods' => array ('type'=>'application/vnd.oasis.opendocument.spreadsheet', 'icon'=>'ods.gif'),
930 'ots' => array ('type'=>'application/vnd.oasis.opendocument.spreadsheet-template', 'icon'=>'ods.gif'),
931 'odc' => array ('type'=>'application/vnd.oasis.opendocument.chart', 'icon'=>'odc.gif'),
932 'odf' => array ('type'=>'application/vnd.oasis.opendocument.formula', 'icon'=>'odf.gif'),
933 'odb' => array ('type'=>'application/vnd.oasis.opendocument.database', 'icon'=>'odb.gif'),
934 'odi' => array ('type'=>'application/vnd.oasis.opendocument.image', 'icon'=>'odi.gif'),
5395334d 935
a370c895 936 'pct' => array ('type'=>'image/pict', 'icon'=>'image.gif'),
937 'pdf' => array ('type'=>'application/pdf', 'icon'=>'pdf.gif'),
938 'php' => array ('type'=>'text/plain', 'icon'=>'text.gif'),
939 'pic' => array ('type'=>'image/pict', 'icon'=>'image.gif'),
940 'pict' => array ('type'=>'image/pict', 'icon'=>'image.gif'),
941 'png' => array ('type'=>'image/png', 'icon'=>'image.gif'),
13499032 942
a370c895 943 'pps' => array ('type'=>'application/vnd.ms-powerpoint', 'icon'=>'powerpoint.gif'),
944 'ppt' => array ('type'=>'application/vnd.ms-powerpoint', 'icon'=>'powerpoint.gif'),
13499032 945 'pptx' => array ('type'=>'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'icon'=>'pptx.gif'),
946 'pptm' => array ('type'=>'application/vnd.ms-powerpoint.presentation.macroEnabled.12', 'icon'=>'pptm.gif'),
947 'potx' => array ('type'=>'application/vnd.openxmlformats-officedocument.presentationml.template', 'icon'=>'potx.gif'),
948 'potm' => array ('type'=>'application/vnd.ms-powerpoint.template.macroEnabled.12', 'icon'=>'potm.gif'),
949 'ppam' => array ('type'=>'application/vnd.ms-powerpoint.addin.macroEnabled.12', 'icon'=>'ppam.gif'),
950 'ppsx' => array ('type'=>'application/vnd.openxmlformats-officedocument.presentationml.slideshow', 'icon'=>'ppsx.gif'),
951 'ppsm' => array ('type'=>'application/vnd.ms-powerpoint.slideshow.macroEnabled.12', 'icon'=>'ppsm.gif'),
952
a370c895 953 'ps' => array ('type'=>'application/postscript', 'icon'=>'pdf.gif'),
954 'qt' => array ('type'=>'video/quicktime', 'icon'=>'video.gif'),
7c59f3be 955 'ra' => array ('type'=>'audio/x-realaudio-plugin', 'icon'=>'audio.gif'),
956 'ram' => array ('type'=>'audio/x-pn-realaudio-plugin', 'icon'=>'audio.gif'),
a00420fb 957 'rhb' => array ('type'=>'text/xml', 'icon'=>'xml.gif'),
7c59f3be 958 'rm' => array ('type'=>'audio/x-pn-realaudio-plugin', 'icon'=>'audio.gif'),
a370c895 959 'rtf' => array ('type'=>'text/rtf', 'icon'=>'text.gif'),
960 'rtx' => array ('type'=>'text/richtext', 'icon'=>'text.gif'),
961 'sh' => array ('type'=>'application/x-sh', 'icon'=>'text.gif'),
962 'sit' => array ('type'=>'application/x-stuffit', 'icon'=>'zip.gif'),
963 'smi' => array ('type'=>'application/smil', 'icon'=>'text.gif'),
964 'smil' => array ('type'=>'application/smil', 'icon'=>'text.gif'),
a00420fb 965 'sqt' => array ('type'=>'text/xml', 'icon'=>'xml.gif'),
4db69ffb 966 'svg' => array ('type'=>'image/svg+xml', 'icon'=>'image.gif'),
967 'svgz' => array ('type'=>'image/svg+xml', 'icon'=>'image.gif'),
a370c895 968 'swa' => array ('type'=>'application/x-director', 'icon'=>'flash.gif'),
969 'swf' => array ('type'=>'application/x-shockwave-flash', 'icon'=>'flash.gif'),
970 'swfl' => array ('type'=>'application/x-shockwave-flash', 'icon'=>'flash.gif'),
5395334d 971
972 'sxw' => array ('type'=>'application/vnd.sun.xml.writer', 'icon'=>'odt.gif'),
973 'stw' => array ('type'=>'application/vnd.sun.xml.writer.template', 'icon'=>'odt.gif'),
974 'sxc' => array ('type'=>'application/vnd.sun.xml.calc', 'icon'=>'odt.gif'),
975 'stc' => array ('type'=>'application/vnd.sun.xml.calc.template', 'icon'=>'odt.gif'),
976 'sxd' => array ('type'=>'application/vnd.sun.xml.draw', 'icon'=>'odt.gif'),
977 'std' => array ('type'=>'application/vnd.sun.xml.draw.template', 'icon'=>'odt.gif'),
978 'sxi' => array ('type'=>'application/vnd.sun.xml.impress', 'icon'=>'odt.gif'),
979 'sti' => array ('type'=>'application/vnd.sun.xml.impress.template', 'icon'=>'odt.gif'),
980 'sxg' => array ('type'=>'application/vnd.sun.xml.writer.global', 'icon'=>'odt.gif'),
981 'sxm' => array ('type'=>'application/vnd.sun.xml.math', 'icon'=>'odt.gif'),
982
a370c895 983 'tar' => array ('type'=>'application/x-tar', 'icon'=>'zip.gif'),
984 'tif' => array ('type'=>'image/tiff', 'icon'=>'image.gif'),
985 'tiff' => array ('type'=>'image/tiff', 'icon'=>'image.gif'),
986 'tex' => array ('type'=>'application/x-tex', 'icon'=>'text.gif'),
987 'texi' => array ('type'=>'application/x-texinfo', 'icon'=>'text.gif'),
988 'texinfo' => array ('type'=>'application/x-texinfo', 'icon'=>'text.gif'),
989 'tsv' => array ('type'=>'text/tab-separated-values', 'icon'=>'text.gif'),
990 'txt' => array ('type'=>'text/plain', 'icon'=>'text.gif'),
991 'wav' => array ('type'=>'audio/wav', 'icon'=>'audio.gif'),
992 'wmv' => array ('type'=>'video/x-ms-wmv', 'icon'=>'avi.gif'),
993 'asf' => array ('type'=>'video/x-ms-asf', 'icon'=>'avi.gif'),
ee7f231d 994 'xdp' => array ('type'=>'application/pdf', 'icon'=>'pdf.gif'),
995 'xfd' => array ('type'=>'application/pdf', 'icon'=>'pdf.gif'),
996 'xfdf' => array ('type'=>'application/pdf', 'icon'=>'pdf.gif'),
13499032 997
a370c895 998 'xls' => array ('type'=>'application/vnd.ms-excel', 'icon'=>'excel.gif'),
13499032 999 'xlsx' => array ('type'=>'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'icon'=>'xlsx.gif'),
1000 'xlsm' => array ('type'=>'application/vnd.ms-excel.sheet.macroEnabled.12', 'icon'=>'xlsm.gif'),
1001 'xltx' => array ('type'=>'application/vnd.openxmlformats-officedocument.spreadsheetml.template', 'icon'=>'xltx.gif'),
1002 'xltm' => array ('type'=>'application/vnd.ms-excel.template.macroEnabled.12', 'icon'=>'xltm.gif'),
1003 'xlsb' => array ('type'=>'application/vnd.ms-excel.sheet.binary.macroEnabled.12', 'icon'=>'xlsb.gif'),
1004 'xlam' => array ('type'=>'application/vnd.ms-excel.addin.macroEnabled.12', 'icon'=>'xlam.gif'),
1005
a370c895 1006 'xml' => array ('type'=>'application/xml', 'icon'=>'xml.gif'),
1007 'xsl' => array ('type'=>'text/xml', 'icon'=>'xml.gif'),
1008 'zip' => array ('type'=>'application/zip', 'icon'=>'zip.gif')
f1e0649c 1009 );
172dd12c 1010 return $mimearray;
3ce73b14 1011}
1012
76ca1ff1 1013/**
3ce73b14 1014 * Obtains information about a filetype based on its extension. Will
1015 * use a default if no information is present about that particular
1016 * extension.
76ca1ff1 1017 * @param string $element Desired information (usually 'icon'
3ce73b14 1018 * for icon filename or 'type' for MIME type)
76ca1ff1 1019 * @param string $filename Filename we're looking up
3ce73b14 1020 * @return string Requested piece of information from array
1021 */
1022function mimeinfo($element, $filename) {
0ef98843 1023 global $CFG;
172dd12c 1024 $mimeinfo = get_mimetypes_array();
f1e0649c 1025
a370c895 1026 if (eregi('\.([a-z0-9]+)$', $filename, $match)) {
f1e0649c 1027 if (isset($mimeinfo[strtolower($match[1])][$element])) {
1028 return $mimeinfo[strtolower($match[1])][$element];
1029 } else {
72aa74ce 1030 if ($element == 'icon32') {
1031 if (isset($mimeinfo[strtolower($match[1])]['icon'])) {
1032 $filename = substr($mimeinfo[strtolower($match[1])]['icon'], 0, -4);
1033 } else {
1034 $filename = 'unknown';
1035 }
0ef98843 1036 $filename .= '-32.png';
1037 if (file_exists($CFG->dirroot.'/pix/f/'.$filename)) {
1038 return $filename;
1039 } else {
7a34d20d 1040 return 'unknown-32.png';
0ef98843 1041 }
1042 } else {
1043 return $mimeinfo['xxx'][$element]; // By default
1044 }
f1e0649c 1045 }
1046 } else {
7a34d20d 1047 if ($element == 'icon32') {
1048 return 'unknown-32.png';
1049 }
a370c895 1050 return $mimeinfo['xxx'][$element]; // By default
f1e0649c 1051 }
1052}
1053
76ca1ff1 1054/**
3ce73b14 1055 * Obtains information about a filetype based on the MIME type rather than
1056 * the other way around.
1057 * @param string $element Desired information (usually 'icon')
76ca1ff1 1058 * @param string $mimetype MIME type we're looking up
3ce73b14 1059 * @return string Requested piece of information from array
1060 */
1061function mimeinfo_from_type($element, $mimetype) {
172dd12c 1062 $mimeinfo = get_mimetypes_array();
76ca1ff1 1063
3ce73b14 1064 foreach($mimeinfo as $values) {
1065 if($values['type']==$mimetype) {
1066 if(isset($values[$element])) {
1067 return $values[$element];
1068 }
1069 break;
1070 }
1071 }
1072 return $mimeinfo['xxx'][$element]; // Default
1073}
b9709b76 1074
42ead7d7 1075/**
1076 * Get information about a filetype based on the icon file.
1077 * @param string $element Desired information (usually 'icon')
1078 * @param string $icon Icon file path.
0b46f19e 1079 * @param boolean $all return all matching entries (defaults to false - last match)
42ead7d7 1080 * @return string Requested piece of information from array
1081 */
0b46f19e 1082function mimeinfo_from_icon($element, $icon, $all=false) {
172dd12c 1083 $mimeinfo = get_mimetypes_array();
42ead7d7 1084
1085 if (preg_match("/\/(.*)/", $icon, $matches)) {
1086 $icon = $matches[1];
1087 }
0b46f19e 1088 $info = array($mimeinfo['xxx'][$element]); // Default
42ead7d7 1089 foreach($mimeinfo as $values) {
1090 if($values['icon']==$icon) {
1091 if(isset($values[$element])) {
0b46f19e 1092 $info[] = $values[$element];
42ead7d7 1093 }
1094 //No break, for example for 'excel.gif' we don't want 'csv'!
1095 }
1096 }
0b46f19e 1097 if ($all) {
1098 return $info;
1099 }
1100 return array_pop($info); // Return last match (mimicking behaviour/comment inside foreach loop)
42ead7d7 1101}
1102
c0381e22 1103/**
76ca1ff1 1104 * Obtains descriptions for file types (e.g. 'Microsoft Word document') from the
1105 * mimetypes.php language file.
c0381e22 1106 * @param string $mimetype MIME type (can be obtained using the mimeinfo function)
1107 * @param bool $capitalise If true, capitalises first character of result
76ca1ff1 1108 * @return string Text description
c0381e22 1109 */
1110function get_mimetype_description($mimetype,$capitalise=false) {
1111 $result=get_string($mimetype,'mimetypes');
1112 // Surrounded by square brackets indicates that there isn't a string for that
1113 // (maybe there is a better way to find this out?)
1114 if(strpos($result,'[')===0) {
1115 $result=get_string('document/unknown','mimetypes');
76ca1ff1 1116 }
c0381e22 1117 if($capitalise) {
1118 $result=ucfirst($result);
1119 }
1120 return $result;
1121}
1122
9e5fa330 1123/**
1124 * Reprot file is not found or not accessible
1125 * @return does not return, terminates script
1126 */
1127function send_file_not_found() {
1128 global $CFG, $COURSE;
1129 header('HTTP/1.0 404 not found');
1130 print_error('filenotfound', 'error', $CFG->wwwroot.'/course/view.php?id='.$COURSE->id); //this is not displayed on IIS??
1131}
1132
c87c428e 1133/**
1134 * Handles the sending of temporary file to user, download is forced.
1135 * File is deleted after abort or succesful sending.
1136 * @param string $path path to file, preferably from moodledata/temp/something; or content of file itself
1137 * @param string $filename proposed file name when saving file
1138 * @param bool $path is content of file
45c0d224 1139 * @return does not return, script terminated
c87c428e 1140 */
45c0d224 1141function send_temp_file($path, $filename, $pathisstring=false) {
c87c428e 1142 global $CFG;
1143
1144 // close session - not needed anymore
56949c17 1145 @session_get_instance()->write_close();
c87c428e 1146
1147 if (!$pathisstring) {
1148 if (!file_exists($path)) {
1149 header('HTTP/1.0 404 not found');
45c0d224 1150 print_error('filenotfound', 'error', $CFG->wwwroot.'/');
c87c428e 1151 }
1152 // executed after normal finish or abort
1153 @register_shutdown_function('send_temp_file_finished', $path);
1154 }
1155
1156 //IE compatibiltiy HACK!
1157 if (ini_get('zlib.output_compression')) {
1158 ini_set('zlib.output_compression', 'Off');
1159 }
1160
1161 // if user is using IE, urlencode the filename so that multibyte file name will show up correctly on popup
1162 if (check_browser_version('MSIE')) {
1163 $filename = urlencode($filename);
1164 }
1165
1166 $filesize = $pathisstring ? strlen($path) : filesize($path);
1167
1168 @header('Content-Disposition: attachment; filename='.$filename);
1169 @header('Content-Length: '.$filesize);
1170 if (strpos($CFG->wwwroot, 'https://') === 0) { //https sites - watch out for IE! KB812935 and KB316431
1171 @header('Cache-Control: max-age=10');
1172 @header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
1173 @header('Pragma: ');
1174 } else { //normal http - prevent caching at all cost
1175 @header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0');
1176 @header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
1177 @header('Pragma: no-cache');
1178 }
1179 @header('Accept-Ranges: none'); // Do not allow byteserving
1180
1181 while (@ob_end_flush()); //flush the buffers - save memory and disable sid rewrite
1182 if ($pathisstring) {
1183 echo $path;
1184 } else {
29e3d7e2 1185 @readfile($path);
c87c428e 1186 }
1187
1188 die; //no more chars to output
1189}
1190
1191/**
1192 * Internal callnack function used by send_temp_file()
1193 */
1194function send_temp_file_finished($path) {
1195 if (file_exists($path)) {
1196 @unlink($path);
1197 }
1198}
1199
76ca1ff1 1200/**
1201 * Handles the sending of file data to the user's browser, including support for
1202 * byteranges etc.
ba75ad94 1203 * @param string $path Path of file on disk (including real filename), or actual content of file as string
1204 * @param string $filename Filename to send
1205 * @param int $lifetime Number of seconds before the file should expire from caches (default 24 hours)
1206 * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only
1207 * @param bool $pathisstring If true (default false), $path is the content to send and not the pathname
1208 * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin
1209 * @param string $mimetype Include to specify the MIME type; leave blank to have it guess the type from $filename
b379f7d9 1210 * @param bool $dontdie - return control to caller afterwards. this is not recommended and only used for cleanup tasks.
1211 * if this is passed as true, ignore_user_abort is called. if you don't want your processing to continue on cancel,
29e3d7e2 1212 * you must detect this case when control is returned using connection_aborted. Please not that session is closed
1213 * and should not be reopened.
1214 * @return no return or void, script execution stopped unless $dontdie is true
b9709b76 1215 */
b379f7d9 1216function send_file($path, $filename, $lifetime = 'default' , $filter=0, $pathisstring=false, $forcedownload=false, $mimetype='', $dontdie=false) {
6800d78e 1217 global $CFG, $COURSE, $SESSION;
f1e0649c 1218
b379f7d9 1219 if ($dontdie) {
15325f55 1220 ignore_user_abort(true);
b379f7d9 1221 }
1222
c8a5c6a4 1223 // MDL-11789, apply $CFG->filelifetime here
1224 if ($lifetime === 'default') {
1225 if (!empty($CFG->filelifetime)) {
0cde3ede 1226 $lifetime = $CFG->filelifetime;
c8a5c6a4 1227 } else {
0cde3ede 1228 $lifetime = 86400;
c8a5c6a4 1229 }
1230 }
1231
56949c17 1232 session_get_instance()->write_close(); // unlock session during fileserving
172dd12c 1233
ba4e0b05 1234 // Use given MIME type if specified, otherwise guess it using mimeinfo.
1235 // IE, Konqueror and Opera open html file directly in browser from web even when directed to save it to disk :-O
1236 // only Firefox saves all files locally before opening when content-disposition: attachment stated
1237 $isFF = check_browser_version('Firefox', '1.5'); // only FF > 1.5 properly tested
76ca1ff1 1238 $mimetype = ($forcedownload and !$isFF) ? 'application/x-forcedownload' :
ba4e0b05 1239 ($mimetype ? $mimetype : mimeinfo('type', $filename));
f1e0649c 1240 $lastmodified = $pathisstring ? time() : filemtime($path);
1241 $filesize = $pathisstring ? strlen($path) : filesize($path);
1242
36bddcf5 1243/* - MDL-13949
ee7f231d 1244 //Adobe Acrobat Reader XSS prevention
1245 if ($mimetype=='application/pdf' or mimeinfo('type', $filename)=='application/pdf') {
1246 //please note that it prevents opening of pdfs in browser when http referer disabled
1247 //or file linked from another site; browser caching of pdfs is now disabled too
c57d8874 1248 if (!empty($_SERVER['HTTP_RANGE'])) {
1249 //already byteserving
76ca1ff1 1250 $lifetime = 1; // >0 needed for byteserving
c57d8874 1251 } else if (empty($_SERVER['HTTP_REFERER']) or strpos($_SERVER['HTTP_REFERER'], $CFG->wwwroot)!==0) {
ee7f231d 1252 $mimetype = 'application/x-forcedownload';
1253 $forcedownload = true;
1254 $lifetime = 0;
1255 } else {
76ca1ff1 1256 $lifetime = 1; // >0 needed for byteserving
ee7f231d 1257 }
b8806ccc 1258 }
36bddcf5 1259*/
f3f7610c 1260
69faecce 1261 //IE compatibiltiy HACK!
4c8c65ec 1262 if (ini_get('zlib.output_compression')) {
69faecce 1263 ini_set('zlib.output_compression', 'Off');
1264 }
1265
4c8c65ec 1266 //try to disable automatic sid rewrite in cookieless mode
8914cb82 1267 @ini_set("session.use_trans_sid", "false");
4c8c65ec 1268
1269 //do not put '@' before the next header to detect incorrect moodle configurations,
1270 //error should be better than "weird" empty lines for admins/users
1271 //TODO: should we remove all those @ before the header()? Are all of the values supported on all servers?
1272 header('Last-Modified: '. gmdate('D, d M Y H:i:s', $lastmodified) .' GMT');
1273
4f047de2 1274 // if user is using IE, urlencode the filename so that multibyte file name will show up correctly on popup
1275 if (check_browser_version('MSIE')) {
4638009b 1276 $filename = rawurlencode($filename);
4f047de2 1277 }
1278
4c8c65ec 1279 if ($forcedownload) {
4638009b 1280 @header('Content-Disposition: attachment; filename="'.$filename.'"');
4c8c65ec 1281 } else {
4638009b 1282 @header('Content-Disposition: inline; filename="'.$filename.'"');
4c8c65ec 1283 }
1284
f1e0649c 1285 if ($lifetime > 0) {
4c8c65ec 1286 @header('Cache-Control: max-age='.$lifetime);
1287 @header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
f1e0649c 1288 @header('Pragma: ');
4c8c65ec 1289
1290 if (empty($CFG->disablebyteserving) && !$pathisstring && $mimetype != 'text/plain' && $mimetype != 'text/html') {
1291
1292 @header('Accept-Ranges: bytes');
1293
1294 if (!empty($_SERVER['HTTP_RANGE']) && strpos($_SERVER['HTTP_RANGE'],'bytes=') !== FALSE) {
1295 // byteserving stuff - for acrobat reader and download accelerators
1296 // see: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35
1297 // inspired by: http://www.coneural.org/florian/papers/04_byteserving.php
1298 $ranges = false;
1299 if (preg_match_all('/(\d*)-(\d*)/', $_SERVER['HTTP_RANGE'], $ranges, PREG_SET_ORDER)) {
1300 foreach ($ranges as $key=>$value) {
1301 if ($ranges[$key][1] == '') {
1302 //suffix case
1303 $ranges[$key][1] = $filesize - $ranges[$key][2];
1304 $ranges[$key][2] = $filesize - 1;
1305 } else if ($ranges[$key][2] == '' || $ranges[$key][2] > $filesize - 1) {
1306 //fix range length
1307 $ranges[$key][2] = $filesize - 1;
1308 }
1309 if ($ranges[$key][2] != '' && $ranges[$key][2] < $ranges[$key][1]) {
1310 //invalid byte-range ==> ignore header
1311 $ranges = false;
1312 break;
1313 }
1314 //prepare multipart header
1315 $ranges[$key][0] = "\r\n--".BYTESERVING_BOUNDARY."\r\nContent-Type: $mimetype\r\n";
1316 $ranges[$key][0] .= "Content-Range: bytes {$ranges[$key][1]}-{$ranges[$key][2]}/$filesize\r\n\r\n";
1317 }
1318 } else {
1319 $ranges = false;
1320 }
1321 if ($ranges) {
172dd12c 1322 $handle = fopen($filename, 'rb');
1323 byteserving_send_file($handle, $mimetype, $ranges, $filesize);
4c8c65ec 1324 }
1325 }
1326 } else {
1327 /// Do not byteserve (disabled, strings, text and html files).
1328 @header('Accept-Ranges: none');
1329 }
1330 } else { // Do not cache files in proxies and browsers
85e00626 1331 if (strpos($CFG->wwwroot, 'https://') === 0) { //https sites - watch out for IE! KB812935 and KB316431
1332 @header('Cache-Control: max-age=10');
4c8c65ec 1333 @header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
85e00626 1334 @header('Pragma: ');
1335 } else { //normal http - prevent caching at all cost
1336 @header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0');
4c8c65ec 1337 @header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
85e00626 1338 @header('Pragma: no-cache');
1339 }
4c8c65ec 1340 @header('Accept-Ranges: none'); // Do not allow byteserving when caching disabled
69faecce 1341 }
f1e0649c 1342
b9709b76 1343 if (empty($filter)) {
0a2092a3 1344 if ($mimetype == 'text/html' && !empty($CFG->usesid)) {
4c8c65ec 1345 //cookieless mode - rewrite links
1346 @header('Content-Type: text/html');
1347 $path = $pathisstring ? $path : implode('', file($path));
0ad6b20c 1348 $path = sid_ob_rewrite($path);
4c8c65ec 1349 $filesize = strlen($path);
1350 $pathisstring = true;
1351 } else if ($mimetype == 'text/plain') {
810944af 1352 @header('Content-Type: Text/plain; charset=utf-8'); //add encoding
f1e0649c 1353 } else {
4c8c65ec 1354 @header('Content-Type: '.$mimetype);
f1e0649c 1355 }
4c8c65ec 1356 @header('Content-Length: '.$filesize);
1357 while (@ob_end_flush()); //flush the buffers - save memory and disable sid rewrite
f1e0649c 1358 if ($pathisstring) {
1359 echo $path;
4c8c65ec 1360 } else {
29e3d7e2 1361 @readfile($path);
f1e0649c 1362 }
1363 } else { // Try to put the file through filters
f1e0649c 1364 if ($mimetype == 'text/html') {
a17c57b5 1365 $options = new object();
f1e0649c 1366 $options->noclean = true;
a17c57b5 1367 $options->nocache = true; // temporary workaround for MDL-5136
f1e0649c 1368 $text = $pathisstring ? $path : implode('', file($path));
76ca1ff1 1369
3ace5ee4 1370 $text = file_modify_html_header($text);
60f9e36e 1371 $output = format_text($text, FORMAT_HTML, $options, $COURSE->id);
0a2092a3 1372 if (!empty($CFG->usesid)) {
4c8c65ec 1373 //cookieless mode - rewrite links
0ad6b20c 1374 $output = sid_ob_rewrite($output);
4c8c65ec 1375 }
f1e0649c 1376
4c8c65ec 1377 @header('Content-Length: '.strlen($output));
1378 @header('Content-Type: text/html');
1379 while (@ob_end_flush()); //flush the buffers - save memory and disable sid rewrite
f1e0649c 1380 echo $output;
b9709b76 1381 // only filter text if filter all files is selected
1382 } else if (($mimetype == 'text/plain') and ($filter == 1)) {
60f9e36e 1383 $options = new object();
f1e0649c 1384 $options->newlines = false;
1385 $options->noclean = true;
1386 $text = htmlentities($pathisstring ? $path : implode('', file($path)));
60f9e36e 1387 $output = '<pre>'. format_text($text, FORMAT_MOODLE, $options, $COURSE->id) .'</pre>';
0a2092a3 1388 if (!empty($CFG->usesid)) {
4c8c65ec 1389 //cookieless mode - rewrite links
0ad6b20c 1390 $output = sid_ob_rewrite($output);
4c8c65ec 1391 }
f1e0649c 1392
4c8c65ec 1393 @header('Content-Length: '.strlen($output));
810944af 1394 @header('Content-Type: text/html; charset=utf-8'); //add encoding
4c8c65ec 1395 while (@ob_end_flush()); //flush the buffers - save memory and disable sid rewrite
f1e0649c 1396 echo $output;
1397 } else { // Just send it out raw
4c8c65ec 1398 @header('Content-Length: '.$filesize);
1399 @header('Content-Type: '.$mimetype);
1400 while (@ob_end_flush()); //flush the buffers - save memory and disable sid rewrite
f1e0649c 1401 if ($pathisstring) {
1402 echo $path;
1403 }else {
29e3d7e2 1404 @readfile($path);
f1e0649c 1405 }
1406 }
1407 }
b379f7d9 1408 if ($dontdie) {
1409 return;
1410 }
f1e0649c 1411 die; //no more chars to output!!!
1412}
1413
172dd12c 1414/**
1415 * Handles the sending of file data to the user's browser, including support for
1416 * byteranges etc.
1417 * @param object $stored_file local file object
1418 * @param int $lifetime Number of seconds before the file should expire from caches (default 24 hours)
1419 * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only
1420 * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin
1421 * @param string $filename Override filename
1422 * @param string $mimetype Include to specify the MIME type; leave blank to have it guess the type from $filename
b379f7d9 1423 * @param bool $dontdie - return control to caller afterwards. this is not recommended and only used for cleanup tasks.
1424 * if this is passed as true, ignore_user_abort is called. if you don't want your processing to continue on cancel,
29e3d7e2 1425 * you must detect this case when control is returned using connection_aborted. Please not that session is closed
1426 * and should not be reopened.
1427 * @return no return or void, script execution stopped unless $dontdie is true
172dd12c 1428 */
b379f7d9 1429function send_stored_file($stored_file, $lifetime=86400 , $filter=0, $forcedownload=false, $filename=null, $dontdie=false) {
172dd12c 1430 global $CFG, $COURSE, $SESSION;
1431
b379f7d9 1432 if ($dontdie) {
15325f55 1433 ignore_user_abort(true);
b379f7d9 1434 }
1435
56949c17 1436 session_get_instance()->write_close(); // unlock session during fileserving
172dd12c 1437
1438 // Use given MIME type if specified, otherwise guess it using mimeinfo.
1439 // IE, Konqueror and Opera open html file directly in browser from web even when directed to save it to disk :-O
1440 // only Firefox saves all files locally before opening when content-disposition: attachment stated
1441 $filename = is_null($filename) ? $stored_file->get_filename() : $filename;
1442 $isFF = check_browser_version('Firefox', '1.5'); // only FF > 1.5 properly tested
1443 $mimetype = ($forcedownload and !$isFF) ? 'application/x-forcedownload' :
1444 ($stored_file->get_mimetype() ? $stored_file->get_mimetype() : mimeinfo('type', $filename));
1445 $lastmodified = $stored_file->get_timemodified();
1446 $filesize = $stored_file->get_filesize();
1447
1448 //IE compatibiltiy HACK!
1449 if (ini_get('zlib.output_compression')) {
1450 ini_set('zlib.output_compression', 'Off');
1451 }
1452
1453 //try to disable automatic sid rewrite in cookieless mode
1454 @ini_set("session.use_trans_sid", "false");
1455
1456 //do not put '@' before the next header to detect incorrect moodle configurations,
1457 //error should be better than "weird" empty lines for admins/users
1458 //TODO: should we remove all those @ before the header()? Are all of the values supported on all servers?
1459 header('Last-Modified: '. gmdate('D, d M Y H:i:s', $lastmodified) .' GMT');
1460
1461 // if user is using IE, urlencode the filename so that multibyte file name will show up correctly on popup
1462 if (check_browser_version('MSIE')) {
1463 $filename = rawurlencode($filename);
1464 }
1465
1466 if ($forcedownload) {
1467 @header('Content-Disposition: attachment; filename="'.$filename.'"');
1468 } else {
1469 @header('Content-Disposition: inline; filename="'.$filename.'"');
1470 }
1471
1472 if ($lifetime > 0) {
1473 @header('Cache-Control: max-age='.$lifetime);
1474 @header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
1475 @header('Pragma: ');
1476
1477 if (empty($CFG->disablebyteserving) && $mimetype != 'text/plain' && $mimetype != 'text/html') {
1478
1479 @header('Accept-Ranges: bytes');
1480
1481 if (!empty($_SERVER['HTTP_RANGE']) && strpos($_SERVER['HTTP_RANGE'],'bytes=') !== FALSE) {
1482 // byteserving stuff - for acrobat reader and download accelerators
1483 // see: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35
1484 // inspired by: http://www.coneural.org/florian/papers/04_byteserving.php
1485 $ranges = false;
1486 if (preg_match_all('/(\d*)-(\d*)/', $_SERVER['HTTP_RANGE'], $ranges, PREG_SET_ORDER)) {
1487 foreach ($ranges as $key=>$value) {
1488 if ($ranges[$key][1] == '') {
1489 //suffix case
1490 $ranges[$key][1] = $filesize - $ranges[$key][2];
1491 $ranges[$key][2] = $filesize - 1;
1492 } else if ($ranges[$key][2] == '' || $ranges[$key][2] > $filesize - 1) {
1493 //fix range length
1494 $ranges[$key][2] = $filesize - 1;
1495 }
1496 if ($ranges[$key][2] != '' && $ranges[$key][2] < $ranges[$key][1]) {
1497 //invalid byte-range ==> ignore header
1498 $ranges = false;
1499 break;
1500 }
1501 //prepare multipart header
1502 $ranges[$key][0] = "\r\n--".BYTESERVING_BOUNDARY."\r\nContent-Type: $mimetype\r\n";
1503 $ranges[$key][0] .= "Content-Range: bytes {$ranges[$key][1]}-{$ranges[$key][2]}/$filesize\r\n\r\n";
1504 }
1505 } else {
1506 $ranges = false;
1507 }
1508 if ($ranges) {
1509 byteserving_send_file($stored_file->get_content_file_handle(), $mimetype, $ranges, $filesize);
1510 }
1511 }
1512 } else {
1513 /// Do not byteserve (disabled, strings, text and html files).
1514 @header('Accept-Ranges: none');
1515 }
1516 } else { // Do not cache files in proxies and browsers
1517 if (strpos($CFG->wwwroot, 'https://') === 0) { //https sites - watch out for IE! KB812935 and KB316431
1518 @header('Cache-Control: max-age=10');
1519 @header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
1520 @header('Pragma: ');
1521 } else { //normal http - prevent caching at all cost
1522 @header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0');
1523 @header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
1524 @header('Pragma: no-cache');
1525 }
1526 @header('Accept-Ranges: none'); // Do not allow byteserving when caching disabled
1527 }
1528
1529 if (empty($filter)) {
1530 $filtered = false;
0a2092a3 1531 if ($mimetype == 'text/html' && !empty($CFG->usesid)) {
172dd12c 1532 //cookieless mode - rewrite links
1533 @header('Content-Type: text/html');
1534 $text = $stored_file->get_content();
0ad6b20c 1535 $text = sid_ob_rewrite($text);
172dd12c 1536 $filesize = strlen($text);
1537 $filtered = true;
1538 } else if ($mimetype == 'text/plain') {
1539 @header('Content-Type: Text/plain; charset=utf-8'); //add encoding
1540 } else {
1541 @header('Content-Type: '.$mimetype);
1542 }
1543 @header('Content-Length: '.$filesize);
1544 while (@ob_end_flush()); //flush the buffers - save memory and disable sid rewrite
1545 if ($filtered) {
1546 echo $text;
1547 } else {
1548 $stored_file->readfile();
1549 }
1550
1551 } else { // Try to put the file through filters
1552 if ($mimetype == 'text/html') {
1553 $options = new object();
1554 $options->noclean = true;
1555 $options->nocache = true; // temporary workaround for MDL-5136
1556 $text = $stored_file->get_content();
1557 $text = file_modify_html_header($text);
1558 $output = format_text($text, FORMAT_HTML, $options, $COURSE->id);
0a2092a3 1559 if (!empty($CFG->usesid)) {
172dd12c 1560 //cookieless mode - rewrite links
0ad6b20c 1561 $output = sid_ob_rewrite($output);
172dd12c 1562 }
1563
1564 @header('Content-Length: '.strlen($output));
1565 @header('Content-Type: text/html');
1566 while (@ob_end_flush()); //flush the buffers - save memory and disable sid rewrite
1567 echo $output;
1568 // only filter text if filter all files is selected
1569 } else if (($mimetype == 'text/plain') and ($filter == 1)) {
1570 $options = new object();
1571 $options->newlines = false;
1572 $options->noclean = true;
1573 $text = $stored_file->get_content();
1574 $output = '<pre>'. format_text($text, FORMAT_MOODLE, $options, $COURSE->id) .'</pre>';
0a2092a3 1575 if (!empty($CFG->usesid)) {
172dd12c 1576 //cookieless mode - rewrite links
0ad6b20c 1577 $output = sid_ob_rewrite($output);
172dd12c 1578 }
1579
1580 @header('Content-Length: '.strlen($output));
1581 @header('Content-Type: text/html; charset=utf-8'); //add encoding
1582 while (@ob_end_flush()); //flush the buffers - save memory and disable sid rewrite
1583 echo $output;
1584 } else { // Just send it out raw
1585 @header('Content-Length: '.$filesize);
1586 @header('Content-Type: '.$mimetype);
1587 while (@ob_end_flush()); //flush the buffers - save memory and disable sid rewrite
1588 $stored_file->readfile();
1589 }
1590 }
b379f7d9 1591 if ($dontdie) {
1592 return;
1593 }
172dd12c 1594 die; //no more chars to output!!!
1595}
1596
a43b5308 1597function get_records_csv($file, $table) {
f33e1ed4 1598 global $CFG, $DB;
599f38f9 1599
f33e1ed4 1600 if (!$metacolumns = $DB->get_columns($table)) {
599f38f9 1601 return false;
1602 }
1603
a77b98eb 1604 if(!($handle = @fopen($file, 'r'))) {
5a2a5331 1605 print_error('get_records_csv failed to open '.$file);
599f38f9 1606 }
1607
1608 $fieldnames = fgetcsv($handle, 4096);
1609 if(empty($fieldnames)) {
1610 fclose($handle);
1611 return false;
1612 }
1613
1614 $columns = array();
1615
1616 foreach($metacolumns as $metacolumn) {
1617 $ord = array_search($metacolumn->name, $fieldnames);
1618 if(is_int($ord)) {
1619 $columns[$metacolumn->name] = $ord;
1620 }
1621 }
1622
1623 $rows = array();
1624
1625 while (($data = fgetcsv($handle, 4096)) !== false) {
1626 $item = new stdClass;
1627 foreach($columns as $name => $ord) {
1628 $item->$name = $data[$ord];
1629 }
1630 $rows[] = $item;
1631 }
1632
1633 fclose($handle);
1634 return $rows;
1635}
1636
a77b98eb 1637function put_records_csv($file, $records, $table = NULL) {
f33e1ed4 1638 global $CFG, $DB;
a77b98eb 1639
a1e93da2 1640 if (empty($records)) {
a77b98eb 1641 return true;
1642 }
1643
1644 $metacolumns = NULL;
f33e1ed4 1645 if ($table !== NULL && !$metacolumns = $DB->get_columns($table)) {
a77b98eb 1646 return false;
1647 }
1648
a1e93da2 1649 echo "x";
1650
a77b98eb 1651 if(!($fp = @fopen($CFG->dataroot.'/temp/'.$file, 'w'))) {
5a2a5331 1652 print_error('put_records_csv failed to open '.$file);
a77b98eb 1653 }
1654
a43b5308 1655 $proto = reset($records);
1656 if(is_object($proto)) {
1657 $fields_records = array_keys(get_object_vars($proto));
1658 }
1659 else if(is_array($proto)) {
1660 $fields_records = array_keys($proto);
1661 }
1662 else {
1663 return false;
1664 }
a1e93da2 1665 echo "x";
a77b98eb 1666
1667 if(!empty($metacolumns)) {
1668 $fields_table = array_map(create_function('$a', 'return $a->name;'), $metacolumns);
1669 $fields = array_intersect($fields_records, $fields_table);
1670 }
1671 else {
1672 $fields = $fields_records;
1673 }
1674
1675 fwrite($fp, implode(',', $fields));
1676 fwrite($fp, "\r\n");
1677
1678 foreach($records as $record) {
a43b5308 1679 $array = (array)$record;
a77b98eb 1680 $values = array();
1681 foreach($fields as $field) {
a43b5308 1682 if(strpos($array[$field], ',')) {
1683 $values[] = '"'.str_replace('"', '\"', $array[$field]).'"';
a77b98eb 1684 }
1685 else {
a43b5308 1686 $values[] = $array[$field];
a77b98eb 1687 }
1688 }
1689 fwrite($fp, implode(',', $values)."\r\n");
1690 }
1691
1692 fclose($fp);
1693 return true;
1694}
1695
f401cc97 1696
34763a79 1697/**
76ca1ff1 1698 * Recursively delete the file or folder with path $location. That is,
34763a79 1699 * if it is a file delete it. If it is a folder, delete all its content
db3a0b34 1700 * then delete it. If $location does not exist to start, that is not
1701 * considered an error.
76ca1ff1 1702 *
34763a79 1703 * @param $location the path to remove.
1704 */
4c8c65ec 1705function fulldelete($location) {
f401cc97 1706 if (is_dir($location)) {
1707 $currdir = opendir($location);
1708 while (false !== ($file = readdir($currdir))) {
1709 if ($file <> ".." && $file <> ".") {
1710 $fullfile = $location."/".$file;
4c8c65ec 1711 if (is_dir($fullfile)) {
f401cc97 1712 if (!fulldelete($fullfile)) {
1713 return false;
1714 }
1715 } else {
1716 if (!unlink($fullfile)) {
1717 return false;
1718 }
4c8c65ec 1719 }
f401cc97 1720 }
4c8c65ec 1721 }
f401cc97 1722 closedir($currdir);
1723 if (! rmdir($location)) {
1724 return false;
1725 }
1726
34763a79 1727 } else if (file_exists($location)) {
f401cc97 1728 if (!unlink($location)) {
1729 return false;
1730 }
1731 }
1732 return true;
1733}
1734
4c8c65ec 1735/**
1736 * Send requested byterange of file.
1737 */
172dd12c 1738function byteserving_send_file($handle, $mimetype, $ranges, $filesize) {
4c8c65ec 1739 $chunksize = 1*(1024*1024); // 1MB chunks - must be less than 2MB!
4c8c65ec 1740 if ($handle === false) {
1741 die;
1742 }
1743 if (count($ranges) == 1) { //only one range requested
1744 $length = $ranges[0][2] - $ranges[0][1] + 1;
1745 @header('HTTP/1.1 206 Partial content');
1746 @header('Content-Length: '.$length);
172dd12c 1747 @header('Content-Range: bytes '.$ranges[0][1].'-'.$ranges[0][2].'/'.$filesize);
4c8c65ec 1748 @header('Content-Type: '.$mimetype);
1749 while (@ob_end_flush()); //flush the buffers - save memory and disable sid rewrite
1750 $buffer = '';
1751 fseek($handle, $ranges[0][1]);
1752 while (!feof($handle) && $length > 0) {
68913aec 1753 @set_time_limit(60*60); //reset time limit to 60 min - should be enough for 1 MB chunk
4c8c65ec 1754 $buffer = fread($handle, ($chunksize < $length ? $chunksize : $length));
1755 echo $buffer;
1756 flush();
1757 $length -= strlen($buffer);
1758 }
1759 fclose($handle);
1760 die;
1761 } else { // multiple ranges requested - not tested much
1762 $totallength = 0;
1763 foreach($ranges as $range) {
aba588a7 1764 $totallength += strlen($range[0]) + $range[2] - $range[1] + 1;
4c8c65ec 1765 }
aba588a7 1766 $totallength += strlen("\r\n--".BYTESERVING_BOUNDARY."--\r\n");
4c8c65ec 1767 @header('HTTP/1.1 206 Partial content');
1768 @header('Content-Length: '.$totallength);
1769 @header('Content-Type: multipart/byteranges; boundary='.BYTESERVING_BOUNDARY);
1770 //TODO: check if "multipart/x-byteranges" is more compatible with current readers/browsers/servers
1771 while (@ob_end_flush()); //flush the buffers - save memory and disable sid rewrite
1772 foreach($ranges as $range) {
1773 $length = $range[2] - $range[1] + 1;
1774 echo $range[0];
1775 $buffer = '';
1776 fseek($handle, $range[1]);
1777 while (!feof($handle) && $length > 0) {
68913aec 1778 @set_time_limit(60*60); //reset time limit to 60 min - should be enough for 1 MB chunk
4c8c65ec 1779 $buffer = fread($handle, ($chunksize < $length ? $chunksize : $length));
1780 echo $buffer;
1781 flush();
1782 $length -= strlen($buffer);
1783 }
1784 }
1785 echo "\r\n--".BYTESERVING_BOUNDARY."--\r\n";
1786 fclose($handle);
1787 die;
1788 }
1789}
f401cc97 1790
3ace5ee4 1791/**
1792 * add includes (js and css) into uploaded files
1793 * before returning them, useful for themes and utf.js includes
1794 * @param string text - text to search and replace
1795 * @return string - text with added head includes
1796 */
1797function file_modify_html_header($text) {
1798 // first look for <head> tag
1799 global $CFG;
76ca1ff1 1800
3ace5ee4 1801 $stylesheetshtml = '';
1802 foreach ($CFG->stylesheets as $stylesheet) {
1803 $stylesheetshtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
76ca1ff1 1804 }
1805
5b8fa09b 1806 $ufo = '';
1807 if (filter_is_enabled('filter/mediaplugin')) {
76ca1ff1 1808 // this script is needed by most media filter plugins.
7a1f5e93 1809 $ufo = get_require_js_code(array($CFG->wwwroot . '/lib/ufo.js'));
3ace5ee4 1810 }
76ca1ff1 1811
3ace5ee4 1812 preg_match('/\<head\>|\<HEAD\>/', $text, $matches);
1813 if ($matches) {
1814 $replacement = '<head>'.$ufo.$stylesheetshtml;
1815 $text = preg_replace('/\<head\>|\<HEAD\>/', $replacement, $text, 1);
76ca1ff1 1816 return $text;
3ace5ee4 1817 }
76ca1ff1 1818
3ace5ee4 1819 // if not, look for <html> tag, and stick <head> right after
1820 preg_match('/\<html\>|\<HTML\>/', $text, $matches);
1821 if ($matches) {
1822 // replace <html> tag with <html><head>includes</head>
13534ef7 1823 $replacement = '<html>'."\n".'<head>'.$ufo.$stylesheetshtml.'</head>';
3ace5ee4 1824 $text = preg_replace('/\<html\>|\<HTML\>/', $replacement, $text, 1);
76ca1ff1 1825 return $text;
3ace5ee4 1826 }
76ca1ff1 1827
3ace5ee4 1828 // if not, look for <body> tag, and stick <head> before body
1829 preg_match('/\<body\>|\<BODY\>/', $text, $matches);
1830 if ($matches) {
13534ef7 1831 $replacement = '<head>'.$ufo.$stylesheetshtml.'</head>'."\n".'<body>';
3ace5ee4 1832 $text = preg_replace('/\<body\>|\<BODY\>/', $replacement, $text, 1);
76ca1ff1 1833 return $text;
1834 }
1835
3ace5ee4 1836 // if not, just stick a <head> tag at the beginning
1837 $text = '<head>'.$ufo.$stylesheetshtml.'</head>'."\n".$text;
1838 return $text;
1839}
1840
bb2c046d 1841/**
1842 * RESTful cURL class
1843 *
1844 * This is a wrapper class for curl, it is quite easy to use:
1845 *
1846 * $c = new curl;
1847 * // enable cache
1848 * $c = new curl(array('cache'=>true));
1849 * // enable cookie
1850 * $c = new curl(array('cookie'=>true));
1851 * // enable proxy
1852 * $c = new curl(array('proxy'=>true));
1853 *
1854 * // HTTP GET Method
1855 * $html = $c->get('http://example.com');
1856 * // HTTP POST Method
1857 * $html = $c->post('http://example.com/', array('q'=>'words', 'name'=>'moodle'));
1858 * // HTTP PUT Method
1859 * $html = $c->put('http://example.com/', array('file'=>'/var/www/test.txt');
1860 *
1861 * @author Dongsheng Cai <dongsheng@cvs.moodle.org>
1862 * @version 0.4 dev
1863 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
1864 */
1865
1866class curl {
1867 public $cache = false;
1868 public $proxy = false;
1869 public $version = '0.4 dev';
1870 public $response = array();
1871 public $header = array();
1872 public $info;
1873 public $error;
1874
1875 private $options;
1876 private $proxy_host = '';
1877 private $proxy_auth = '';
1878 private $proxy_type = '';
1879 private $debug = false;
1880 private $cookie = false;
1881
1882 public function __construct($options = array()){
1883 global $CFG;
1884 if (!function_exists('curl_init')) {
1885 $this->error = 'cURL module must be enabled!';
1886 trigger_error($this->error, E_USER_ERROR);
1887 return false;
1888 }
1889 // the options of curl should be init here.
1890 $this->resetopt();
1891 if (!empty($options['debug'])) {
1892 $this->debug = true;
1893 }
1894 if(!empty($options['cookie'])) {
1895 if($options['cookie'] === true) {
1896 $this->cookie = $CFG->dataroot.'/curl_cookie.txt';
1897 } else {
1898 $this->cookie = $options['cookie'];
1899 }
1900 }
1901 if (!empty($options['cache'])) {
1902 if (class_exists('curl_cache')) {
5430f05b 1903 if (!empty($options['module_cache'])) {
1904 $this->cache = new curl_cache($options['module_cache']);
1905 } else {
1906 $this->cache = new curl_cache('misc');
1907 }
bb2c046d 1908 }
1909 }
d04ce87f 1910 if (!empty($CFG->proxyhost)) {
1911 if (empty($CFG->proxyport)) {
1912 $this->proxy_host = $CFG->proxyhost;
1913 } else {
1914 $this->proxy_host = $CFG->proxyhost.':'.$CFG->proxyport;
1915 }
1916 if (!empty($CFG->proxyuser) and !empty($CFG->proxypassword)) {
1917 $this->proxy_auth = $CFG->proxyuser.':'.$CFG->proxypassword;
1918 $this->setopt(array(
1919 'proxyauth'=> CURLAUTH_BASIC | CURLAUTH_NTLM,
1920 'proxyuserpwd'=>$this->proxy_auth));
1921 }
1922 if (!empty($CFG->proxytype)) {
1923 if ($CFG->proxytype == 'SOCKS5') {
1924 $this->proxy_type = CURLPROXY_SOCKS5;
bb2c046d 1925 } else {
d04ce87f 1926 $this->proxy_type = CURLPROXY_HTTP;
f6d3e2c4 1927 $this->setopt(array('httpproxytunnel'=>false));
bb2c046d 1928 }
d04ce87f 1929 $this->setopt(array('proxytype'=>$this->proxy_type));
bb2c046d 1930 }
d04ce87f 1931 }
1932 if (!empty($this->proxy_host)) {
1933 $this->proxy = array('proxy'=>$this->proxy_host);
bb2c046d 1934 }
1935 }
1936 public function resetopt(){
1937 $this->options = array();
1938 $this->options['CURLOPT_USERAGENT'] = 'MoodleBot/1.0';
1939 // True to include the header in the output
1940 $this->options['CURLOPT_HEADER'] = 0;
1941 // True to Exclude the body from the output
1942 $this->options['CURLOPT_NOBODY'] = 0;
1943 // TRUE to follow any "Location: " header that the server
1944 // sends as part of the HTTP header (note this is recursive,
1945 // PHP will follow as many "Location: " headers that it is sent,
1946 // unless CURLOPT_MAXREDIRS is set).
1947 $this->options['CURLOPT_FOLLOWLOCATION'] = 1;
1948 $this->options['CURLOPT_MAXREDIRS'] = 10;
1949 $this->options['CURLOPT_ENCODING'] = '';
1950 // TRUE to return the transfer as a string of the return
1951 // value of curl_exec() instead of outputting it out directly.
1952 $this->options['CURLOPT_RETURNTRANSFER'] = 1;
1953 $this->options['CURLOPT_BINARYTRANSFER'] = 0;
1954 $this->options['CURLOPT_SSL_VERIFYPEER'] = 0;
1955 $this->options['CURLOPT_SSL_VERIFYHOST'] = 2;
6135bd45 1956 $this->options['CURLOPT_CONNECTTIMEOUT'] = 30;
bb2c046d 1957 }
1958
1959 /**
1960 * Reset Cookie
1961 *
1962 * @param array $options If array is null, this function will
1963 * reset the options to default value.
1964 *
1965 */
1966 public function resetcookie() {
1967 if (!empty($this->cookie)) {
1968 if (is_file($this->cookie)) {
1969 $fp = fopen($this->cookie, 'w');
1970 if (!empty($fp)) {
1971 fwrite($fp, '');
1972 fclose($fp);
1973 }
1974 }
1975 }
1976 }
1977
1978 /**
1979 * Set curl options
1980 *
1981 * @param array $options If array is null, this function will
1982 * reset the options to default value.
1983 *
1984 */
1985 public function setopt($options = array()) {
1986 if (is_array($options)) {
1987 foreach($options as $name => $val){
1988 if (stripos($name, 'CURLOPT_') === false) {
1989 $name = strtoupper('CURLOPT_'.$name);
1990 }
1991 $this->options[$name] = $val;
1992 }
1993 }
1994 }
1995 /**
1996 * Reset http method
1997 *
1998 */
1999 public function cleanopt(){
2000 unset($this->options['CURLOPT_HTTPGET']);
2001 unset($this->options['CURLOPT_POST']);
2002 unset($this->options['CURLOPT_POSTFIELDS']);
2003 unset($this->options['CURLOPT_PUT']);
2004 unset($this->options['CURLOPT_INFILE']);
2005 unset($this->options['CURLOPT_INFILESIZE']);
2006 unset($this->options['CURLOPT_CUSTOMREQUEST']);
2007 }
2008
2009 /**
2010 * Set HTTP Request Header
2011 *
2012 * @param array $headers
2013 *
2014 */
2015 public function setHeader($header) {
2016 if (is_array($header)){
2017 foreach ($header as $v) {
2018 $this->setHeader($v);
2019 }
2020 } else {
2021 $this->header[] = $header;
2022 }
2023 }
2024 /**
2025 * Set HTTP Response Header
2026 *
2027 */
2028 public function getResponse(){
2029 return $this->response;
2030 }
2031 /**
2032 * private callback function
2033 * Formatting HTTP Response Header
2034 *
2035 */
2036 private function formatHeader($ch, $header)
2037 {
2038 $this->count++;
2039 if (strlen($header) > 2) {
2040 list($key, $value) = explode(" ", rtrim($header, "\r\n"), 2);
2041 $key = rtrim($key, ':');
2042 if (!empty($this->response[$key])) {
2043 if (is_array($this->response[$key])){
2044 $this->response[$key][] = $value;
2045 } else {
2046 $tmp = $this->response[$key];
2047 $this->response[$key] = array();
2048 $this->response[$key][] = $tmp;
2049 $this->response[$key][] = $value;
2050
2051 }
2052 } else {
2053 $this->response[$key] = $value;
2054 }
2055 }
2056 return strlen($header);
2057 }
2058
2059 /**
2060 * Set options for individual curl instance
2061 */
2062 private function apply_opt($curl, $options) {
2063 // Clean up
2064 $this->cleanopt();
2065 // set cookie
2066 if (!empty($this->cookie) || !empty($options['cookie'])) {
2067 $this->setopt(array('cookiejar'=>$this->cookie,
2068 'cookiefile'=>$this->cookie
2069 ));
2070 }
2071
2072 // set proxy
2073 if (!empty($this->proxy) || !empty($options['proxy'])) {
2074 $this->setopt($this->proxy);
2075 }
2076 $this->setopt($options);
2077 // reset before set options
2078 curl_setopt($curl, CURLOPT_HEADERFUNCTION, array(&$this,'formatHeader'));
2079 // set headers
2080 if (empty($this->header)){
2081 $this->setHeader(array(
2082 'User-Agent: MoodleBot/1.0',
2083 'Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7',
2084 'Connection: keep-alive'
2085 ));
2086 }
2087 curl_setopt($curl, CURLOPT_HTTPHEADER, $this->header);
2088
2089 if ($this->debug){
2090 echo '<h1>Options</h1>';
2091 var_dump($this->options);
2092 echo '<h1>Header</h1>';
2093 var_dump($this->header);
2094 }
2095
2096 // set options
2097 foreach($this->options as $name => $val) {
2098 if (is_string($name)) {
2099 $name = constant(strtoupper($name));
2100 }
2101 curl_setopt($curl, $name, $val);
2102 }
2103 return $curl;
2104 }
2105 /*
2106 * Download multiple files in parallel
2107 * $c = new curl;
2108 * $c->download(array(
172dd12c 2109 * array('url'=>'http://localhost/', 'file'=>fopen('a', 'wb')),
bb2c046d 2110 * array('url'=>'http://localhost/20/', 'file'=>fopen('b', 'wb'))
2111 * ));
2112 */
2113 public function download($requests, $options = array()) {
2114 $options['CURLOPT_BINARYTRANSFER'] = 1;
2115 $options['RETURNTRANSFER'] = false;
2116 return $this->multi($requests, $options);
2117 }
2118 /*
2119 * Mulit HTTP Requests
2120 * This function could run multi-requests in parallel.
2121 */
2122 protected function multi($requests, $options = array()) {
2123 $count = count($requests);
2124 $handles = array();
2125 $results = array();
2126 $main = curl_multi_init();
2127 for ($i = 0; $i < $count; $i++) {
2128 $url = $requests[$i];
2129 foreach($url as $n=>$v){
2130 $options[$n] = $url[$n];
2131 }
2132 $handles[$i] = curl_init($url['url']);
2133 $this->apply_opt($handles[$i], $options);
2134 curl_multi_add_handle($main, $handles[$i]);
2135 }
2136 $running = 0;
2137 do {
2138 curl_multi_exec($main, $running);
2139 } while($running > 0);
2140 for ($i = 0; $i < $count; $i++) {
2141 if (!empty($optins['CURLOPT_RETURNTRANSFER'])) {
2142 $results[] = true;
2143 } else {
2144 $results[] = curl_multi_getcontent($handles[$i]);
2145 }
2146 curl_multi_remove_handle($main, $handles[$i]);
2147 }
2148 curl_multi_close($main);
2149 return $results;
2150 }
2151 /**
2152 * Single HTTP Request
2153 */
2154 protected function request($url, $options = array()){
2155 // create curl instance
2156 $curl = curl_init($url);
2157 $options['url'] = $url;
2158 $this->apply_opt($curl, $options);
2159 if ($this->cache && $ret = $this->cache->get($this->options)) {
2160 return $ret;
2161 } else {
6135bd45 2162 $ret = curl_exec($curl);
bb2c046d 2163 if ($this->cache) {
2164 $this->cache->set($this->options, $ret);
2165 }
2166 }
2167
2168 $this->info = curl_getinfo($curl);
2169 $this->error = curl_error($curl);
2170
2171 if ($this->debug){
2172 echo '<h1>Return Data</h1>';
2173 var_dump($ret);
2174 echo '<h1>Info</h1>';
2175 var_dump($this->info);
2176 echo '<h1>Error</h1>';
2177 var_dump($this->error);
2178 }
2179
2180 curl_close($curl);
2181
6135bd45 2182 if (empty($this->error)){
bb2c046d 2183 return $ret;
2184 } else {
6135bd45 2185 throw new moodle_exception($this->error, 'curl');
bb2c046d 2186 }
2187 }
2188
2189 /**
2190 * HTTP HEAD method
2191 */
2192 public function head($url, $options = array()){
2193 $options['CURLOPT_HTTPGET'] = 0;
2194 $options['CURLOPT_HEADER'] = 1;
2195 $options['CURLOPT_NOBODY'] = 1;
2196 return $this->request($url, $options);
2197 }
2198
2199 /**
2200 * HTTP POST method
2201 */
28c58294 2202 public function post($url, $params = '', $options = array()){
bb2c046d 2203 $options['CURLOPT_POST'] = 1;
28c58294 2204 if (is_array($params)) {
2205 $this->_tmp_file_post_params = array();
2206 foreach ($params as $key => $value) {
2207 if ($value instanceof stored_file) {
2208 $value->add_to_curl_request($this, $key);
2209 } else {
2210 $this->_tmp_file_post_params[$key] = $value;
2211 }
5035a8b4 2212 }
28c58294 2213 $options['CURLOPT_POSTFIELDS'] = $this->_tmp_file_post_params;
2214 unset($this->_tmp_file_post_params);
2215 } else {
2216 // $params is the raw post data
2217 $options['CURLOPT_POSTFIELDS'] = $params;
5035a8b4 2218 }
bb2c046d 2219 return $this->request($url, $options);
2220 }
2221
2222 /**
2223 * HTTP GET method
2224 */
2225 public function get($url, $params = array(), $options = array()){
2226 $options['CURLOPT_HTTPGET'] = 1;
2227
2228 if (!empty($params)){
2229 $url .= (stripos($url, '?') !== false) ? '&' : '?';
2230 $url .= http_build_query($params, '', '&');
2231 }
2232 return $this->request($url, $options);
2233 }
2234
2235 /**
2236 * HTTP PUT method
2237 */
2238 public function put($url, $params = array(), $options = array()){
2239 $file = $params['file'];
2240 if (!is_file($file)){
2241 return null;
2242 }
2243 $fp = fopen($file, 'r');
2244 $size = filesize($file);
2245 $options['CURLOPT_PUT'] = 1;
2246 $options['CURLOPT_INFILESIZE'] = $size;
2247 $options['CURLOPT_INFILE'] = $fp;
2248 if (!isset($this->options['CURLOPT_USERPWD'])){
2249 $this->setopt(array('CURLOPT_USERPWD'=>'anonymous: noreply@moodle.org'));
2250 }
2251 $ret = $this->request($url, $options);
2252 fclose($fp);
2253 return $ret;
2254 }
2255
2256 /**
2257 * HTTP DELETE method
2258 */
2259 public function delete($url, $param = array(), $options = array()){
2260 $options['CURLOPT_CUSTOMREQUEST'] = 'DELETE';
2261 if (!isset($options['CURLOPT_USERPWD'])) {
2262 $options['CURLOPT_USERPWD'] = 'anonymous: noreply@moodle.org';
2263 }
2264 $ret = $this->request($url, $options);
2265 return $ret;
2266 }
2267 /**
2268 * HTTP TRACE method
2269 */
2270 public function trace($url, $options = array()){
2271 $options['CURLOPT_CUSTOMREQUEST'] = 'TRACE';
2272 $ret = $this->request($url, $options);
2273 return $ret;
2274 }
2275 /**
2276 * HTTP OPTIONS method
2277 */
2278 public function options($url, $options = array()){
2279 $options['CURLOPT_CUSTOMREQUEST'] = 'OPTIONS';
2280 $ret = $this->request($url, $options);
2281 return $ret;
2282 }
2283}
2284
2285/**
2286 * This class is used by cURL class, use case:
2287 *
5430f05b 2288 * $CFG->repositorycacheexpire = 120;
2289 * $CFG->curlcache = 120;
2290 *
2291 * $c = new curl(array('cache'=>true), 'module_cache'=>'repository');
bb2c046d 2292 * $ret = $c->get('http://www.google.com');
2293 *
2294 */
2295class curl_cache {
2296 public $dir = '';
5430f05b 2297 /**
2298 *
2299 * @global $CFG
2300 * @param string @module, which module is using curl_cache
2301 *
2302 */
37051e58 2303 function __construct($module = 'repository'){
bb2c046d 2304 global $CFG;
5430f05b 2305 if (!empty($module)) {
2306 $this->dir = $CFG->dataroot.'/cache/'.$module.'/';
2307 } else {
2308 $this->dir = $CFG->dataroot.'/cache/misc/';
bb2c046d 2309 }
5430f05b 2310 if (!file_exists($this->dir)) {
2311 mkdir($this->dir, 0700, true);
bb2c046d 2312 }
5430f05b 2313 if ($module == 'repository') {
2314 if (empty($CFG->repositorycacheexpire)) {
2315 $CFG->repositorycacheexpire = 120;
2316 }
2317 $this->ttl = $CFG->repositorycacheexpire;
2318 } else {
2319 if (empty($CFG->curlcache)) {
2320 $CFG->curlcache = 120;
2321 }
2322 $this->ttl = $CFG->curlcache;
b933a139 2323 }
bb2c046d 2324 }
a08171c5 2325
509f67e3 2326 /**
2327 * TODO Document
2328 */
bb2c046d 2329 public function get($param){
aae85978 2330 global $CFG, $USER;
5430f05b 2331 $this->cleanup($this->ttl);
aae85978 2332 $filename = 'u'.$USER->id.'_'.md5(serialize($param));
bb2c046d 2333 if(file_exists($this->dir.$filename)) {
2334 $lasttime = filemtime($this->dir.$filename);
5430f05b 2335 if(time()-$lasttime > $this->ttl)
d7e122d6 2336 {
bb2c046d 2337 return false;
2338 } else {
2339 $fp = fopen($this->dir.$filename, 'r');
2340 $size = filesize($this->dir.$filename);
2341 $content = fread($fp, $size);
2342 return unserialize($content);
2343 }
2344 }
2345 return false;
2346 }
a08171c5 2347
509f67e3 2348 /**
2349 * TODO Document
2350 */
bb2c046d 2351 public function set($param, $val){
aae85978 2352 global $CFG, $USER;
2353 $filename = 'u'.$USER->id.'_'.md5(serialize($param));
bb2c046d 2354 $fp = fopen($this->dir.$filename, 'w');
2355 fwrite($fp, serialize($val));
2356 fclose($fp);
2357 }
a08171c5 2358
509f67e3 2359 /**
2360 * TODO Document
2361 */
bb2c046d 2362 public function cleanup($expire){
2363 if($dir = opendir($this->dir)){
2364 while (false !== ($file = readdir($dir))) {
2365 if(!is_dir($file) && $file != '.' && $file != '..') {
2366 $lasttime = @filemtime($this->dir.$file);
2367 if(time() - $lasttime > $expire){
2368 @unlink($this->dir.$file);
2369 }
2370 }
2371 }
2372 }
2373 }
aae85978 2374 /**
2375 * delete current user's cache file
2376 *
2377 * @return null
2378 */
2379 public function refresh(){
2380 global $CFG, $USER;
2381 if($dir = opendir($this->dir)){
2382 while (false !== ($file = readdir($dir))) {
2383 if(!is_dir($file) && $file != '.' && $file != '..') {
2384 if(strpos($file, 'u'.$USER->id.'_')!==false){
2385 @unlink($this->dir.$file);
2386 }
2387 }
2388 }
2389 }
2390 }
bb2c046d 2391}
509f67e3 2392
2393/**
2394 * TODO Document
2395 */
014c1ca0 2396class file_type_to_ext {
2397 public function __construct($file = '') {
2398 global $CFG;
2399 if (empty($file)) {
2400 $this->file = $CFG->libdir.'/file/file_types.mm';
2401 } else {
2402 $this->file = $file;
2403 }
2404 $this->tree = array();
2405 $this->result = array();
2406 }
a08171c5 2407
509f67e3 2408 /**
2409 * TODO Document
2410 */
014c1ca0 2411 private function _browse_nodes($parent, $types) {
2412 $key = (string)$parent['TEXT'];
2413 if(isset($parent->node)) {
2414 $this->tree[$key] = array();
2415 if (in_array((string)$parent['TEXT'], $types)) {
2416 $this->_select_nodes($parent, $this->result);
2417 } else {
2418 foreach($parent->node as $v){
2419 $this->_browse_nodes($v, $types);
2420 }
2421 }
2422 } else {
2423 $this->tree[] = $key;
2424 }
2425 }
a08171c5 2426
509f67e3 2427 /**
2428 * TODO Document
2429 */
014c1ca0 2430 private function _select_nodes($parent){
2431 if(isset($parent->node)) {
2432 foreach($parent->node as $v){
2433 $this->_select_nodes($v, $this->result);
2434 }
2435 } else {
2436 $this->result[] = (string)$parent['TEXT'];
2437 }
2438 }
2439
a08171c5 2440
509f67e3 2441 /**
2442 * TODO Document
2443 */
014c1ca0 2444 public function get_file_ext($types) {
2445 $this->result = array();
fc11edbf 2446 if ((is_array($types) && in_array('*', $types)) ||
b933b378 2447 $types == '*' || empty($types)) {
2448 return array('*');
014c1ca0 2449 }
2450 foreach ($types as $key=>$value){
2451 if (strpos($value, '.') !== false) {
2452 $this->result[] = $value;
2453 unset($types[$key]);
2454 }
2455 }
2456 if (file_exists($this->file)) {
2457 $xml = simplexml_load_file($this->file);
2458 foreach($xml->node->node as $v){
2459 if (in_array((string)$v['TEXT'], $types)) {
2460 $this->_select_nodes($v);
2461 } else {
2462 $this->_browse_nodes($v, $types);
2463 }
2464 }
2465 } else {
2466 exit('Failed to open test.xml.');
2467 }
2468 return $this->result;
2469 }
2470}