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