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