cca046444c9f783a7e14c943a683ee6df45a38f0
[moodle.git] / lib / filelib.php
1 <?php //$Id$
3 define('BYTESERVING_BOUNDARY', 's1k2o3d4a5k6s7'); //unique string constant
5 function get_file_url($path, $options=null, $type='coursefile') {
6     global $CFG;
8     $path = str_replace('//', '/', $path);  
9     $path = trim($path, '/'); // no leading and trailing slashes
11     // type of file
12     switch ($type) {
13        case 'questionfile':
14             $url = $CFG->wwwroot."/question/exportfile.php";
15             break;
16        case 'rssfile':
17             $url = $CFG->wwwroot."/rss/file.php";
18             break;
19         case 'user':
20             $url = $CFG->wwwroot."/user/pix.php";
21             break;
22         case 'usergroup':
23             $url = $CFG->wwwroot."/user/pixgroup.php";
24             break;
25         case 'httpscoursefile':
26             $url = $CFG->httpswwwroot."/file.php";
27             break;
28          case 'coursefile':
29         default:
30             $url = $CFG->wwwroot."/file.php";
31     }
33     if ($CFG->slasharguments) {
34         $parts = explode('/', $path);
35         $parts = array_map('rawurlencode', $parts);
36         $path  = implode('/', $parts);
37         $ffurl = $url.'/'.$path;
38         $separator = '?';
39     } else {
40         $path = rawurlencode('/'.$path);
41         $ffurl = $url.'?file='.$path;
42         $separator = '&amp;';
43     }
45     if ($options) {
46         foreach ($options as $name=>$value) {
47             $ffurl = $ffurl.$separator.$name.'='.$value;
48             $separator = '&amp;';
49         }
50     }
52     return $ffurl;
53 }
55 /**
56  * Fetches content of file from Internet (using proxy if defined). Uses cURL extension if present.
57  * Due to security concerns only downloads from http(s) sources are supported.
58  *
59  * @param string $url file url starting with http(s)://
60  * @param array $headers http headers, null if none. If set, should be an
61  *   associative array of header name => value pairs.
62  * @param array $postdata array means use POST request with given parameters
63  * @param bool $fullresponse return headers, responses, etc in a similar way snoopy does
64  *   (if false, just returns content)
65  * @param int $timeout timeout for complete download process including all file transfer
66  *   (default 5 minutes)
67  * @param int $connecttimeout timeout for connection to server; this is the timeout that
68  *   usually happens if the remote server is completely down (default 20 seconds);
69  *   may not work when using proxy
70  * @param bool $skipcertverify If true, the peer's SSL certificate will not be checked. Only use this when already in a trusted location.
71  * @return mixed false if request failed or content of the file as string if ok.
72  */
73 function download_file_content($url, $headers=null, $postdata=null, $fullresponse=false, $timeout=300, $connecttimeout=20, $skipcertverify=false) {
74     global $CFG;
76     // some extra security
77     $newlines = array("\r", "\n");
78     if (is_array($headers) ) {
79         foreach ($headers as $key => $value) {
80             $headers[$key] = str_replace($newlines, '', $value);
81         }
82     }
83     $url = str_replace($newlines, '', $url);
84     if (!preg_match('|^https?://|i', $url)) {
85         if ($fullresponse) {
86             $response = new object();
87             $response->status        = 0;
88             $response->headers       = array();
89             $response->response_code = 'Invalid protocol specified in url';
90             $response->results       = '';
91             $response->error         = 'Invalid protocol specified in url';
92             return $response;
93         } else {
94             return false;
95         }
96     }
98     // check if proxy (if used) should be bypassed for this url
99     $proxybypass = is_proxybypass( $url );
101     if (!extension_loaded('curl') or ($ch = curl_init($url)) === false) {
102         require_once($CFG->libdir.'/snoopy/Snoopy.class.inc');
103         $snoopy = new Snoopy();
104         $snoopy->read_timeout = $timeout;
105         $snoopy->_fp_timeout  = $connecttimeout;
106         if (!$proxybypass) {
107             $snoopy->proxy_host   = $CFG->proxyhost;
108             $snoopy->proxy_port   = $CFG->proxyport;
109             if (!empty($CFG->proxyuser) and !empty($CFG->proxypassword)) {
110                 // this will probably fail, but let's try it anyway
111                 $snoopy->proxy_user     = $CFG->proxyuser;
112                 $snoopy->proxy_password = $CFG->proxypassword;
113             }
114         }
116         if (is_array($headers) ) {
117             $client->rawheaders = $headers;
118         }
120         if (is_array($postdata)) {
121             $fetch = @$snoopy->fetch($url, $postdata); // use more specific debug code bellow
122         } else {
123             $fetch = @$snoopy->fetch($url); // use more specific debug code bellow
124         }
126         if ($fetch) {
127             if ($fullresponse) {
128                 //fix header line endings
129                 foreach ($snoopy->headers as $key=>$unused) {
130                     $snoopy->headers[$key] = trim($snoopy->headers[$key]);
131                 }
132                 $response = new object();
133                 $response->status        = $snoopy->status;
134                 $response->headers       = $snoopy->headers;
135                 $response->response_code = trim($snoopy->response_code);
136                 $response->results       = $snoopy->results;
137                 $response->error         = $snoopy->error;
138                 return $response;
140             } else if ($snoopy->status != 200) {
141                 debugging("Snoopy request for \"$url\" failed, http response code: ".$snoopy->response_code, DEBUG_ALL);
142                 return false;
144             } else {
145                 return $snoopy->results;
146             }
147         } else {
148             if ($fullresponse) {
149                 $response = new object();
150                 $response->status        = $snoopy->status;
151                 $response->headers       = array();
152                 $response->response_code = $snoopy->response_code;
153                 $response->results       = '';
154                 $response->error         = $snoopy->error;
155                 return $response;
156             } else {
157                 debugging("Snoopy request for \"$url\" failed with: ".$snoopy->error, DEBUG_ALL);
158                 return false;
159             }
160         }
161     }
163     // set extra headers
164     if (is_array($headers) ) {
165         $headers2 = array();
166         foreach ($headers as $key => $value) {
167             $headers2[] = "$key: $value";
168         }
169         curl_setopt($ch, CURLOPT_HTTPHEADER, $headers2);
170     }
173     if ($skipcertverify) {
174         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
175     }
177     // use POST if requested
178     if (is_array($postdata)) {
179         foreach ($postdata as $k=>$v) {
180             $postdata[$k] = urlencode($k).'='.urlencode($v);
181         }
182         $postdata = implode('&', $postdata);
183         curl_setopt($ch, CURLOPT_POST, true);
184         curl_setopt($ch, CURLOPT_POSTFIELDS, $postdata);
185     }
187     curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
188     curl_setopt($ch, CURLOPT_HEADER, true);
189     curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $connecttimeout);
190     curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
191     if (!ini_get('open_basedir') and !ini_get('safe_mode')) {
192         // TODO: add version test for '7.10.5'
193         curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
194         curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
195     }
197     if (!empty($CFG->proxyhost) and !$proxybypass) {
198         // SOCKS supported in PHP5 only
199         if (!empty($CFG->proxytype) and ($CFG->proxytype == 'SOCKS5')) {
200             if (defined('CURLPROXY_SOCKS5')) {
201                 curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
202             } else {
203                 curl_close($ch);
204                 if ($fullresponse) {
205                     $response = new object();
206                     $response->status        = '0';
207                     $response->headers       = array();
208                     $response->response_code = 'SOCKS5 proxy is not supported in PHP4';
209                     $response->results       = '';
210                     $response->error         = 'SOCKS5 proxy is not supported in PHP4';
211                     return $response;
212                 } else {
213                     debugging("SOCKS5 proxy is not supported in PHP4.", DEBUG_ALL);
214                     return false;
215                 }
216             }
217         }
219         curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, false);
221         if (empty($CFG->proxyport)) {
222             curl_setopt($ch, CURLOPT_PROXY, $CFG->proxyhost);
223         } else {
224             curl_setopt($ch, CURLOPT_PROXY, $CFG->proxyhost.':'.$CFG->proxyport);
225         }
227         if (!empty($CFG->proxyuser) and !empty($CFG->proxypassword)) {
228             curl_setopt($ch, CURLOPT_PROXYUSERPWD, $CFG->proxyuser.':'.$CFG->proxypassword);
229             if (defined('CURLOPT_PROXYAUTH')) {
230                 // any proxy authentication if PHP 5.1
231                 curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_BASIC | CURLAUTH_NTLM);
232             }
233         }
234     }
236     $data = curl_exec($ch);
238     // try to detect encoding problems
239     if ((curl_errno($ch) == 23 or curl_errno($ch) == 61) and defined('CURLOPT_ENCODING')) {
240         curl_setopt($ch, CURLOPT_ENCODING, 'none');
241         $data = curl_exec($ch);
242     }
244     if (curl_errno($ch)) {
245         $error    = curl_error($ch);
246         $error_no = curl_errno($ch);
247         curl_close($ch);
249         if ($fullresponse) {
250             $response = new object();
251             if ($error_no == 28) {
252                 $response->status    = '-100'; // mimic snoopy
253             } else {
254                 $response->status    = '0';
255             }
256             $response->headers       = array();
257             $response->response_code = $error;
258             $response->results       = '';
259             $response->error         = $error;
260             return $response;
261         } else {
262             debugging("cURL request for \"$url\" failed with: $error ($error_no)", DEBUG_ALL);
263             return false;
264         }
266     } else {
267         $info = curl_getinfo($ch);
268         curl_close($ch);
270         if (empty($info['http_code'])) {
271             // for security reasons we support only true http connections (Location: file:// exploit prevention)
272             $response = new object();
273             $response->status        = '0';
274             $response->headers       = array();
275             $response->response_code = 'Unknown cURL error';
276             $response->results       = ''; // do NOT change this!
277             $response->error         = 'Unknown cURL error';
279         } else {
280             // strip redirect headers and get headers array and content
281             $data = explode("\r\n\r\n", $data, $info['redirect_count'] + 2);
282             $results = array_pop($data);
283             $headers = array_pop($data);
284             $headers = explode("\r\n", trim($headers));
286             $response = new object();;
287             $response->status        = (string)$info['http_code'];
288             $response->headers       = $headers;
289             $response->response_code = $headers[0];
290             $response->results       = $results;
291             $response->error         = '';
292         }
294         if ($fullresponse) {
295             return $response;
296         } else if ($info['http_code'] != 200) {
297             debugging("cURL request for \"$url\" failed, HTTP response code: ".$response->response_code, DEBUG_ALL);
298             return false;
299         } else {
300             return $response->results;
301         }
302     }
305 /**
306  * @return List of information about file types based on extensions.
307  *   Associative array of extension (lower-case) to associative array
308  *   from 'element name' to data. Current element names are 'type' and 'icon'.
309  *   Unknown types should use the 'xxx' entry which includes defaults.
310  */
311 function get_mimetypes_array() {
312     return array (
313         'xxx'  => array ('type'=>'document/unknown', 'icon'=>'unknown.gif'),
314         '3gp'  => array ('type'=>'video/quicktime', 'icon'=>'video.gif'),
315         'ai'   => array ('type'=>'application/postscript', 'icon'=>'image.gif'),
316         'aif'  => array ('type'=>'audio/x-aiff', 'icon'=>'audio.gif'),
317         'aiff' => array ('type'=>'audio/x-aiff', 'icon'=>'audio.gif'),
318         'aifc' => array ('type'=>'audio/x-aiff', 'icon'=>'audio.gif'),
319         'applescript'  => array ('type'=>'text/plain', 'icon'=>'text.gif'),
320         'asc'  => array ('type'=>'text/plain', 'icon'=>'text.gif'),
321         'asm'  => array ('type'=>'text/plain', 'icon'=>'text.gif'),
322         'au'   => array ('type'=>'audio/au', 'icon'=>'audio.gif'),
323         'avi'  => array ('type'=>'video/x-ms-wm', 'icon'=>'avi.gif'),
324         'bmp'  => array ('type'=>'image/bmp', 'icon'=>'image.gif'),
325         'c'    => array ('type'=>'text/plain', 'icon'=>'text.gif'),
326         'cct'  => array ('type'=>'shockwave/director', 'icon'=>'flash.gif'),
327         'cpp'  => array ('type'=>'text/plain', 'icon'=>'text.gif'),
328         'cs'   => array ('type'=>'application/x-csh', 'icon'=>'text.gif'),
329         'css'  => array ('type'=>'text/css', 'icon'=>'text.gif'),
330         'csv'  => array ('type'=>'text/csv', 'icon'=>'excel.gif'),
331         'dv'   => array ('type'=>'video/x-dv', 'icon'=>'video.gif'),
332         'dmg'  => array ('type'=>'application/octet-stream', 'icon'=>'dmg.gif'),
333         'doc'  => array ('type'=>'application/msword', 'icon'=>'word.gif'),
334         'docx' => array ('type'=>'application/msword', 'icon'=>'docx.gif'),
335         'docm' => array ('type'=>'application/msword', 'icon'=>'docm.gif'),
336         'dotx' => array ('type'=>'application/msword', 'icon'=>'dotx.gif'),
337         'dcr'  => array ('type'=>'application/x-director', 'icon'=>'flash.gif'),
338         'dif'  => array ('type'=>'video/x-dv', 'icon'=>'video.gif'),
339         'dir'  => array ('type'=>'application/x-director', 'icon'=>'flash.gif'),
340         'dxr'  => array ('type'=>'application/x-director', 'icon'=>'flash.gif'),
341         'eps'  => array ('type'=>'application/postscript', 'icon'=>'pdf.gif'),
342         'fdf'  => array ('type'=>'application/pdf', 'icon'=>'pdf.gif'),
343         'flv'  => array ('type'=>'video/x-flv', 'icon'=>'video.gif'),
344         'gif'  => array ('type'=>'image/gif', 'icon'=>'image.gif'),
345         'gtar' => array ('type'=>'application/x-gtar', 'icon'=>'zip.gif'),
346         'tgz'  => array ('type'=>'application/g-zip', 'icon'=>'zip.gif'),
347         'gz'   => array ('type'=>'application/g-zip', 'icon'=>'zip.gif'),
348         'gzip' => array ('type'=>'application/g-zip', 'icon'=>'zip.gif'),
349         'h'    => array ('type'=>'text/plain', 'icon'=>'text.gif'),
350         'hpp'  => array ('type'=>'text/plain', 'icon'=>'text.gif'),
351         'hqx'  => array ('type'=>'application/mac-binhex40', 'icon'=>'zip.gif'),
352         'htc'  => array ('type'=>'text/x-component', 'icon'=>'text.gif'),
353         'html' => array ('type'=>'text/html', 'icon'=>'html.gif'),
354         'xhtml'=> array ('type'=>'application/xhtml+xml', 'icon'=>'html.gif'),
355         'htm'  => array ('type'=>'text/html', 'icon'=>'html.gif'),
356         'ico'  => array ('type'=>'image/vnd.microsoft.icon', 'icon'=>'image.gif'),
357         'ics'  => array ('type'=>'text/calendar', 'icon'=>'text.gif'),
358         'isf'  => array ('type'=>'application/inspiration', 'icon'=>'isf.gif'),
359         'ist'  => array ('type'=>'application/inspiration.template', 'icon'=>'isf.gif'),
360         'java' => array ('type'=>'text/plain', 'icon'=>'text.gif'),
361         'jcb'  => array ('type'=>'text/xml', 'icon'=>'jcb.gif'),
362         'jcl'  => array ('type'=>'text/xml', 'icon'=>'jcl.gif'),
363         'jcw'  => array ('type'=>'text/xml', 'icon'=>'jcw.gif'),
364         'jmt'  => array ('type'=>'text/xml', 'icon'=>'jmt.gif'),
365         'jmx'  => array ('type'=>'text/xml', 'icon'=>'jmx.gif'),
366         'jpe'  => array ('type'=>'image/jpeg', 'icon'=>'image.gif'),
367         'jpeg' => array ('type'=>'image/jpeg', 'icon'=>'image.gif'),
368         'jpg'  => array ('type'=>'image/jpeg', 'icon'=>'image.gif'),
369         'jqz'  => array ('type'=>'text/xml', 'icon'=>'jqz.gif'),
370         'js'   => array ('type'=>'application/x-javascript', 'icon'=>'text.gif'),
371         'latex'=> array ('type'=>'application/x-latex', 'icon'=>'text.gif'),
372         'm'    => array ('type'=>'text/plain', 'icon'=>'text.gif'),
373         'mov'  => array ('type'=>'video/quicktime', 'icon'=>'video.gif'),
374         'movie'=> array ('type'=>'video/x-sgi-movie', 'icon'=>'video.gif'),
375         'm3u'  => array ('type'=>'audio/x-mpegurl', 'icon'=>'audio.gif'),
376         'mp3'  => array ('type'=>'audio/mp3', 'icon'=>'audio.gif'),
377         'mp4'  => array ('type'=>'video/mp4', 'icon'=>'video.gif'),
378         'mpeg' => array ('type'=>'video/mpeg', 'icon'=>'video.gif'),
379         'mpe'  => array ('type'=>'video/mpeg', 'icon'=>'video.gif'),
380         'mpg'  => array ('type'=>'video/mpeg', 'icon'=>'video.gif'),
382         'odt'  => array ('type'=>'application/vnd.oasis.opendocument.text', 'icon'=>'odt.gif'),
383         'ott'  => array ('type'=>'application/vnd.oasis.opendocument.text-template', 'icon'=>'odt.gif'),
384         'oth'  => array ('type'=>'application/vnd.oasis.opendocument.text-web', 'icon'=>'odt.gif'),
385         'odm'  => array ('type'=>'application/vnd.oasis.opendocument.text-master', 'icon'=>'odm.gif'),
386         'odg'  => array ('type'=>'application/vnd.oasis.opendocument.graphics', 'icon'=>'odg.gif'),
387         'otg'  => array ('type'=>'application/vnd.oasis.opendocument.graphics-template', 'icon'=>'odg.gif'),
388         'odp'  => array ('type'=>'application/vnd.oasis.opendocument.presentation', 'icon'=>'odp.gif'),
389         'otp'  => array ('type'=>'application/vnd.oasis.opendocument.presentation-template', 'icon'=>'odp.gif'),
390         'ods'  => array ('type'=>'application/vnd.oasis.opendocument.spreadsheet', 'icon'=>'ods.gif'),
391         'ots'  => array ('type'=>'application/vnd.oasis.opendocument.spreadsheet-template', 'icon'=>'ods.gif'),
392         'odc'  => array ('type'=>'application/vnd.oasis.opendocument.chart', 'icon'=>'odc.gif'),
393         'odf'  => array ('type'=>'application/vnd.oasis.opendocument.formula', 'icon'=>'odf.gif'),
394         'odb'  => array ('type'=>'application/vnd.oasis.opendocument.database', 'icon'=>'odb.gif'),
395         'odi'  => array ('type'=>'application/vnd.oasis.opendocument.image', 'icon'=>'odi.gif'),
397         'pct'  => array ('type'=>'image/pict', 'icon'=>'image.gif'),
398         'pdf'  => array ('type'=>'application/pdf', 'icon'=>'pdf.gif'),
399         'php'  => array ('type'=>'text/plain', 'icon'=>'text.gif'),
400         'pic'  => array ('type'=>'image/pict', 'icon'=>'image.gif'),
401         'pict' => array ('type'=>'image/pict', 'icon'=>'image.gif'),
402         'png'  => array ('type'=>'image/png', 'icon'=>'image.gif'),
403         'pps'  => array ('type'=>'application/vnd.ms-powerpoint', 'icon'=>'powerpoint.gif'),
404         'ppt'  => array ('type'=>'application/vnd.ms-powerpoint', 'icon'=>'powerpoint.gif'),
405         'pptx' => array ('type'=>'application/vnd.ms-powerpoint', 'icon'=>'pptx.gif'),
406         'pptm' => array ('type'=>'application/vnd.ms-powerpoint', 'icon'=>'pptm.gif'),
407         'potx' => array ('type'=>'application/vnd.ms-powerpoint', 'icon'=>'potx.gif'),
408         'potm' => array ('type'=>'application/vnd.ms-powerpoint', 'icon'=>'potm.gif'),
409         'ppam' => array ('type'=>'application/vnd.ms-powerpoint', 'icon'=>'ppam.gif'),
410         'ppsx' => array ('type'=>'application/vnd.ms-powerpoint', 'icon'=>'ppsx.gif'),
411         'ppsm' => array ('type'=>'application/vnd.ms-powerpoint', 'icon'=>'ppsm.gif'),
412         'ps'   => array ('type'=>'application/postscript', 'icon'=>'pdf.gif'),
413         'qt'   => array ('type'=>'video/quicktime', 'icon'=>'video.gif'),
414         'ra'   => array ('type'=>'audio/x-realaudio', 'icon'=>'audio.gif'),
415         'ram'  => array ('type'=>'audio/x-pn-realaudio', 'icon'=>'audio.gif'),
416         'rhb'  => array ('type'=>'text/xml', 'icon'=>'xml.gif'),
417         'rm'   => array ('type'=>'audio/x-pn-realaudio', 'icon'=>'audio.gif'),
418         'rtf'  => array ('type'=>'text/rtf', 'icon'=>'text.gif'),
419         'rtx'  => array ('type'=>'text/richtext', 'icon'=>'text.gif'),
420         'sh'   => array ('type'=>'application/x-sh', 'icon'=>'text.gif'),
421         'sit'  => array ('type'=>'application/x-stuffit', 'icon'=>'zip.gif'),
422         'smi'  => array ('type'=>'application/smil', 'icon'=>'text.gif'),
423         'smil' => array ('type'=>'application/smil', 'icon'=>'text.gif'),
424         'sqt'  => array ('type'=>'text/xml', 'icon'=>'xml.gif'),
425         'svg'  => array ('type'=>'image/svg+xml', 'icon'=>'image.gif'),
426         'svgz' => array ('type'=>'image/svg+xml', 'icon'=>'image.gif'),
427         'swa'  => array ('type'=>'application/x-director', 'icon'=>'flash.gif'),
428         'swf'  => array ('type'=>'application/x-shockwave-flash', 'icon'=>'flash.gif'),
429         'swfl' => array ('type'=>'application/x-shockwave-flash', 'icon'=>'flash.gif'),
431         'sxw'  => array ('type'=>'application/vnd.sun.xml.writer', 'icon'=>'odt.gif'),
432         'stw'  => array ('type'=>'application/vnd.sun.xml.writer.template', 'icon'=>'odt.gif'),
433         'sxc'  => array ('type'=>'application/vnd.sun.xml.calc', 'icon'=>'odt.gif'),
434         'stc'  => array ('type'=>'application/vnd.sun.xml.calc.template', 'icon'=>'odt.gif'),
435         'sxd'  => array ('type'=>'application/vnd.sun.xml.draw', 'icon'=>'odt.gif'),
436         'std'  => array ('type'=>'application/vnd.sun.xml.draw.template', 'icon'=>'odt.gif'),
437         'sxi'  => array ('type'=>'application/vnd.sun.xml.impress', 'icon'=>'odt.gif'),
438         'sti'  => array ('type'=>'application/vnd.sun.xml.impress.template', 'icon'=>'odt.gif'),
439         'sxg'  => array ('type'=>'application/vnd.sun.xml.writer.global', 'icon'=>'odt.gif'),
440         'sxm'  => array ('type'=>'application/vnd.sun.xml.math', 'icon'=>'odt.gif'),
442         'tar'  => array ('type'=>'application/x-tar', 'icon'=>'zip.gif'),
443         'tif'  => array ('type'=>'image/tiff', 'icon'=>'image.gif'),
444         'tiff' => array ('type'=>'image/tiff', 'icon'=>'image.gif'),
445         'tex'  => array ('type'=>'application/x-tex', 'icon'=>'text.gif'),
446         'texi' => array ('type'=>'application/x-texinfo', 'icon'=>'text.gif'),
447         'texinfo'  => array ('type'=>'application/x-texinfo', 'icon'=>'text.gif'),
448         'tsv'  => array ('type'=>'text/tab-separated-values', 'icon'=>'text.gif'),
449         'txt'  => array ('type'=>'text/plain', 'icon'=>'text.gif'),
450         'wav'  => array ('type'=>'audio/wav', 'icon'=>'audio.gif'),
451         'wmv'  => array ('type'=>'video/x-ms-wmv', 'icon'=>'avi.gif'),
452         'asf'  => array ('type'=>'video/x-ms-asf', 'icon'=>'avi.gif'),
453         'xdp'  => array ('type'=>'application/pdf', 'icon'=>'pdf.gif'),
454         'xfd'  => array ('type'=>'application/pdf', 'icon'=>'pdf.gif'),
455         'xfdf' => array ('type'=>'application/pdf', 'icon'=>'pdf.gif'),
456         'xls'  => array ('type'=>'application/vnd.ms-excel', 'icon'=>'excel.gif'),
457         'xlsx' => array ('type'=>'application/vnd.ms-excel', 'icon'=>'xlsx.gif'),
458         'xlsm' => array ('type'=>'application/vnd.ms-excel', 'icon'=>'xlsm.gif'),
459         'xltx' => array ('type'=>'application/vnd.ms-excel', 'icon'=>'xltx.gif'),
460         'xltm' => array ('type'=>'application/vnd.ms-excel', 'icon'=>'xltm.gif'),
461         'xlsb' => array ('type'=>'application/vnd.ms-excel', 'icon'=>'xlsb.gif'),
462         'xlam' => array ('type'=>'application/vnd.ms-excel', 'icon'=>'xlam.gif'),
463         'xml'  => array ('type'=>'application/xml', 'icon'=>'xml.gif'),
464         'xsl'  => array ('type'=>'text/xml', 'icon'=>'xml.gif'),
465         'zip'  => array ('type'=>'application/zip', 'icon'=>'zip.gif')
466     );
469 /**
470  * Obtains information about a filetype based on its extension. Will
471  * use a default if no information is present about that particular
472  * extension.
473  * @param string $element Desired information (usually 'icon'
474  *   for icon filename or 'type' for MIME type)
475  * @param string $filename Filename we're looking up
476  * @return string Requested piece of information from array
477  */
478 function mimeinfo($element, $filename) {
479     static $mimeinfo = null;
480     if (is_null($mimeinfo)) {
481         $mimeinfo = get_mimetypes_array();
482     }
484     if (eregi('\.([a-z0-9]+)$', $filename, $match)) {
485         if (isset($mimeinfo[strtolower($match[1])][$element])) {
486             return $mimeinfo[strtolower($match[1])][$element];
487         } else {
488             return $mimeinfo['xxx'][$element];   // By default
489         }
490     } else {
491         return $mimeinfo['xxx'][$element];   // By default
492     }
495 /**
496  * Obtains information about a filetype based on the MIME type rather than
497  * the other way around.
498  * @param string $element Desired information (usually 'icon')
499  * @param string $mimetype MIME type we're looking up
500  * @return string Requested piece of information from array
501  */
502 function mimeinfo_from_type($element, $mimetype) {
503     static $mimeinfo;
504     $mimeinfo=get_mimetypes_array();
506     foreach($mimeinfo as $values) {
507         if($values['type']==$mimetype) {
508             if(isset($values[$element])) {
509                 return $values[$element];
510             }
511             break;
512         }
513     }
514     return $mimeinfo['xxx'][$element]; // Default
517 /**
518  * Get information about a filetype based on the icon file.
519  * @param string $element Desired information (usually 'icon')
520  * @param string $icon Icon file path.
521  * @return string Requested piece of information from array
522  */
523 function mimeinfo_from_icon($element, $icon) {
524     static $mimeinfo;
525     $mimeinfo=get_mimetypes_array();
527     if (preg_match("/\/(.*)/", $icon, $matches)) {
528         $icon = $matches[1];
529     }
530     $info = $mimeinfo['xxx'][$element]; // Default
531     foreach($mimeinfo as $values) {
532         if($values['icon']==$icon) {
533             if(isset($values[$element])) {
534                 $info = $values[$element];
535             }
536             //No break, for example for 'excel.gif' we don't want 'csv'!
537         }
538     }
539     return $info;
542 /**
543  * Obtains descriptions for file types (e.g. 'Microsoft Word document') from the
544  * mimetypes.php language file.
545  * @param string $mimetype MIME type (can be obtained using the mimeinfo function)
546  * @param bool $capitalise If true, capitalises first character of result
547  * @return string Text description
548  */
549 function get_mimetype_description($mimetype,$capitalise=false) {
550     $result=get_string($mimetype,'mimetypes');
551     // Surrounded by square brackets indicates that there isn't a string for that
552     // (maybe there is a better way to find this out?)
553     if(strpos($result,'[')===0) {
554         $result=get_string('document/unknown','mimetypes');
555     }
556     if($capitalise) {
557         $result=ucfirst($result);
558     }
559     return $result;
562 /**
563  * Handles the sending of temporary file to user, download is forced.
564  * File is deleted after abort or succesful sending.
565  * @param string $path path to file, preferably from moodledata/temp/something; or content of file itself
566  * @param string $filename proposed file name when saving file
567  * @param bool $path is content of file
568  */
569 function send_temp_file($path, $filename, $pathisstring=false) {
570     global $CFG;
572     // close session - not needed anymore
573     @session_write_close();
575     if (!$pathisstring) {
576         if (!file_exists($path)) {
577             header('HTTP/1.0 404 not found');
578             error(get_string('filenotfound', 'error'), $CFG->wwwroot.'/');
579         }
580         // executed after normal finish or abort
581         @register_shutdown_function('send_temp_file_finished', $path);
582     }
584     //IE compatibiltiy HACK!
585     if (ini_get('zlib.output_compression')) {
586         ini_set('zlib.output_compression', 'Off');
587     }
589     // if user is using IE, urlencode the filename so that multibyte file name will show up correctly on popup
590     if (check_browser_version('MSIE')) {
591         $filename = urlencode($filename);
592     }
594     $filesize = $pathisstring ? strlen($path) : filesize($path);
596     @header('Content-Disposition: attachment; filename='.$filename);
597     @header('Content-Length: '.$filesize);
598     if (strpos($CFG->wwwroot, 'https://') === 0) { //https sites - watch out for IE! KB812935 and KB316431
599         @header('Cache-Control: max-age=10');
600         @header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
601         @header('Pragma: ');
602     } else { //normal http - prevent caching at all cost
603         @header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0');
604         @header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
605         @header('Pragma: no-cache');
606     }
607     @header('Accept-Ranges: none'); // Do not allow byteserving
609     while (@ob_end_flush()); //flush the buffers - save memory and disable sid rewrite
610     if ($pathisstring) {
611         echo $path;
612     } else {
613         readfile_chunked($path);
614     }
616     die; //no more chars to output
619 /**
620  * Internal callnack function used by send_temp_file()
621  */
622 function send_temp_file_finished($path) {
623     if (file_exists($path)) {
624         @unlink($path);
625     }
628 /**
629  * Handles the sending of file data to the user's browser, including support for
630  * byteranges etc.
631  * @param string $path Path of file on disk (including real filename), or actual content of file as string
632  * @param string $filename Filename to send
633  * @param int $lifetime Number of seconds before the file should expire from caches (default 24 hours)
634  * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only
635  * @param bool $pathisstring If true (default false), $path is the content to send and not the pathname
636  * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin
637  * @param string $mimetype Include to specify the MIME type; leave blank to have it guess the type from $filename
638  */
639 function send_file($path, $filename, $lifetime=86400 , $filter=0, $pathisstring=false, $forcedownload=false, $mimetype='') {
640     global $CFG, $COURSE, $SESSION;
642     // Use given MIME type if specified, otherwise guess it using mimeinfo.
643     // IE, Konqueror and Opera open html file directly in browser from web even when directed to save it to disk :-O
644     // only Firefox saves all files locally before opening when content-disposition: attachment stated
645     $isFF         = check_browser_version('Firefox', '1.5'); // only FF > 1.5 properly tested
646     $mimetype     = ($forcedownload and !$isFF) ? 'application/x-forcedownload' :
647                          ($mimetype ? $mimetype : mimeinfo('type', $filename));
648     $lastmodified = $pathisstring ? time() : filemtime($path);
649     $filesize     = $pathisstring ? strlen($path) : filesize($path);
651 /* - MDL-13949
652     //Adobe Acrobat Reader XSS prevention
653     if ($mimetype=='application/pdf' or mimeinfo('type', $filename)=='application/pdf') {
654         //please note that it prevents opening of pdfs in browser when http referer disabled
655         //or file linked from another site; browser caching of pdfs is now disabled too
656         if (!empty($_SERVER['HTTP_RANGE'])) {
657             //already byteserving
658             $lifetime = 1; // >0 needed for byteserving
659         } else if (empty($_SERVER['HTTP_REFERER']) or strpos($_SERVER['HTTP_REFERER'], $CFG->wwwroot)!==0) {
660             $mimetype = 'application/x-forcedownload';
661             $forcedownload = true;
662             $lifetime = 0;
663         } else {
664             $lifetime = 1; // >0 needed for byteserving
665         }
666     }
667 */
669     //IE compatibiltiy HACK!
670     if (ini_get('zlib.output_compression')) {
671         ini_set('zlib.output_compression', 'Off');
672     }
674     //try to disable automatic sid rewrite in cookieless mode
675     @ini_set("session.use_trans_sid", "false");
677     //do not put '@' before the next header to detect incorrect moodle configurations,
678     //error should be better than "weird" empty lines for admins/users
679     //TODO: should we remove all those @ before the header()? Are all of the values supported on all servers?
680     header('Last-Modified: '. gmdate('D, d M Y H:i:s', $lastmodified) .' GMT');
682     // if user is using IE, urlencode the filename so that multibyte file name will show up correctly on popup
683     if (check_browser_version('MSIE')) {
684         $filename = rawurlencode($filename);
685     }
687     if ($forcedownload) {
688         @header('Content-Disposition: attachment; filename="'.$filename.'"');
689     } else {
690         @header('Content-Disposition: inline; filename="'.$filename.'"');
691     }
693     if ($lifetime > 0) {
694         @header('Cache-Control: max-age='.$lifetime);
695         @header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
696         @header('Pragma: ');
698         if (empty($CFG->disablebyteserving) && !$pathisstring && $mimetype != 'text/plain' && $mimetype != 'text/html') {
700             @header('Accept-Ranges: bytes');
702             if (!empty($_SERVER['HTTP_RANGE']) && strpos($_SERVER['HTTP_RANGE'],'bytes=') !== FALSE) {
703                 // byteserving stuff - for acrobat reader and download accelerators
704                 // see: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35
705                 // inspired by: http://www.coneural.org/florian/papers/04_byteserving.php
706                 $ranges = false;
707                 if (preg_match_all('/(\d*)-(\d*)/', $_SERVER['HTTP_RANGE'], $ranges, PREG_SET_ORDER)) {
708                     foreach ($ranges as $key=>$value) {
709                         if ($ranges[$key][1] == '') {
710                             //suffix case
711                             $ranges[$key][1] = $filesize - $ranges[$key][2];
712                             $ranges[$key][2] = $filesize - 1;
713                         } else if ($ranges[$key][2] == '' || $ranges[$key][2] > $filesize - 1) {
714                             //fix range length
715                             $ranges[$key][2] = $filesize - 1;
716                         }
717                         if ($ranges[$key][2] != '' && $ranges[$key][2] < $ranges[$key][1]) {
718                             //invalid byte-range ==> ignore header
719                             $ranges = false;
720                             break;
721                         }
722                         //prepare multipart header
723                         $ranges[$key][0] =  "\r\n--".BYTESERVING_BOUNDARY."\r\nContent-Type: $mimetype\r\n";
724                         $ranges[$key][0] .= "Content-Range: bytes {$ranges[$key][1]}-{$ranges[$key][2]}/$filesize\r\n\r\n";
725                     }
726                 } else {
727                     $ranges = false;
728                 }
729                 if ($ranges) {
730                     byteserving_send_file($path, $mimetype, $ranges);
731                 }
732             }
733         } else {
734             /// Do not byteserve (disabled, strings, text and html files).
735             @header('Accept-Ranges: none');
736         }
737     } else { // Do not cache files in proxies and browsers
738         if (strpos($CFG->wwwroot, 'https://') === 0) { //https sites - watch out for IE! KB812935 and KB316431
739             @header('Cache-Control: max-age=10');
740             @header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
741             @header('Pragma: ');
742         } else { //normal http - prevent caching at all cost
743             @header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0');
744             @header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
745             @header('Pragma: no-cache');
746         }
747         @header('Accept-Ranges: none'); // Do not allow byteserving when caching disabled
748     }
750     if (empty($filter)) {
751         if ($mimetype == 'text/html' && !empty($CFG->usesid) && empty($_COOKIE['MoodleSession'.$CFG->sessioncookie])) {
752             //cookieless mode - rewrite links
753             @header('Content-Type: text/html');
754             $path = $pathisstring ? $path : implode('', file($path));
755             $path = $SESSION->sid_ob_rewrite($path);
756             $filesize = strlen($path);
757             $pathisstring = true;
758         } else if ($mimetype == 'text/plain') {
759             @header('Content-Type: Text/plain; charset=utf-8'); //add encoding
760         } else {
761             @header('Content-Type: '.$mimetype);
762         }
763         @header('Content-Length: '.$filesize);
764         while (@ob_end_flush()); //flush the buffers - save memory and disable sid rewrite
765         if ($pathisstring) {
766             echo $path;
767         } else {
768             readfile_chunked($path);
769         }
770     } else {     // Try to put the file through filters
771         if ($mimetype == 'text/html') {
772             $options = new object();
773             $options->noclean = true;
774             $options->nocache = true; // temporary workaround for MDL-5136
775             $text = $pathisstring ? $path : implode('', file($path));
777             $text = file_modify_html_header($text);
778             $output = format_text($text, FORMAT_HTML, $options, $COURSE->id);
779             if (!empty($CFG->usesid) && empty($_COOKIE['MoodleSession'.$CFG->sessioncookie])) {
780                 //cookieless mode - rewrite links
781                 $output = $SESSION->sid_ob_rewrite($output);
782             }
784             @header('Content-Length: '.strlen($output));
785             @header('Content-Type: text/html');
786             while (@ob_end_flush()); //flush the buffers - save memory and disable sid rewrite
787             echo $output;
788         // only filter text if filter all files is selected
789         } else if (($mimetype == 'text/plain') and ($filter == 1)) {
790             $options = new object();
791             $options->newlines = false;
792             $options->noclean = true;
793             $text = htmlentities($pathisstring ? $path : implode('', file($path)));
794             $output = '<pre>'. format_text($text, FORMAT_MOODLE, $options, $COURSE->id) .'</pre>';
795             if (!empty($CFG->usesid) && empty($_COOKIE['MoodleSession'.$CFG->sessioncookie])) {
796                 //cookieless mode - rewrite links
797                 $output = $SESSION->sid_ob_rewrite($output);
798             }
800             @header('Content-Length: '.strlen($output));
801             @header('Content-Type: text/html; charset=utf-8'); //add encoding
802             while (@ob_end_flush()); //flush the buffers - save memory and disable sid rewrite
803             echo $output;
804         } else {    // Just send it out raw
805             @header('Content-Length: '.$filesize);
806             @header('Content-Type: '.$mimetype);
807             while (@ob_end_flush()); //flush the buffers - save memory and disable sid rewrite
808             if ($pathisstring) {
809                 echo $path;
810             }else {
811                 readfile_chunked($path);
812             }
813         }
814     }
815     die; //no more chars to output!!!
818 function get_records_csv($file, $table) {
819     global $CFG, $DB;
821     if (!$metacolumns = $DB->get_columns($table)) {
822         return false;
823     }
825     if(!($handle = @fopen($file, 'r'))) {
826         print_error('get_records_csv failed to open '.$file);
827     }
829     $fieldnames = fgetcsv($handle, 4096);
830     if(empty($fieldnames)) {
831         fclose($handle);
832         return false;
833     }
835     $columns = array();
837     foreach($metacolumns as $metacolumn) {
838         $ord = array_search($metacolumn->name, $fieldnames);
839         if(is_int($ord)) {
840             $columns[$metacolumn->name] = $ord;
841         }
842     }
844     $rows = array();
846     while (($data = fgetcsv($handle, 4096)) !== false) {
847         $item = new stdClass;
848         foreach($columns as $name => $ord) {
849             $item->$name = $data[$ord];
850         }
851         $rows[] = $item;
852     }
854     fclose($handle);
855     return $rows;
858 function put_records_csv($file, $records, $table = NULL) {
859     global $CFG, $DB;
861     if (empty($records)) {
862         return true;
863     }
865     $metacolumns = NULL;
866     if ($table !== NULL && !$metacolumns = $DB->get_columns($table)) {
867         return false;
868     }
870     echo "x";
872     if(!($fp = @fopen($CFG->dataroot.'/temp/'.$file, 'w'))) {
873         print_error('put_records_csv failed to open '.$file);
874     }
876     $proto = reset($records);
877     if(is_object($proto)) {
878         $fields_records = array_keys(get_object_vars($proto));
879     }
880     else if(is_array($proto)) {
881         $fields_records = array_keys($proto);
882     }
883     else {
884         return false;
885     }
886     echo "x";
888     if(!empty($metacolumns)) {
889         $fields_table = array_map(create_function('$a', 'return $a->name;'), $metacolumns);
890         $fields = array_intersect($fields_records, $fields_table);
891     }
892     else {
893         $fields = $fields_records;
894     }
896     fwrite($fp, implode(',', $fields));
897     fwrite($fp, "\r\n");
899     foreach($records as $record) {
900         $array  = (array)$record;
901         $values = array();
902         foreach($fields as $field) {
903             if(strpos($array[$field], ',')) {
904                 $values[] = '"'.str_replace('"', '\"', $array[$field]).'"';
905             }
906             else {
907                 $values[] = $array[$field];
908             }
909         }
910         fwrite($fp, implode(',', $values)."\r\n");
911     }
913     fclose($fp);
914     return true;
918 /**
919  * Recursively delete the file or folder with path $location. That is,
920  * if it is a file delete it. If it is a folder, delete all its content
921  * then delete it. If $location does not exist to start, that is not
922  * considered an error.
923  *
924  * @param $location the path to remove.
925  */
926 function fulldelete($location) {
927     if (is_dir($location)) {
928         $currdir = opendir($location);
929         while (false !== ($file = readdir($currdir))) {
930             if ($file <> ".." && $file <> ".") {
931                 $fullfile = $location."/".$file;
932                 if (is_dir($fullfile)) {
933                     if (!fulldelete($fullfile)) {
934                         return false;
935                     }
936                 } else {
937                     if (!unlink($fullfile)) {
938                         return false;
939                     }
940                 }
941             }
942         }
943         closedir($currdir);
944         if (! rmdir($location)) {
945             return false;
946         }
948     } else if (file_exists($location)) {
949         if (!unlink($location)) {
950             return false;
951         }
952     }
953     return true;
956 /**
957  * Improves memory consumptions and works around buggy readfile() in PHP 5.0.4 (2MB readfile limit).
958  */
959 function readfile_chunked($filename, $retbytes=true) {
960     $chunksize = 1*(1024*1024); // 1MB chunks - must be less than 2MB!
961     $buffer = '';
962     $cnt =0;
963     $handle = fopen($filename, 'rb');
964     if ($handle === false) {
965         return false;
966     }
968     while (!feof($handle)) {
969         @set_time_limit(60*60); //reset time limit to 60 min - should be enough for 1 MB chunk
970         $buffer = fread($handle, $chunksize);
971         echo $buffer;
972         flush();
973         if ($retbytes) {
974             $cnt += strlen($buffer);
975         }
976     }
977     $status = fclose($handle);
978     if ($retbytes && $status) {
979         return $cnt; // return num. bytes delivered like readfile() does.
980     }
981     return $status;
984 /**
985  * Send requested byterange of file.
986  */
987 function byteserving_send_file($filename, $mimetype, $ranges) {
988     $chunksize = 1*(1024*1024); // 1MB chunks - must be less than 2MB!
989     $handle = fopen($filename, 'rb');
990     if ($handle === false) {
991         die;
992     }
993     if (count($ranges) == 1) { //only one range requested
994         $length = $ranges[0][2] - $ranges[0][1] + 1;
995         @header('HTTP/1.1 206 Partial content');
996         @header('Content-Length: '.$length);
997         @header('Content-Range: bytes '.$ranges[0][1].'-'.$ranges[0][2].'/'.filesize($filename));
998         @header('Content-Type: '.$mimetype);
999         while (@ob_end_flush()); //flush the buffers - save memory and disable sid rewrite
1000         $buffer = '';
1001         fseek($handle, $ranges[0][1]);
1002         while (!feof($handle) && $length > 0) {
1003             @set_time_limit(60*60); //reset time limit to 60 min - should be enough for 1 MB chunk
1004             $buffer = fread($handle, ($chunksize < $length ? $chunksize : $length));
1005             echo $buffer;
1006             flush();
1007             $length -= strlen($buffer);
1008         }
1009         fclose($handle);
1010         die;
1011     } else { // multiple ranges requested - not tested much
1012         $totallength = 0;
1013         foreach($ranges as $range) {
1014             $totallength += strlen($range[0]) + $range[2] - $range[1] + 1;
1015         }
1016         $totallength += strlen("\r\n--".BYTESERVING_BOUNDARY."--\r\n");
1017         @header('HTTP/1.1 206 Partial content');
1018         @header('Content-Length: '.$totallength);
1019         @header('Content-Type: multipart/byteranges; boundary='.BYTESERVING_BOUNDARY);
1020         //TODO: check if "multipart/x-byteranges" is more compatible with current readers/browsers/servers
1021         while (@ob_end_flush()); //flush the buffers - save memory and disable sid rewrite
1022         foreach($ranges as $range) {
1023             $length = $range[2] - $range[1] + 1;
1024             echo $range[0];
1025             $buffer = '';
1026             fseek($handle, $range[1]);
1027             while (!feof($handle) && $length > 0) {
1028                 @set_time_limit(60*60); //reset time limit to 60 min - should be enough for 1 MB chunk
1029                 $buffer = fread($handle, ($chunksize < $length ? $chunksize : $length));
1030                 echo $buffer;
1031                 flush();
1032                 $length -= strlen($buffer);
1033             }
1034         }
1035         echo "\r\n--".BYTESERVING_BOUNDARY."--\r\n";
1036         fclose($handle);
1037         die;
1038     }
1041 /**
1042  * add includes (js and css) into uploaded files
1043  * before returning them, useful for themes and utf.js includes
1044  * @param string text - text to search and replace
1045  * @return string - text with added head includes
1046  */
1047 function file_modify_html_header($text) {
1048     // first look for <head> tag
1049     global $CFG;
1051     $stylesheetshtml = '';
1052     foreach ($CFG->stylesheets as $stylesheet) {
1053         $stylesheetshtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
1054     }
1056     $filters = explode(",", $CFG->textfilters);
1057     if (in_array('filter/mediaplugin', $filters)) {
1058         // this script is needed by most media filter plugins.
1059         $ufo = "\n".'<script type="text/javascript" src="'.$CFG->wwwroot.'/lib/ufo.js"></script>'."\n";
1060     } else {
1061         $ufo = '';
1062     }
1064     preg_match('/\<head\>|\<HEAD\>/', $text, $matches);
1065     if ($matches) {
1066         $replacement = '<head>'.$ufo.$stylesheetshtml;
1067         $text = preg_replace('/\<head\>|\<HEAD\>/', $replacement, $text, 1);
1068         return $text;
1069     }
1071     // if not, look for <html> tag, and stick <head> right after
1072     preg_match('/\<html\>|\<HTML\>/', $text, $matches);
1073     if ($matches) {
1074         // replace <html> tag with <html><head>includes</head>
1075         $replacement = '<html>'."\n".'<head>'.$ufo.$stylesheetshtml.'</head>';
1076         $text = preg_replace('/\<html\>|\<HTML\>/', $replacement, $text, 1);
1077         return $text;
1078     }
1080     // if not, look for <body> tag, and stick <head> before body
1081     preg_match('/\<body\>|\<BODY\>/', $text, $matches);
1082     if ($matches) {
1083         $replacement = '<head>'.$ufo.$stylesheetshtml.'</head>'."\n".'<body>';
1084         $text = preg_replace('/\<body\>|\<BODY\>/', $replacement, $text, 1);
1085         return $text;
1086     }
1088     // if not, just stick a <head> tag at the beginning
1089     $text = '<head>'.$ufo.$stylesheetshtml.'</head>'."\n".$text;
1090     return $text;
1093 /**
1094  * RESTful cURL class
1095  *
1096  * This is a wrapper class for curl, it is quite easy to use:
1097  *
1098  * $c = new curl;
1099  * // enable cache
1100  * $c = new curl(array('cache'=>true));
1101  * // enable cookie
1102  * $c = new curl(array('cookie'=>true));
1103  * // enable proxy
1104  * $c = new curl(array('proxy'=>true));
1105  *
1106  * // HTTP GET Method
1107  * $html = $c->get('http://example.com');
1108  * // HTTP POST Method
1109  * $html = $c->post('http://example.com/', array('q'=>'words', 'name'=>'moodle'));
1110  * // HTTP PUT Method
1111  * $html = $c->put('http://example.com/', array('file'=>'/var/www/test.txt');
1112  *
1113  * @author Dongsheng Cai <dongsheng@cvs.moodle.org>
1114  * @version 0.4 dev
1115  * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
1116  */
1118 class curl {
1119     public  $cache    = false;
1120     public  $proxy    = false;
1121     public  $version  = '0.4 dev';
1122     public  $response = array();
1123     public  $header   = array();
1124     public  $info;
1125     public  $error;
1127     private $options;
1128     private $proxy_host = '';
1129     private $proxy_auth = '';
1130     private $proxy_type = '';
1131     private $debug    = false;
1132     private $cookie   = false;
1134     public function __construct($options = array()){
1135         global $CFG;
1136         if (!function_exists('curl_init')) {
1137             $this->error = 'cURL module must be enabled!';
1138             trigger_error($this->error, E_USER_ERROR);
1139             return false;
1140         }
1141         // the options of curl should be init here.
1142         $this->resetopt();
1143         if (!empty($options['debug'])) {
1144             $this->debug = true;
1145         }
1146         if(!empty($options['cookie'])) {
1147             if($options['cookie'] === true) {
1148                 $this->cookie = $CFG->dataroot.'/curl_cookie.txt';
1149             } else {
1150                 $this->cookie = $options['cookie'];
1151             }
1152         }
1153         if (!empty($options['cache'])) {
1154             if (class_exists('curl_cache')) {
1155                 $this->cache = new curl_cache;
1156             }
1157         }
1158         if (!empty($options['proxy'])) {
1159             if (!empty($CFG->proxyhost)) {
1160                 if (empty($CFG->proxyport)) {
1161                     $this->proxy_host = $CFG->proxyhost;
1162                 } else {
1163                     $this->proxy_host = $CFG->proxyhost.':'.$CFG->proxyport;
1164                 }
1165                 if (!empty($CFG->proxyuser) and !empty($CFG->proxypassword)) {
1166                     $this->proxy_auth = $CFG->proxyuser.':'.$CFG->proxypassword;
1167                     $this->setopt(array(
1168                                 'proxyauth'=> CURLAUTH_BASIC | CURLAUTH_NTLM,
1169                                 'proxyuserpwd'=>$this->proxy_auth));
1170                 }
1171                 if (!empty($CFG->proxytype)) {
1172                     if ($CFG->proxytype == 'SOCKS5') {
1173                         $this->proxy_type = CURLPROXY_SOCKS5;
1174                     } else {
1175                         $this->proxy_type = CURLPROXY_HTTP;
1176                         $this->setopt(array('httpproxytunnel'=>true));
1177                     }
1178                     $this->setopt(array('proxytype'=>$this->proxy_type));
1179                 }
1180             }
1181             if (!empty($this->proxy_host)) {
1182                 $this->proxy = array('proxy'=>$this->proxy_host);
1183             }
1184         }
1185     }
1186     public function resetopt(){
1187         $this->options = array();
1188         $this->options['CURLOPT_USERAGENT']         = 'MoodleBot/1.0';
1189         // True to include the header in the output
1190         $this->options['CURLOPT_HEADER']            = 0;
1191         // True to Exclude the body from the output
1192         $this->options['CURLOPT_NOBODY']            = 0;
1193         // TRUE to follow any "Location: " header that the server
1194         // sends as part of the HTTP header (note this is recursive,
1195         // PHP will follow as many "Location: " headers that it is sent,
1196         // unless CURLOPT_MAXREDIRS is set).
1197         $this->options['CURLOPT_FOLLOWLOCATION']    = 1;
1198         $this->options['CURLOPT_MAXREDIRS']         = 10;
1199         $this->options['CURLOPT_ENCODING']          = '';
1200         // TRUE to return the transfer as a string of the return
1201         // value of curl_exec() instead of outputting it out directly.
1202         $this->options['CURLOPT_RETURNTRANSFER']    = 1;
1203         $this->options['CURLOPT_BINARYTRANSFER']    = 0;
1204         $this->options['CURLOPT_SSL_VERIFYPEER']    = 0;
1205         $this->options['CURLOPT_SSL_VERIFYHOST']    = 2;
1206         $this->options['CURLOPT_CONNECTTIMEOUT']    = 30;
1207     }
1209     /**
1210      * Reset Cookie
1211      *
1212      * @param array $options If array is null, this function will
1213      * reset the options to default value.
1214      *
1215      */
1216     public function resetcookie() {
1217         if (!empty($this->cookie)) {
1218             if (is_file($this->cookie)) {
1219                 $fp = fopen($this->cookie, 'w');
1220                 if (!empty($fp)) {
1221                     fwrite($fp, '');
1222                     fclose($fp);
1223                 }
1224             }
1225         }
1226     }
1228     /**
1229      * Set curl options
1230      *
1231      * @param array $options If array is null, this function will
1232      * reset the options to default value.
1233      *
1234      */
1235     public function setopt($options = array()) {
1236         if (is_array($options)) {
1237             foreach($options as $name => $val){
1238                 if (stripos($name, 'CURLOPT_') === false) {
1239                     $name = strtoupper('CURLOPT_'.$name);
1240                 }
1241                 $this->options[$name] = $val;
1242             }
1243         }
1244     }
1245     /**
1246      * Reset http method
1247      *
1248      */
1249     public function cleanopt(){
1250         unset($this->options['CURLOPT_HTTPGET']);
1251         unset($this->options['CURLOPT_POST']);
1252         unset($this->options['CURLOPT_POSTFIELDS']);
1253         unset($this->options['CURLOPT_PUT']);
1254         unset($this->options['CURLOPT_INFILE']);
1255         unset($this->options['CURLOPT_INFILESIZE']);
1256         unset($this->options['CURLOPT_CUSTOMREQUEST']);
1257     }
1259     /**
1260      * Set HTTP Request Header
1261      *
1262      * @param array $headers
1263      *
1264      */
1265     public function setHeader($header) {
1266         if (is_array($header)){
1267             foreach ($header as $v) {
1268                 $this->setHeader($v);
1269             }
1270         } else {
1271             $this->header[] = $header;
1272         }
1273     }
1274     /**
1275      * Set HTTP Response Header
1276      *
1277      */
1278     public function getResponse(){
1279         return $this->response;
1280     }
1281     /**
1282      * private callback function
1283      * Formatting HTTP Response Header
1284      *
1285      */
1286     private function formatHeader($ch, $header)
1287     {
1288         $this->count++;
1289         if (strlen($header) > 2) {
1290             list($key, $value) = explode(" ", rtrim($header, "\r\n"), 2);
1291             $key = rtrim($key, ':');
1292             if (!empty($this->response[$key])) {
1293                 if (is_array($this->response[$key])){
1294                     $this->response[$key][] = $value;
1295                 } else {
1296                     $tmp = $this->response[$key];
1297                     $this->response[$key] = array();
1298                     $this->response[$key][] = $tmp;
1299                     $this->response[$key][] = $value;
1301                 }
1302             } else {
1303                 $this->response[$key] = $value;
1304             }
1305         }
1306         return strlen($header);
1307     }
1309     /**
1310      * Set options for individual curl instance
1311      */
1312     private function apply_opt($curl, $options) {
1313         // Clean up
1314         $this->cleanopt();
1315         // set cookie
1316         if (!empty($this->cookie) || !empty($options['cookie'])) {
1317             $this->setopt(array('cookiejar'=>$this->cookie,
1318                             'cookiefile'=>$this->cookie
1319                              ));
1320         }
1322         // set proxy
1323         if (!empty($this->proxy) || !empty($options['proxy'])) {
1324             $this->setopt($this->proxy);
1325         }
1326         $this->setopt($options);
1327         // reset before set options
1328         curl_setopt($curl, CURLOPT_HEADERFUNCTION, array(&$this,'formatHeader'));
1329         // set headers
1330         if (empty($this->header)){
1331             $this->setHeader(array(
1332                 'User-Agent: MoodleBot/1.0',
1333                 'Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7',
1334                 'Connection: keep-alive'
1335                 ));
1336         }
1337         curl_setopt($curl, CURLOPT_HTTPHEADER, $this->header);
1339         if ($this->debug){
1340             echo '<h1>Options</h1>';
1341             var_dump($this->options);
1342             echo '<h1>Header</h1>';
1343             var_dump($this->header);
1344         }
1346         // set options
1347         foreach($this->options as $name => $val) {
1348             if (is_string($name)) {
1349                 $name = constant(strtoupper($name));
1350             }
1351             curl_setopt($curl, $name, $val);
1352         }
1353         return $curl;
1354     }
1355     /*
1356      * Download multiple files in parallel
1357      * $c = new curl;
1358      * $c->download(array(
1359      *              array('url'=>'http://localhost/', 'file'=>fopen('a', 'wb')), 
1360      *              array('url'=>'http://localhost/20/', 'file'=>fopen('b', 'wb'))
1361      *              ));
1362      */
1363     public function download($requests, $options = array()) {
1364         $options['CURLOPT_BINARYTRANSFER'] = 1;
1365         $options['RETURNTRANSFER'] = false;
1366         return $this->multi($requests, $options);
1367     }
1368     /*
1369      * Mulit HTTP Requests
1370      * This function could run multi-requests in parallel.
1371      */
1372     protected function multi($requests, $options = array()) {
1373         $count   = count($requests);
1374         $handles = array();
1375         $results = array();
1376         $main    = curl_multi_init();
1377         for ($i = 0; $i < $count; $i++) {
1378             $url = $requests[$i];
1379             foreach($url as $n=>$v){
1380                 $options[$n] = $url[$n];
1381             }
1382             $handles[$i] = curl_init($url['url']);
1383             $this->apply_opt($handles[$i], $options);
1384             curl_multi_add_handle($main, $handles[$i]);
1385         }
1386         $running = 0;
1387         do {
1388             curl_multi_exec($main, $running);
1389         } while($running > 0);
1390         for ($i = 0; $i < $count; $i++) {
1391             if (!empty($optins['CURLOPT_RETURNTRANSFER'])) {
1392                 $results[] = true;
1393             } else {
1394                 $results[] = curl_multi_getcontent($handles[$i]);
1395             }
1396             curl_multi_remove_handle($main, $handles[$i]);
1397         }
1398         curl_multi_close($main);
1399         return $results;
1400     }
1401     /**
1402      * Single HTTP Request
1403      */
1404     protected function request($url, $options = array()){
1405         // create curl instance
1406         $curl = curl_init($url);
1407         $options['url'] = $url;
1408         $this->apply_opt($curl, $options);
1409         if ($this->cache && $ret = $this->cache->get($this->options)) {
1410             return $ret;
1411         } else {
1412             $ret = curl_exec($curl);
1413             if ($this->cache) {
1414                 $this->cache->set($this->options, $ret);
1415             }
1416         }
1418         $this->info  = curl_getinfo($curl);
1419         $this->error = curl_error($curl);
1421         if ($this->debug){
1422             echo '<h1>Return Data</h1>';
1423             var_dump($ret);
1424             echo '<h1>Info</h1>';
1425             var_dump($this->info);
1426             echo '<h1>Error</h1>';
1427             var_dump($this->error);
1428         }
1430         curl_close($curl);
1432         if (empty($this->error)){
1433             return $ret;
1434         } else {
1435             throw new moodle_exception($this->error, 'curl');
1436         }
1437     }
1439     /**
1440      * HTTP HEAD method
1441      */
1442     public function head($url, $options = array()){
1443         $options['CURLOPT_HTTPGET'] = 0;
1444         $options['CURLOPT_HEADER']  = 1;
1445         $options['CURLOPT_NOBODY']  = 1;
1446         return $this->request($url, $options);
1447     }
1449     /**
1450      * HTTP POST method
1451      */
1452     public function post($url, $params = array(), $options = array()){
1453         $options['CURLOPT_POST']       = 1;
1454         $options['CURLOPT_POSTFIELDS'] = $params;
1455         return $this->request($url, $options);
1456     }
1458     /**
1459      * HTTP GET method
1460      */
1461     public function get($url, $params = array(), $options = array()){
1462         $options['CURLOPT_HTTPGET'] = 1;
1464         if (!empty($params)){
1465             $url .= (stripos($url, '?') !== false) ? '&' : '?';
1466             $url .= http_build_query($params, '', '&');
1467         }
1468         return $this->request($url, $options);
1469     }
1471     /**
1472      * HTTP PUT method
1473      */
1474     public function put($url, $params = array(), $options = array()){
1475         $file = $params['file'];
1476         if (!is_file($file)){
1477             return null;
1478         }
1479         $fp   = fopen($file, 'r');
1480         $size = filesize($file);
1481         $options['CURLOPT_PUT']        = 1;
1482         $options['CURLOPT_INFILESIZE'] = $size;
1483         $options['CURLOPT_INFILE']     = $fp;
1484         if (!isset($this->options['CURLOPT_USERPWD'])){
1485             $this->setopt(array('CURLOPT_USERPWD'=>'anonymous: noreply@moodle.org'));
1486         }
1487         $ret = $this->request($url, $options);
1488         fclose($fp);
1489         return $ret;
1490     }
1492     /**
1493      * HTTP DELETE method
1494      */
1495     public function delete($url, $param = array(), $options = array()){
1496         $options['CURLOPT_CUSTOMREQUEST'] = 'DELETE';
1497         if (!isset($options['CURLOPT_USERPWD'])) {
1498             $options['CURLOPT_USERPWD'] = 'anonymous: noreply@moodle.org';
1499         }
1500         $ret = $this->request($url, $options);
1501         return $ret;
1502     }
1503     /**
1504      * HTTP TRACE method
1505      */
1506     public function trace($url, $options = array()){
1507         $options['CURLOPT_CUSTOMREQUEST'] = 'TRACE';
1508         $ret = $this->request($url, $options);
1509         return $ret;
1510     }
1511     /**
1512      * HTTP OPTIONS method
1513      */
1514     public function options($url, $options = array()){
1515         $options['CURLOPT_CUSTOMREQUEST'] = 'OPTIONS';
1516         $ret = $this->request($url, $options);
1517         return $ret;
1518     }
1521 /**
1522  * This class is used by cURL class, use case:
1523  *
1524  * $CFG->repository_cache_expire = 120;
1525  * $c = new curl(array('cache'=>true));
1526  * $ret = $c->get('http://www.google.com');
1527  *
1528  */
1529 class curl_cache {
1530     public $dir = '';
1531     function __construct(){
1532         global $CFG;
1533         if (!file_exists($CFG->dataroot.'/repository/cache')) {
1534             mkdir($CFG->dataroot.'/repository/cache/', 0777, true);
1535         }
1536         if(is_dir($CFG->dataroot.'/repository/cache')) {
1537             $this->dir = $CFG->dataroot.'/repository/cache/';
1538         }
1539     }
1540     public function get($param){
1541         global $CFG;
1542         $filename = md5(serialize($param));
1543         if(file_exists($this->dir.$filename)) {
1544             $lasttime = filemtime($this->dir.$filename);
1545             if(time()-$lasttime > $CFG->repository_cache_expire) {
1546                 return false;
1547             } else {
1548                 $fp = fopen($this->dir.$filename, 'r');
1549                 $size = filesize($this->dir.$filename);
1550                 $content = fread($fp, $size);
1551                 return unserialize($content);
1552             }
1553         }
1554         return false;
1555     }
1556     public function set($param, $val){
1557         $filename = md5(serialize($param));
1558         $fp = fopen($this->dir.$filename, 'w');
1559         fwrite($fp, serialize($val));
1560         fclose($fp);
1561     }
1562     public function cleanup($expire){
1563         if($dir = opendir($this->dir)){
1564             while (false !== ($file = readdir($dir))) {
1565                 if(!is_dir($file) && $file != '.' && $file != '..') {
1566                     $lasttime = @filemtime($this->dir.$file);
1567                     if(time() - $lasttime > $expire){
1568                         @unlink($this->dir.$file);
1569                     }
1570                 }
1571             }
1572         }
1573     }