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