Commit | Line | Data |
---|---|---|
64a19b38 | 1 | <?php |
64a19b38 | 2 | // This file is part of Moodle - http://moodle.org/ |
3 | // | |
4 | // Moodle is free software: you can redistribute it and/or modify | |
5 | // it under the terms of the GNU General Public License as published by | |
6 | // the Free Software Foundation, either version 3 of the License, or | |
7 | // (at your option) any later version. | |
8 | // | |
9 | // Moodle is distributed in the hope that it will be useful, | |
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | // GNU General Public License for more details. | |
13 | // | |
14 | // You should have received a copy of the GNU General Public License | |
15 | // along with Moodle. If not, see <http://www.gnu.org/licenses/>. | |
edc0c493 | 16 | |
17 | /** | |
18 | * Functions for file handling. | |
19 | * | |
d2b7803e DC |
20 | * @package core_files |
21 | * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) | |
22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
edc0c493 | 23 | */ |
24 | ||
64f93798 PS |
25 | defined('MOODLE_INTERNAL') || die(); |
26 | ||
d2b7803e DC |
27 | /** |
28 | * BYTESERVING_BOUNDARY - string unique string constant. | |
29 | */ | |
edc0c493 | 30 | define('BYTESERVING_BOUNDARY', 's1k2o3d4a5k6s7'); |
4c8c65ec | 31 | |
68acd115 FM |
32 | /** |
33 | * Unlimited area size constant | |
34 | */ | |
35 | define('FILE_AREA_MAX_BYTES_UNLIMITED', -1); | |
36 | ||
64f93798 PS |
37 | require_once("$CFG->libdir/filestorage/file_exceptions.php"); |
38 | require_once("$CFG->libdir/filestorage/file_storage.php"); | |
39 | require_once("$CFG->libdir/filestorage/zip_packer.php"); | |
40 | require_once("$CFG->libdir/filebrowser/file_browser.php"); | |
172dd12c | 41 | |
4eef1399 | 42 | /** |
43 | * Encodes file serving url | |
ba21c9d4 | 44 | * |
f28ee49e PS |
45 | * @deprecated use moodle_url factory methods instead |
46 | * | |
d2b7803e DC |
47 | * @todo MDL-31071 deprecate this function |
48 | * @global stdClass $CFG | |
4eef1399 | 49 | * @param string $urlbase |
50 | * @param string $path /filearea/itemid/dir/dir/file.exe | |
51 | * @param bool $forcedownload | |
52 | * @param bool $https https url required | |
53 | * @return string encoded file url | |
54 | */ | |
55 | function file_encode_url($urlbase, $path, $forcedownload=false, $https=false) { | |
56 | global $CFG; | |
57 | ||
f28ee49e PS |
58 | //TODO: deprecate this |
59 | ||
4eef1399 | 60 | if ($CFG->slasharguments) { |
61 | $parts = explode('/', $path); | |
62 | $parts = array_map('rawurlencode', $parts); | |
63 | $path = implode('/', $parts); | |
64 | $return = $urlbase.$path; | |
65 | if ($forcedownload) { | |
66 | $return .= '?forcedownload=1'; | |
67 | } | |
68 | } else { | |
69 | $path = rawurlencode($path); | |
70 | $return = $urlbase.'?file='.$path; | |
71 | if ($forcedownload) { | |
72 | $return .= '&forcedownload=1'; | |
73 | } | |
74 | } | |
75 | ||
76 | if ($https) { | |
77 | $return = str_replace('http://', 'https://', $return); | |
78 | } | |
79 | ||
80 | return $return; | |
81 | } | |
82 | ||
4db9d482 PS |
83 | /** |
84 | * Detects if area contains subdirs, | |
85 | * this is intended for file areas that are attached to content | |
86 | * migrated from 1.x where subdirs were allowed everywhere. | |
87 | * | |
88 | * @param context $context | |
89 | * @param string $component | |
90 | * @param string $filearea | |
91 | * @param string $itemid | |
92 | * @return bool | |
93 | */ | |
94 | function file_area_contains_subdirs(context $context, $component, $filearea, $itemid) { | |
95 | global $DB; | |
96 | ||
97 | if (!isset($itemid)) { | |
98 | // Not initialised yet. | |
99 | return false; | |
100 | } | |
101 | ||
102 | // Detect if any directories are already present, this is necessary for content upgraded from 1.x. | |
103 | $select = "contextid = :contextid AND component = :component AND filearea = :filearea AND itemid = :itemid AND filepath <> '/' AND filename = '.'"; | |
104 | $params = array('contextid'=>$context->id, 'component'=>$component, 'filearea'=>$filearea, 'itemid'=>$itemid); | |
105 | return $DB->record_exists_select('files', $select, $params); | |
106 | } | |
107 | ||
c94882ff | 108 | /** |
db2cc99b | 109 | * Prepares 'editor' formslib element from data in database |
ba21c9d4 | 110 | * |
db2cc99b | 111 | * The passed $data record must contain field foobar, foobarformat and optionally foobartrust. This |
34ff25a6 PS |
112 | * function then copies the embedded files into draft area (assigning itemids automatically), |
113 | * creates the form element foobar_editor and rewrites the URLs so the embedded images can be | |
db2cc99b | 114 | * displayed. |
115 | * In your mform definition, you must have an 'editor' element called foobar_editor. Then you call | |
116 | * your mform's set_data() supplying the object returned by this function. | |
117 | * | |
d2b7803e DC |
118 | * @category files |
119 | * @param stdClass $data database field that holds the html text with embedded media | |
34ff25a6 | 120 | * @param string $field the name of the database field that holds the html text with embedded media |
db2cc99b | 121 | * @param array $options editor options (like maxifiles, maxbytes etc.) |
d2b7803e | 122 | * @param stdClass $context context of the editor |
64f93798 | 123 | * @param string $component |
c94882ff | 124 | * @param string $filearea file area name |
125 | * @param int $itemid item id, required if item exists | |
d2b7803e | 126 | * @return stdClass modified data object |
c94882ff | 127 | */ |
64f93798 | 128 | function file_prepare_standard_editor($data, $field, array $options, $context=null, $component=null, $filearea=null, $itemid=null) { |
c94882ff | 129 | $options = (array)$options; |
130 | if (!isset($options['trusttext'])) { | |
131 | $options['trusttext'] = false; | |
132 | } | |
133 | if (!isset($options['forcehttps'])) { | |
134 | $options['forcehttps'] = false; | |
135 | } | |
136 | if (!isset($options['subdirs'])) { | |
137 | $options['subdirs'] = false; | |
138 | } | |
989c16ee | 139 | if (!isset($options['maxfiles'])) { |
140 | $options['maxfiles'] = 0; // no files by default | |
141 | } | |
b96ddb7d | 142 | if (!isset($options['noclean'])) { |
143 | $options['noclean'] = false; | |
144 | } | |
c94882ff | 145 | |
f9157eb7 RT |
146 | //sanity check for passed context. This function doesn't expect $option['context'] to be set |
147 | //But this function is called before creating editor hence, this is one of the best places to check | |
148 | //if context is used properly. This check notify developer that they missed passing context to editor. | |
149 | if (isset($context) && !isset($options['context'])) { | |
150 | //if $context is not null then make sure $option['context'] is also set. | |
151 | debugging('Context for editor is not set in editoroptions. Hence editor will not respect editor filters', DEBUG_DEVELOPER); | |
152 | } else if (isset($options['context']) && isset($context)) { | |
153 | //If both are passed then they should be equal. | |
154 | if ($options['context']->id != $context->id) { | |
155 | $exceptionmsg = 'Editor context ['.$options['context']->id.'] is not equal to passed context ['.$context->id.']'; | |
156 | throw new coding_exception($exceptionmsg); | |
157 | } | |
158 | } | |
32dc8ecd | 159 | |
d5934b35 | 160 | if (is_null($itemid) or is_null($context)) { |
c94882ff | 161 | $contextid = null; |
d5934b35 | 162 | $itemid = null; |
b85b25eb PS |
163 | if (!isset($data)) { |
164 | $data = new stdClass(); | |
165 | } | |
c94882ff | 166 | if (!isset($data->{$field})) { |
167 | $data->{$field} = ''; | |
168 | } | |
169 | if (!isset($data->{$field.'format'})) { | |
20e5da7d | 170 | $data->{$field.'format'} = editors_get_preferred_format(); |
b96ddb7d | 171 | } |
172 | if (!$options['noclean']) { | |
173 | $data->{$field} = clean_text($data->{$field}, $data->{$field.'format'}); | |
c94882ff | 174 | } |
c94882ff | 175 | |
176 | } else { | |
177 | if ($options['trusttext']) { | |
b96ddb7d | 178 | // noclean ignored if trusttext enabled |
c94882ff | 179 | if (!isset($data->{$field.'trust'})) { |
180 | $data->{$field.'trust'} = 0; | |
181 | } | |
182 | $data = trusttext_pre_edit($data, $field, $context); | |
183 | } else { | |
b96ddb7d | 184 | if (!$options['noclean']) { |
185 | $data->{$field} = clean_text($data->{$field}, $data->{$field.'format'}); | |
186 | } | |
c94882ff | 187 | } |
188 | $contextid = $context->id; | |
189 | } | |
190 | ||
989c16ee | 191 | if ($options['maxfiles'] != 0) { |
192 | $draftid_editor = file_get_submitted_draft_itemid($field); | |
64f93798 | 193 | $currenttext = file_prepare_draft_area($draftid_editor, $contextid, $component, $filearea, $itemid, $options, $data->{$field}); |
989c16ee | 194 | $data->{$field.'_editor'} = array('text'=>$currenttext, 'format'=>$data->{$field.'format'}, 'itemid'=>$draftid_editor); |
195 | } else { | |
8302aae1 | 196 | $data->{$field.'_editor'} = array('text'=>$data->{$field}, 'format'=>$data->{$field.'format'}, 'itemid'=>0); |
989c16ee | 197 | } |
c94882ff | 198 | |
199 | return $data; | |
200 | } | |
201 | ||
202 | /** | |
34ff25a6 | 203 | * Prepares the content of the 'editor' form element with embedded media files to be saved in database |
ba21c9d4 | 204 | * |
19fbc617 | 205 | * This function moves files from draft area to the destination area and |
206 | * encodes URLs to the draft files so they can be safely saved into DB. The | |
207 | * form has to contain the 'editor' element named foobar_editor, where 'foobar' | |
208 | * is the name of the database field to hold the wysiwyg editor content. The | |
209 | * editor data comes as an array with text, format and itemid properties. This | |
210 | * function automatically adds $data properties foobar, foobarformat and | |
34ff25a6 | 211 | * foobartrust, where foobar has URL to embedded files encoded. |
19fbc617 | 212 | * |
d2b7803e DC |
213 | * @category files |
214 | * @param stdClass $data raw data submitted by the form | |
34ff25a6 | 215 | * @param string $field name of the database field containing the html with embedded media files |
19fbc617 | 216 | * @param array $options editor options (trusttext, subdirs, maxfiles, maxbytes etc.) |
d2b7803e DC |
217 | * @param stdClass $context context, required for existing data |
218 | * @param string $component file component | |
c94882ff | 219 | * @param string $filearea file area name |
220 | * @param int $itemid item id, required if item exists | |
d2b7803e | 221 | * @return stdClass modified data object |
c94882ff | 222 | */ |
64f93798 | 223 | function file_postupdate_standard_editor($data, $field, array $options, $context, $component=null, $filearea=null, $itemid=null) { |
c94882ff | 224 | $options = (array)$options; |
225 | if (!isset($options['trusttext'])) { | |
226 | $options['trusttext'] = false; | |
227 | } | |
228 | if (!isset($options['forcehttps'])) { | |
229 | $options['forcehttps'] = false; | |
230 | } | |
231 | if (!isset($options['subdirs'])) { | |
232 | $options['subdirs'] = false; | |
233 | } | |
234 | if (!isset($options['maxfiles'])) { | |
85db95e7 | 235 | $options['maxfiles'] = 0; // no files by default |
c94882ff | 236 | } |
237 | if (!isset($options['maxbytes'])) { | |
238 | $options['maxbytes'] = 0; // unlimited | |
239 | } | |
240 | ||
241 | if ($options['trusttext']) { | |
19fbc617 | 242 | $data->{$field.'trust'} = trusttext_trusted($context); |
c94882ff | 243 | } else { |
19fbc617 | 244 | $data->{$field.'trust'} = 0; |
c94882ff | 245 | } |
246 | ||
85db95e7 | 247 | $editor = $data->{$field.'_editor'}; |
c94882ff | 248 | |
9057a848 | 249 | if ($options['maxfiles'] == 0 or is_null($filearea) or is_null($itemid) or empty($editor['itemid'])) { |
989c16ee | 250 | $data->{$field} = $editor['text']; |
7f0fedc0 | 251 | } else { |
64f93798 | 252 | $data->{$field} = file_save_draft_area_files($editor['itemid'], $context->id, $component, $filearea, $itemid, $options, $editor['text'], $options['forcehttps']); |
989c16ee | 253 | } |
c94882ff | 254 | $data->{$field.'format'} = $editor['format']; |
255 | ||
256 | return $data; | |
257 | } | |
258 | ||
259 | /** | |
260 | * Saves text and files modified by Editor formslib element | |
ba21c9d4 | 261 | * |
d2b7803e DC |
262 | * @category files |
263 | * @param stdClass $data $database entry field | |
c94882ff | 264 | * @param string $field name of data field |
265 | * @param array $options various options | |
d2b7803e | 266 | * @param stdClass $context context - must already exist |
64f93798 | 267 | * @param string $component |
c94882ff | 268 | * @param string $filearea file area name |
269 | * @param int $itemid must already exist, usually means data is in db | |
d2b7803e | 270 | * @return stdClass modified data obejct |
c94882ff | 271 | */ |
64f93798 | 272 | function file_prepare_standard_filemanager($data, $field, array $options, $context=null, $component=null, $filearea=null, $itemid=null) { |
c94882ff | 273 | $options = (array)$options; |
274 | if (!isset($options['subdirs'])) { | |
275 | $options['subdirs'] = false; | |
276 | } | |
d5934b35 | 277 | if (is_null($itemid) or is_null($context)) { |
278 | $itemid = null; | |
c94882ff | 279 | $contextid = null; |
280 | } else { | |
281 | $contextid = $context->id; | |
282 | } | |
283 | ||
85db95e7 | 284 | $draftid_editor = file_get_submitted_draft_itemid($field.'_filemanager'); |
64f93798 | 285 | file_prepare_draft_area($draftid_editor, $contextid, $component, $filearea, $itemid, $options); |
85db95e7 | 286 | $data->{$field.'_filemanager'} = $draftid_editor; |
c94882ff | 287 | |
288 | return $data; | |
289 | } | |
290 | ||
291 | /** | |
292 | * Saves files modified by File manager formslib element | |
ba21c9d4 | 293 | * |
d2b7803e DC |
294 | * @todo MDL-31073 review this function |
295 | * @category files | |
296 | * @param stdClass $data $database entry field | |
c94882ff | 297 | * @param string $field name of data field |
298 | * @param array $options various options | |
d2b7803e | 299 | * @param stdClass $context context - must already exist |
64f93798 | 300 | * @param string $component |
c94882ff | 301 | * @param string $filearea file area name |
302 | * @param int $itemid must already exist, usually means data is in db | |
d2b7803e | 303 | * @return stdClass modified data obejct |
c94882ff | 304 | */ |
64f93798 | 305 | function file_postupdate_standard_filemanager($data, $field, array $options, $context, $component, $filearea, $itemid) { |
c94882ff | 306 | $options = (array)$options; |
307 | if (!isset($options['subdirs'])) { | |
308 | $options['subdirs'] = false; | |
309 | } | |
310 | if (!isset($options['maxfiles'])) { | |
311 | $options['maxfiles'] = -1; // unlimited | |
312 | } | |
313 | if (!isset($options['maxbytes'])) { | |
314 | $options['maxbytes'] = 0; // unlimited | |
315 | } | |
316 | ||
85db95e7 | 317 | if (empty($data->{$field.'_filemanager'})) { |
c94882ff | 318 | $data->$field = ''; |
319 | ||
320 | } else { | |
64f93798 | 321 | file_save_draft_area_files($data->{$field.'_filemanager'}, $context->id, $component, $filearea, $itemid, $options); |
c94882ff | 322 | $fs = get_file_storage(); |
323 | ||
64f93798 PS |
324 | if ($fs->get_area_files($context->id, $component, $filearea, $itemid)) { |
325 | $data->$field = '1'; // TODO: this is an ugly hack (skodak) | |
c94882ff | 326 | } else { |
327 | $data->$field = ''; | |
328 | } | |
329 | } | |
330 | ||
331 | return $data; | |
332 | } | |
333 | ||
8546def3 | 334 | /** |
d2b7803e | 335 | * Generate a draft itemid |
ba21c9d4 | 336 | * |
d2b7803e DC |
337 | * @category files |
338 | * @global moodle_database $DB | |
339 | * @global stdClass $USER | |
509f67e3 | 340 | * @return int a random but available draft itemid that can be used to create a new draft |
edc0c493 | 341 | * file area. |
8546def3 | 342 | */ |
edc0c493 | 343 | function file_get_unused_draft_itemid() { |
8546def3 | 344 | global $DB, $USER; |
345 | ||
346 | if (isguestuser() or !isloggedin()) { | |
85db95e7 | 347 | // guests and not-logged-in users can not be allowed to upload anything!!!!!! |
8546def3 | 348 | print_error('noguest'); |
349 | } | |
350 | ||
b0c6dc1c | 351 | $contextid = context_user::instance($USER->id)->id; |
8546def3 | 352 | |
353 | $fs = get_file_storage(); | |
354 | $draftitemid = rand(1, 999999999); | |
64f93798 | 355 | while ($files = $fs->get_area_files($contextid, 'user', 'draft', $draftitemid)) { |
8546def3 | 356 | $draftitemid = rand(1, 999999999); |
357 | } | |
358 | ||
b933a139 | 359 | return $draftitemid; |
8546def3 | 360 | } |
361 | ||
7983d682 | 362 | /** |
edc0c493 | 363 | * Initialise a draft file area from a real one by copying the files. A draft |
364 | * area will be created if one does not already exist. Normally you should | |
365 | * get $draftitemid by calling file_get_submitted_draft_itemid('elementname'); | |
ba21c9d4 | 366 | * |
d2b7803e DC |
367 | * @category files |
368 | * @global stdClass $CFG | |
369 | * @global stdClass $USER | |
370 | * @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. | |
371 | * @param int $contextid This parameter and the next two identify the file area to copy files from. | |
64f93798 | 372 | * @param string $component |
edc0c493 | 373 | * @param string $filearea helps indentify the file area. |
d2b7803e | 374 | * @param int $itemid helps identify the file area. Can be null if there are no files yet. |
33374f76 | 375 | * @param array $options text and file options ('subdirs'=>false, 'forcehttps'=>false) |
edc0c493 | 376 | * @param string $text some html content that needs to have embedded links rewritten to point to the draft area. |
d2b7803e | 377 | * @return string|null returns string if $text was passed in, the rewritten $text is returned. Otherwise NULL. |
7983d682 | 378 | */ |
64f93798 | 379 | function file_prepare_draft_area(&$draftitemid, $contextid, $component, $filearea, $itemid, array $options=null, $text=null) { |
893fe4b6 | 380 | global $CFG, $USER, $CFG; |
7983d682 | 381 | |
a19a06d0 | 382 | $options = (array)$options; |
383 | if (!isset($options['subdirs'])) { | |
384 | $options['subdirs'] = false; | |
385 | } | |
33374f76 | 386 | if (!isset($options['forcehttps'])) { |
a19a06d0 | 387 | $options['forcehttps'] = false; |
388 | } | |
389 | ||
b0c6dc1c | 390 | $usercontext = context_user::instance($USER->id); |
b933a139 | 391 | $fs = get_file_storage(); |
392 | ||
393 | if (empty($draftitemid)) { | |
394 | // create a new area and copy existing files into | |
edc0c493 | 395 | $draftitemid = file_get_unused_draft_itemid(); |
64f93798 PS |
396 | $file_record = array('contextid'=>$usercontext->id, 'component'=>'user', 'filearea'=>'draft', 'itemid'=>$draftitemid); |
397 | if (!is_null($itemid) and $files = $fs->get_area_files($contextid, $component, $filearea, $itemid)) { | |
b933a139 | 398 | foreach ($files as $file) { |
64f93798 PS |
399 | if ($file->is_directory() and $file->get_filepath() === '/') { |
400 | // we need a way to mark the age of each draft area, | |
401 | // by not copying the root dir we force it to be created automatically with current timestamp | |
402 | continue; | |
403 | } | |
a19a06d0 | 404 | if (!$options['subdirs'] and ($file->is_directory() or $file->get_filepath() !== '/')) { |
b5b188ce | 405 | continue; |
406 | } | |
67233725 | 407 | $draftfile = $fs->create_file_from_storedfile($file_record, $file); |
61506a0a | 408 | // XXX: This is a hack for file manager (MDL-28666) |
67233725 DC |
409 | // File manager needs to know the original file information before copying |
410 | // to draft area, so we append these information in mdl_files.source field | |
411 | // {@link file_storage::search_references()} | |
412 | // {@link file_storage::search_references_count()} | |
413 | $sourcefield = $file->get_source(); | |
414 | $newsourcefield = new stdClass; | |
415 | $newsourcefield->source = $sourcefield; | |
416 | $original = new stdClass; | |
417 | $original->contextid = $contextid; | |
418 | $original->component = $component; | |
419 | $original->filearea = $filearea; | |
420 | $original->itemid = $itemid; | |
421 | $original->filename = $file->get_filename(); | |
422 | $original->filepath = $file->get_filepath(); | |
423 | $newsourcefield->original = file_storage::pack_reference($original); | |
424 | $draftfile->set_source(serialize($newsourcefield)); | |
425 | // End of file manager hack | |
b933a139 | 426 | } |
427 | } | |
893fe4b6 PS |
428 | if (!is_null($text)) { |
429 | // at this point there should not be any draftfile links yet, | |
430 | // because this is a new text from database that should still contain the @@pluginfile@@ links | |
431 | // this happens when developers forget to post process the text | |
432 | $text = str_replace("\"$CFG->httpswwwroot/draftfile.php", "\"$CFG->httpswwwroot/brokenfile.php#", $text); | |
433 | } | |
b933a139 | 434 | } else { |
435 | // nothing to do | |
436 | } | |
437 | ||
438 | if (is_null($text)) { | |
439 | return null; | |
440 | } | |
441 | ||
edc0c493 | 442 | // relink embedded files - editor can not handle @@PLUGINFILE@@ ! |
64f93798 | 443 | return file_rewrite_pluginfile_urls($text, 'draftfile.php', $usercontext->id, 'user', 'draft', $draftitemid, $options); |
644d506a | 444 | } |
445 | ||
446 | /** | |
edc0c493 | 447 | * Convert encoded URLs in $text from the @@PLUGINFILE@@/... form to an actual URL. |
ba21c9d4 | 448 | * |
d2b7803e DC |
449 | * @category files |
450 | * @global stdClass $CFG | |
edc0c493 | 451 | * @param string $text The content that may contain ULRs in need of rewriting. |
452 | * @param string $file The script that should be used to serve these files. pluginfile.php, draftfile.php, etc. | |
d2b7803e | 453 | * @param int $contextid This parameter and the next two identify the file area to use. |
64f93798 | 454 | * @param string $component |
34ff25a6 | 455 | * @param string $filearea helps identify the file area. |
d2b7803e | 456 | * @param int $itemid helps identify the file area. |
33374f76 | 457 | * @param array $options text and file options ('forcehttps'=>false) |
edc0c493 | 458 | * @return string the processed text. |
644d506a | 459 | */ |
64f93798 | 460 | function file_rewrite_pluginfile_urls($text, $file, $contextid, $component, $filearea, $itemid, array $options=null) { |
644d506a | 461 | global $CFG; |
b933a139 | 462 | |
33374f76 MH |
463 | $options = (array)$options; |
464 | if (!isset($options['forcehttps'])) { | |
465 | $options['forcehttps'] = false; | |
466 | } | |
467 | ||
edc0c493 | 468 | if (!$CFG->slasharguments) { |
469 | $file = $file . '?file='; | |
b933a139 | 470 | } |
8546def3 | 471 | |
64f93798 | 472 | $baseurl = "$CFG->wwwroot/$file/$contextid/$component/$filearea/"; |
7d2948bd | 473 | |
474 | if ($itemid !== null) { | |
475 | $baseurl .= "$itemid/"; | |
476 | } | |
edc0c493 | 477 | |
33374f76 | 478 | if ($options['forcehttps']) { |
edc0c493 | 479 | $baseurl = str_replace('http://', 'https://', $baseurl); |
b933a139 | 480 | } |
481 | ||
edc0c493 | 482 | return str_replace('@@PLUGINFILE@@/', $baseurl, $text); |
b933a139 | 483 | } |
484 | ||
12fab708 | 485 | /** |
edc0c493 | 486 | * Returns information about files in a draft area. |
ba21c9d4 | 487 | * |
d2b7803e DC |
488 | * @global stdClass $CFG |
489 | * @global stdClass $USER | |
490 | * @param int $draftitemid the draft area item id. | |
638d72cd | 491 | * @param string $filepath path to the directory from which the information have to be retrieved. |
edc0c493 | 492 | * @return array with the following entries: |
493 | * 'filecount' => number of files in the draft area. | |
638d72cd FM |
494 | * 'filesize' => total size of the files in the draft area. |
495 | * 'foldercount' => number of folders in the draft area. | |
7aff4683 | 496 | * 'filesize_without_references' => total size of the area excluding file references. |
edc0c493 | 497 | * (more information will be added as needed). |
12fab708 | 498 | */ |
638d72cd | 499 | function file_get_draft_area_info($draftitemid, $filepath = '/') { |
12fab708 | 500 | global $CFG, $USER; |
501 | ||
b0c6dc1c | 502 | $usercontext = context_user::instance($USER->id); |
12fab708 | 503 | $fs = get_file_storage(); |
504 | ||
638d72cd FM |
505 | $results = array( |
506 | 'filecount' => 0, | |
507 | 'foldercount' => 0, | |
7aff4683 | 508 | 'filesize' => 0, |
212dac27 | 509 | 'filesize_without_references' => 0 |
638d72cd | 510 | ); |
edc0c493 | 511 | |
638d72cd FM |
512 | if ($filepath != '/') { |
513 | $draftfiles = $fs->get_directory_files($usercontext->id, 'user', 'draft', $draftitemid, $filepath, true, true); | |
514 | } else { | |
515 | $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid, 'id', true); | |
516 | } | |
529377b5 | 517 | foreach ($draftfiles as $file) { |
638d72cd FM |
518 | if ($file->is_directory()) { |
519 | $results['foldercount'] += 1; | |
520 | } else { | |
521 | $results['filecount'] += 1; | |
522 | } | |
7aff4683 FM |
523 | |
524 | $filesize = $file->get_filesize(); | |
525 | $results['filesize'] += $filesize; | |
526 | if (!$file->is_external_file()) { | |
527 | $results['filesize_without_references'] += $filesize; | |
528 | } | |
529377b5 | 529 | } |
b6accf69 DC |
530 | |
531 | return $results; | |
532 | } | |
533 | ||
21e3ea77 FM |
534 | /** |
535 | * Returns whether a draft area has exceeded/will exceed its size limit. | |
536 | * | |
68acd115 FM |
537 | * Please note that the unlimited value for $areamaxbytes is -1 {@link FILE_AREA_MAX_BYTES_UNLIMITED}, not 0. |
538 | * | |
21e3ea77 FM |
539 | * @param int $draftitemid the draft area item id. |
540 | * @param int $areamaxbytes the maximum size allowed in this draft area. | |
541 | * @param int $newfilesize the size that would be added to the current area. | |
7aff4683 | 542 | * @param bool $includereferences true to include the size of the references in the area size. |
21e3ea77 | 543 | * @return bool true if the area will/has exceeded its limit. |
5bcfd504 | 544 | * @since Moodle 2.4 |
21e3ea77 | 545 | */ |
7aff4683 | 546 | function file_is_draft_area_limit_reached($draftitemid, $areamaxbytes, $newfilesize = 0, $includereferences = false) { |
68acd115 | 547 | if ($areamaxbytes != FILE_AREA_MAX_BYTES_UNLIMITED) { |
21e3ea77 | 548 | $draftinfo = file_get_draft_area_info($draftitemid); |
7aff4683 FM |
549 | $areasize = $draftinfo['filesize_without_references']; |
550 | if ($includereferences) { | |
551 | $areasize = $draftinfo['filesize']; | |
552 | } | |
553 | if ($areasize + $newfilesize > $areamaxbytes) { | |
21e3ea77 FM |
554 | return true; |
555 | } | |
556 | } | |
557 | return false; | |
558 | } | |
559 | ||
ea1780ad DC |
560 | /** |
561 | * Get used space of files | |
d2b7803e DC |
562 | * @global moodle_database $DB |
563 | * @global stdClass $USER | |
34ff25a6 | 564 | * @return int total bytes |
ea1780ad DC |
565 | */ |
566 | function file_get_user_used_space() { | |
e35194be | 567 | global $DB, $USER; |
ea1780ad | 568 | |
b0c6dc1c | 569 | $usercontext = context_user::instance($USER->id); |
e35194be DC |
570 | $sql = "SELECT SUM(files1.filesize) AS totalbytes FROM {files} files1 |
571 | JOIN (SELECT contenthash, filename, MAX(id) AS id | |
572 | FROM {files} | |
573 | WHERE contextid = ? AND component = ? AND filearea != ? | |
574 | GROUP BY contenthash, filename) files2 ON files1.id = files2.id"; | |
575 | $params = array('contextid'=>$usercontext->id, 'component'=>'user', 'filearea'=>'draft'); | |
576 | $record = $DB->get_record_sql($sql, $params); | |
577 | return (int)$record->totalbytes; | |
ea1780ad | 578 | } |
f0e5f031 | 579 | |
580 | /** | |
581 | * Convert any string to a valid filepath | |
d2b7803e | 582 | * @todo review this function |
f0e5f031 | 583 | * @param string $str |
584 | * @return string path | |
585 | */ | |
caf16a57 | 586 | function file_correct_filepath($str) { //TODO: what is this? (skodak) - No idea (Fred) |
f36cb951 DC |
587 | if ($str == '/' or empty($str)) { |
588 | return '/'; | |
589 | } else { | |
caf16a57 | 590 | return '/'.trim($str, '/').'/'; |
f36cb951 | 591 | } |
f0e5f031 | 592 | } |
593 | ||
594 | /** | |
34ff25a6 | 595 | * Generate a folder tree of draft area of current USER recursively |
d2b7803e DC |
596 | * |
597 | * @todo MDL-31073 use normal return value instead, this does not fit the rest of api here (skodak) | |
598 | * @param int $draftitemid | |
f0e5f031 | 599 | * @param string $filepath |
d2b7803e | 600 | * @param mixed $data |
f0e5f031 | 601 | */ |
64f93798 | 602 | function file_get_drafarea_folders($draftitemid, $filepath, &$data) { |
f0e5f031 | 603 | global $USER, $OUTPUT, $CFG; |
604 | $data->children = array(); | |
b0c6dc1c | 605 | $context = context_user::instance($USER->id); |
f0e5f031 | 606 | $fs = get_file_storage(); |
64f93798 | 607 | if ($files = $fs->get_directory_files($context->id, 'user', 'draft', $draftitemid, $filepath, false)) { |
f0e5f031 | 608 | foreach ($files as $file) { |
609 | if ($file->is_directory()) { | |
6bdfef5d | 610 | $item = new stdClass(); |
f79321f1 | 611 | $item->sortorder = $file->get_sortorder(); |
f0e5f031 | 612 | $item->filepath = $file->get_filepath(); |
613 | ||
614 | $foldername = explode('/', trim($item->filepath, '/')); | |
615 | $item->fullname = trim(array_pop($foldername), '/'); | |
616 | ||
617 | $item->id = uniqid(); | |
64f93798 | 618 | file_get_drafarea_folders($draftitemid, $item->filepath, $item); |
f0e5f031 | 619 | $data->children[] = $item; |
620 | } else { | |
621 | continue; | |
622 | } | |
623 | } | |
624 | } | |
625 | } | |
626 | ||
627 | /** | |
628 | * Listing all files (including folders) in current path (draft area) | |
629 | * used by file manager | |
630 | * @param int $draftitemid | |
631 | * @param string $filepath | |
d2b7803e | 632 | * @return stdClass |
f0e5f031 | 633 | */ |
64f93798 | 634 | function file_get_drafarea_files($draftitemid, $filepath = '/') { |
f0e5f031 | 635 | global $USER, $OUTPUT, $CFG; |
636 | ||
b0c6dc1c | 637 | $context = context_user::instance($USER->id); |
f0e5f031 | 638 | $fs = get_file_storage(); |
639 | ||
6bdfef5d | 640 | $data = new stdClass(); |
f0e5f031 | 641 | $data->path = array(); |
642 | $data->path[] = array('name'=>get_string('files'), 'path'=>'/'); | |
643 | ||
644 | // will be used to build breadcrumb | |
e709ddd2 | 645 | $trail = '/'; |
f0e5f031 | 646 | if ($filepath !== '/') { |
647 | $filepath = file_correct_filepath($filepath); | |
648 | $parts = explode('/', $filepath); | |
649 | foreach ($parts as $part) { | |
650 | if ($part != '' && $part != null) { | |
e709ddd2 | 651 | $trail .= ($part.'/'); |
f0e5f031 | 652 | $data->path[] = array('name'=>$part, 'path'=>$trail); |
653 | } | |
654 | } | |
655 | } | |
656 | ||
657 | $list = array(); | |
f79321f1 | 658 | $maxlength = 12; |
64f93798 | 659 | if ($files = $fs->get_directory_files($context->id, 'user', 'draft', $draftitemid, $filepath, false)) { |
f0e5f031 | 660 | foreach ($files as $file) { |
6bdfef5d | 661 | $item = new stdClass(); |
f0e5f031 | 662 | $item->filename = $file->get_filename(); |
663 | $item->filepath = $file->get_filepath(); | |
664 | $item->fullname = trim($item->filename, '/'); | |
665 | $filesize = $file->get_filesize(); | |
e709ddd2 | 666 | $item->size = $filesize ? $filesize : null; |
f0e5f031 | 667 | $item->filesize = $filesize ? display_size($filesize) : ''; |
668 | ||
f79321f1 | 669 | $item->sortorder = $file->get_sortorder(); |
e709ddd2 MG |
670 | $item->author = $file->get_author(); |
671 | $item->license = $file->get_license(); | |
672 | $item->datemodified = $file->get_timemodified(); | |
673 | $item->datecreated = $file->get_timecreated(); | |
9a62f779 | 674 | $item->isref = $file->is_external_file(); |
0b2bfbd1 MG |
675 | if ($item->isref && $file->get_status() == 666) { |
676 | $item->originalmissing = true; | |
677 | } | |
6dd299be MG |
678 | // find the file this draft file was created from and count all references in local |
679 | // system pointing to that file | |
83d2700e | 680 | $source = @unserialize($file->get_source()); |
6dd299be MG |
681 | if (isset($source->original)) { |
682 | $item->refcount = $fs->search_references_count($source->original); | |
683 | } | |
f0e5f031 | 684 | |
f0e5f031 | 685 | if ($file->is_directory()) { |
686 | $item->filesize = 0; | |
559276b1 | 687 | $item->icon = $OUTPUT->pix_url(file_folder_icon(24))->out(false); |
f0e5f031 | 688 | $item->type = 'folder'; |
689 | $foldername = explode('/', trim($item->filepath, '/')); | |
690 | $item->fullname = trim(array_pop($foldername), '/'); | |
559276b1 | 691 | $item->thumbnail = $OUTPUT->pix_url(file_folder_icon(90))->out(false); |
f0e5f031 | 692 | } else { |
64f93798 | 693 | // do NOT use file browser here! |
559276b1 | 694 | $item->mimetype = get_mimetype_description($file); |
ff32444a | 695 | if (file_extension_in_typegroup($file->get_filename(), 'archive')) { |
559276b1 MG |
696 | $item->type = 'zip'; |
697 | } else { | |
698 | $item->type = 'file'; | |
699 | } | |
dafe1296 DM |
700 | $itemurl = moodle_url::make_draftfile_url($draftitemid, $item->filepath, $item->filename); |
701 | $item->url = $itemurl->out(); | |
559276b1 MG |
702 | $item->icon = $OUTPUT->pix_url(file_file_icon($file, 24))->out(false); |
703 | $item->thumbnail = $OUTPUT->pix_url(file_file_icon($file, 90))->out(false); | |
dafe1296 | 704 | if ($imageinfo = $file->get_imageinfo()) { |
3333e7e2 DM |
705 | $item->realthumbnail = $itemurl->out(false, array('preview' => 'thumb', 'oid' => $file->get_timemodified())); |
706 | $item->realicon = $itemurl->out(false, array('preview' => 'tinyicon', 'oid' => $file->get_timemodified())); | |
dafe1296 DM |
707 | $item->image_width = $imageinfo['width']; |
708 | $item->image_height = $imageinfo['height']; | |
709 | } | |
f0e5f031 | 710 | } |
711 | $list[] = $item; | |
712 | } | |
713 | } | |
714 | $data->itemid = $draftitemid; | |
715 | $data->list = $list; | |
716 | return $data; | |
717 | } | |
718 | ||
3156b8ca | 719 | /** |
edc0c493 | 720 | * Returns draft area itemid for a given element. |
ba21c9d4 | 721 | * |
d2b7803e | 722 | * @category files |
edc0c493 | 723 | * @param string $elname name of formlib editor element, or a hidden form field that stores the draft area item id, etc. |
d2b7803e | 724 | * @return int the itemid, or 0 if there is not one yet. |
3156b8ca | 725 | */ |
edc0c493 | 726 | function file_get_submitted_draft_itemid($elname) { |
18bd7573 PS |
727 | // this is a nasty hack, ideally all new elements should use arrays here or there should be a new parameter |
728 | if (!isset($_REQUEST[$elname])) { | |
729 | return 0; | |
edc0c493 | 730 | } |
18bd7573 PS |
731 | if (is_array($_REQUEST[$elname])) { |
732 | $param = optional_param_array($elname, 0, PARAM_INT); | |
56a7bf68 | 733 | if (!empty($param['itemid'])) { |
734 | $param = $param['itemid']; | |
735 | } else { | |
736 | debugging('Missing itemid, maybe caused by unset maxfiles option', DEBUG_DEVELOPER); | |
737 | return false; | |
738 | } | |
18bd7573 PS |
739 | |
740 | } else { | |
741 | $param = optional_param($elname, 0, PARAM_INT); | |
3156b8ca | 742 | } |
18bd7573 PS |
743 | |
744 | if ($param) { | |
745 | require_sesskey(); | |
746 | } | |
747 | ||
edc0c493 | 748 | return $param; |
3156b8ca | 749 | } |
750 | ||
67233725 DC |
751 | /** |
752 | * Restore the original source field from draft files | |
753 | * | |
935429af MG |
754 | * Do not use this function because it makes field files.source inconsistent |
755 | * for draft area files. This function will be deprecated in 2.6 | |
756 | * | |
67233725 DC |
757 | * @param stored_file $storedfile This only works with draft files |
758 | * @return stored_file | |
759 | */ | |
760 | function file_restore_source_field_from_draft_file($storedfile) { | |
83d2700e | 761 | $source = @unserialize($storedfile->get_source()); |
61506a0a DC |
762 | if (!empty($source)) { |
763 | if (is_object($source)) { | |
764 | $restoredsource = $source->source; | |
765 | $storedfile->set_source($restoredsource); | |
766 | } else { | |
767 | throw new moodle_exception('invalidsourcefield', 'error'); | |
768 | } | |
67233725 DC |
769 | } |
770 | return $storedfile; | |
771 | } | |
b933a139 | 772 | /** |
edc0c493 | 773 | * Saves files from a draft file area to a real one (merging the list of files). |
774 | * Can rewrite URLs in some content at the same time if desired. | |
ba21c9d4 | 775 | * |
d2b7803e DC |
776 | * @category files |
777 | * @global stdClass $USER | |
778 | * @param int $draftitemid the id of the draft area to use. Normally obtained | |
edc0c493 | 779 | * from file_get_submitted_draft_itemid('elementname') or similar. |
d2b7803e | 780 | * @param int $contextid This parameter and the next two identify the file area to save to. |
64f93798 | 781 | * @param string $component |
a08171c5 | 782 | * @param string $filearea indentifies the file area. |
d2b7803e | 783 | * @param int $itemid helps identifies the file area. |
ba21c9d4 | 784 | * @param array $options area options (subdirs=>false, maxfiles=-1, maxbytes=0) |
edc0c493 | 785 | * @param string $text some html content that needs to have embedded links rewritten |
786 | * to the @@PLUGINFILE@@ form for saving in the database. | |
d2b7803e DC |
787 | * @param bool $forcehttps force https urls. |
788 | * @return string|null if $text was passed in, the rewritten $text is returned. Otherwise NULL. | |
b933a139 | 789 | */ |
64f93798 | 790 | function file_save_draft_area_files($draftitemid, $contextid, $component, $filearea, $itemid, array $options=null, $text=null, $forcehttps=false) { |
c08643da | 791 | global $USER; |
b933a139 | 792 | |
b0c6dc1c | 793 | $usercontext = context_user::instance($USER->id); |
8546def3 | 794 | $fs = get_file_storage(); |
b933a139 | 795 | |
a08171c5 | 796 | $options = (array)$options; |
797 | if (!isset($options['subdirs'])) { | |
798 | $options['subdirs'] = false; | |
799 | } | |
800 | if (!isset($options['maxfiles'])) { | |
801 | $options['maxfiles'] = -1; // unlimited | |
802 | } | |
3b6629c0 | 803 | if (!isset($options['maxbytes']) || $options['maxbytes'] == USER_CAN_IGNORE_FILE_SIZE_LIMITS) { |
a08171c5 | 804 | $options['maxbytes'] = 0; // unlimited |
805 | } | |
68acd115 FM |
806 | if (!isset($options['areamaxbytes'])) { |
807 | $options['areamaxbytes'] = FILE_AREA_MAX_BYTES_UNLIMITED; // Unlimited. | |
808 | } | |
3b6629c0 MG |
809 | $allowreferences = true; |
810 | if (isset($options['return_types']) && !($options['return_types'] & FILE_REFERENCE)) { | |
811 | // we assume that if $options['return_types'] is NOT specified, we DO allow references. | |
812 | // this is not exactly right. BUT there are many places in code where filemanager options | |
813 | // are not passed to file_save_draft_area_files() | |
814 | $allowreferences = false; | |
815 | } | |
a08171c5 | 816 | |
68acd115 FM |
817 | // Check if the draft area has exceeded the authorised limit. This should never happen as validation |
818 | // should have taken place before, unless the user is doing something nauthly. If so, let's just not save | |
819 | // anything at all in the next area. | |
820 | if (file_is_draft_area_limit_reached($draftitemid, $options['areamaxbytes'])) { | |
821 | return null; | |
822 | } | |
823 | ||
64f93798 PS |
824 | $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid, 'id'); |
825 | $oldfiles = $fs->get_area_files($contextid, $component, $filearea, $itemid, 'id'); | |
b933a139 | 826 | |
489bd32b MG |
827 | // One file in filearea means it is empty (it has only top-level directory '.'). |
828 | if (count($draftfiles) > 1 || count($oldfiles) > 1) { | |
b933a139 | 829 | // we have to merge old and new files - we want to keep file ids for files that were not changed |
656e1184 | 830 | // we change time modified for all new and changed files, we keep time created as is |
a08171c5 | 831 | |
832 | $newhashes = array(); | |
32495c06 | 833 | $filecount = 0; |
b933a139 | 834 | foreach ($draftfiles as $file) { |
d90c8115 | 835 | if (!$options['subdirs'] && $file->get_filepath() !== '/') { |
32495c06 MG |
836 | continue; |
837 | } | |
838 | if (!$allowreferences && $file->is_external_file()) { | |
839 | continue; | |
840 | } | |
841 | if (!$file->is_directory()) { | |
842 | if ($options['maxbytes'] and $options['maxbytes'] < $file->get_filesize()) { | |
843 | // oversized file - should not get here at all | |
844 | continue; | |
845 | } | |
846 | if ($options['maxfiles'] != -1 and $options['maxfiles'] <= $filecount) { | |
847 | // more files - should not get here at all | |
848 | continue; | |
849 | } | |
850 | $filecount++; | |
851 | } | |
64f93798 | 852 | $newhash = $fs->get_pathname_hash($contextid, $component, $filearea, $itemid, $file->get_filepath(), $file->get_filename()); |
a08171c5 | 853 | $newhashes[$newhash] = $file; |
854 | } | |
32495c06 | 855 | |
489bd32b MG |
856 | // Loop through oldfiles and decide which we need to delete and which to update. |
857 | // After this cycle the array $newhashes will only contain the files that need to be added. | |
656e1184 PS |
858 | foreach ($oldfiles as $oldfile) { |
859 | $oldhash = $oldfile->get_pathnamehash(); | |
860 | if (!isset($newhashes[$oldhash])) { | |
64f93798 | 861 | // delete files not needed any more - deleted by user |
656e1184 PS |
862 | $oldfile->delete(); |
863 | continue; | |
864 | } | |
67233725 | 865 | |
656e1184 | 866 | $newfile = $newhashes[$oldhash]; |
0e7fd52e MG |
867 | // Now we know that we have $oldfile and $newfile for the same path. |
868 | // Let's check if we can update this file or we need to delete and create. | |
869 | if ($newfile->is_directory()) { | |
870 | // Directories are always ok to just update. | |
871 | } else if (($source = @unserialize($newfile->get_source())) && isset($source->original)) { | |
872 | // File has the 'original' - we need to update the file (it may even have not been changed at all). | |
873 | $original = file_storage::unpack_reference($source->original); | |
874 | if ($original['filename'] !== $oldfile->get_filename() || $original['filepath'] !== $oldfile->get_filepath()) { | |
875 | // Very odd, original points to another file. Delete and create file. | |
876 | $oldfile->delete(); | |
877 | continue; | |
878 | } | |
879 | } else { | |
880 | // The same file name but absence of 'original' means that file was deteled and uploaded again. | |
881 | // By deleting and creating new file we properly manage all existing references. | |
882 | $oldfile->delete(); | |
883 | continue; | |
884 | } | |
885 | ||
67233725 DC |
886 | // status changed, we delete old file, and create a new one |
887 | if ($oldfile->get_status() != $newfile->get_status()) { | |
656e1184 PS |
888 | // file was changed, use updated with new timemodified data |
889 | $oldfile->delete(); | |
67233725 | 890 | // This file will be added later |
656e1184 PS |
891 | continue; |
892 | } | |
67233725 | 893 | |
67233725 DC |
894 | // Updated author |
895 | if ($oldfile->get_author() != $newfile->get_author()) { | |
896 | $oldfile->set_author($newfile->get_author()); | |
897 | } | |
898 | // Updated license | |
899 | if ($oldfile->get_license() != $newfile->get_license()) { | |
900 | $oldfile->set_license($newfile->get_license()); | |
901 | } | |
902 | ||
903 | // Updated file source | |
935429af MG |
904 | // Field files.source for draftarea files contains serialised object with source and original information. |
905 | // We only store the source part of it for non-draft file area. | |
0e7fd52e MG |
906 | $newsource = $newfile->get_source(); |
907 | if ($source = @unserialize($newfile->get_source())) { | |
908 | $newsource = $source->source; | |
909 | } | |
910 | if ($oldfile->get_source() !== $newsource) { | |
911 | $oldfile->set_source($newsource); | |
67233725 DC |
912 | } |
913 | ||
914 | // Updated sort order | |
915 | if ($oldfile->get_sortorder() != $newfile->get_sortorder()) { | |
916 | $oldfile->set_sortorder($newfile->get_sortorder()); | |
917 | } | |
918 | ||
f653c9d8 DM |
919 | // Update file timemodified |
920 | if ($oldfile->get_timemodified() != $newfile->get_timemodified()) { | |
921 | $oldfile->set_timemodified($newfile->get_timemodified()); | |
922 | } | |
923 | ||
14b7e500 | 924 | // Replaced file content |
0e7fd52e MG |
925 | if (!$oldfile->is_directory() && |
926 | ($oldfile->get_contenthash() != $newfile->get_contenthash() || | |
e9e32b1d | 927 | $oldfile->get_filesize() != $newfile->get_filesize() || |
6dd92c02 MG |
928 | $oldfile->get_referencefileid() != $newfile->get_referencefileid() || |
929 | $oldfile->get_userid() != $newfile->get_userid())) { | |
e9e32b1d | 930 | $oldfile->replace_file_with($newfile); |
14b7e500 MG |
931 | } |
932 | ||
656e1184 PS |
933 | // unchanged file or directory - we keep it as is |
934 | unset($newhashes[$oldhash]); | |
b933a139 | 935 | } |
a08171c5 | 936 | |
67233725 | 937 | // Add fresh file or the file which has changed status |
656e1184 | 938 | // the size and subdirectory tests are extra safety only, the UI should prevent it |
a08171c5 | 939 | foreach ($newhashes as $file) { |
65fead53 | 940 | $file_record = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea, 'itemid'=>$itemid, 'timemodified'=>time()); |
0e7fd52e MG |
941 | if ($source = @unserialize($file->get_source())) { |
942 | // Field files.source for draftarea files contains serialised object with source and original information. | |
935429af | 943 | // We only store the source part of it for non-draft file area. |
0e7fd52e MG |
944 | $file_record['source'] = $source->source; |
945 | } | |
67233725 DC |
946 | |
947 | if ($file->is_external_file()) { | |
948 | $repoid = $file->get_repository_id(); | |
949 | if (!empty($repoid)) { | |
950 | $file_record['repositoryid'] = $repoid; | |
951 | $file_record['reference'] = $file->get_reference(); | |
952 | } | |
953 | } | |
954 | ||
a08171c5 | 955 | $fs->create_file_from_storedfile($file_record, $file); |
8546def3 | 956 | } |
957 | } | |
958 | ||
64f93798 | 959 | // note: do not purge the draft area - we clean up areas later in cron, |
42776c94 PS |
960 | // the reason is that user might press submit twice and they would loose the files, |
961 | // also sometimes we might want to use hacks that save files into two different areas | |
b933a139 | 962 | |
8546def3 | 963 | if (is_null($text)) { |
964 | return null; | |
c08643da TH |
965 | } else { |
966 | return file_rewrite_urls_to_pluginfile($text, $draftitemid, $forcehttps); | |
8546def3 | 967 | } |
c08643da TH |
968 | } |
969 | ||
970 | /** | |
971 | * Convert the draft file area URLs in some content to @@PLUGINFILE@@ tokens | |
972 | * ready to be saved in the database. Normally, this is done automatically by | |
973 | * {@link file_save_draft_area_files()}. | |
d2b7803e DC |
974 | * |
975 | * @category files | |
c08643da TH |
976 | * @param string $text the content to process. |
977 | * @param int $draftitemid the draft file area the content was using. | |
978 | * @param bool $forcehttps whether the content contains https URLs. Default false. | |
979 | * @return string the processed content. | |
980 | */ | |
981 | function file_rewrite_urls_to_pluginfile($text, $draftitemid, $forcehttps = false) { | |
982 | global $CFG, $USER; | |
983 | ||
b0c6dc1c | 984 | $usercontext = context_user::instance($USER->id); |
8546def3 | 985 | |
9337cf32 PS |
986 | $wwwroot = $CFG->wwwroot; |
987 | if ($forcehttps) { | |
988 | $wwwroot = str_replace('http://', 'https://', $wwwroot); | |
7983d682 | 989 | } |
990 | ||
9337cf32 PS |
991 | // relink embedded files if text submitted - no absolute links allowed in database! |
992 | $text = str_ireplace("$wwwroot/draftfile.php/$usercontext->id/user/draft/$draftitemid/", '@@PLUGINFILE@@/', $text); | |
993 | ||
994 | if (strpos($text, 'draftfile.php?file=') !== false) { | |
995 | $matches = array(); | |
996 | preg_match_all("!$wwwroot/draftfile.php\?file=%2F{$usercontext->id}%2Fuser%2Fdraft%2F{$draftitemid}%2F[^'\",&<>|`\s:\\\\]+!iu", $text, $matches); | |
997 | if ($matches) { | |
998 | foreach ($matches[0] as $match) { | |
999 | $replace = str_ireplace('%2F', '/', $match); | |
1000 | $text = str_replace($match, $replace, $text); | |
1001 | } | |
1002 | } | |
1003 | $text = str_ireplace("$wwwroot/draftfile.php?file=/$usercontext->id/user/draft/$draftitemid/", '@@PLUGINFILE@@/', $text); | |
7983d682 | 1004 | } |
1005 | ||
7983d682 | 1006 | return $text; |
1007 | } | |
1008 | ||
f79321f1 DC |
1009 | /** |
1010 | * Set file sort order | |
d2b7803e DC |
1011 | * |
1012 | * @global moodle_database $DB | |
1013 | * @param int $contextid the context id | |
1014 | * @param string $component file component | |
f79321f1 | 1015 | * @param string $filearea file area. |
d2b7803e | 1016 | * @param int $itemid itemid. |
f79321f1 DC |
1017 | * @param string $filepath file path. |
1018 | * @param string $filename file name. | |
d2b7803e DC |
1019 | * @param int $sortorder the sort order of file. |
1020 | * @return bool | |
f79321f1 | 1021 | */ |
64f93798 | 1022 | function file_set_sortorder($contextid, $component, $filearea, $itemid, $filepath, $filename, $sortorder) { |
f79321f1 | 1023 | global $DB; |
64f93798 | 1024 | $conditions = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea, 'itemid'=>$itemid, 'filepath'=>$filepath, 'filename'=>$filename); |
f79321f1 DC |
1025 | if ($file_record = $DB->get_record('files', $conditions)) { |
1026 | $sortorder = (int)$sortorder; | |
1027 | $file_record->sortorder = $sortorder; | |
1028 | $DB->update_record('files', $file_record); | |
1029 | return true; | |
1030 | } | |
1031 | return false; | |
1032 | } | |
1033 | ||
1034 | /** | |
1035 | * reset file sort order number to 0 | |
d2b7803e DC |
1036 | * @global moodle_database $DB |
1037 | * @param int $contextid the context id | |
64f93798 | 1038 | * @param string $component |
f79321f1 | 1039 | * @param string $filearea file area. |
d2b7803e DC |
1040 | * @param int|bool $itemid itemid. |
1041 | * @return bool | |
f79321f1 | 1042 | */ |
64f93798 | 1043 | function file_reset_sortorder($contextid, $component, $filearea, $itemid=false) { |
f79321f1 DC |
1044 | global $DB; |
1045 | ||
64f93798 | 1046 | $conditions = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea); |
f79321f1 DC |
1047 | if ($itemid !== false) { |
1048 | $conditions['itemid'] = $itemid; | |
1049 | } | |
1050 | ||
1051 | $file_records = $DB->get_records('files', $conditions); | |
1052 | foreach ($file_records as $file_record) { | |
1053 | $file_record->sortorder = 0; | |
1054 | $DB->update_record('files', $file_record); | |
1055 | } | |
1056 | return true; | |
1057 | } | |
1058 | ||
a83ad946 | 1059 | /** |
1060 | * Returns description of upload error | |
ba21c9d4 | 1061 | * |
a83ad946 | 1062 | * @param int $errorcode found in $_FILES['filename.ext']['error'] |
ba21c9d4 | 1063 | * @return string error description string, '' if ok |
a83ad946 | 1064 | */ |
1065 | function file_get_upload_error($errorcode) { | |
a08171c5 | 1066 | |
a83ad946 | 1067 | switch ($errorcode) { |
1068 | case 0: // UPLOAD_ERR_OK - no error | |
1069 | $errmessage = ''; | |
1070 | break; | |
a08171c5 | 1071 | |
a83ad946 | 1072 | case 1: // UPLOAD_ERR_INI_SIZE |
1073 | $errmessage = get_string('uploadserverlimit'); | |
1074 | break; | |
a08171c5 | 1075 | |
a83ad946 | 1076 | case 2: // UPLOAD_ERR_FORM_SIZE |
1077 | $errmessage = get_string('uploadformlimit'); | |
1078 | break; | |
a08171c5 | 1079 | |
a83ad946 | 1080 | case 3: // UPLOAD_ERR_PARTIAL |
1081 | $errmessage = get_string('uploadpartialfile'); | |
1082 | break; | |
a08171c5 | 1083 | |
a83ad946 | 1084 | case 4: // UPLOAD_ERR_NO_FILE |
1085 | $errmessage = get_string('uploadnofilefound'); | |
1086 | break; | |
a08171c5 | 1087 | |
a83ad946 | 1088 | // Note: there is no error with a value of 5 |
1089 | ||
1090 | case 6: // UPLOAD_ERR_NO_TMP_DIR | |
1091 | $errmessage = get_string('uploadnotempdir'); | |
1092 | break; | |
1093 | ||
1094 | case 7: // UPLOAD_ERR_CANT_WRITE | |
1095 | $errmessage = get_string('uploadcantwrite'); | |
1096 | break; | |
1097 | ||
1098 | case 8: // UPLOAD_ERR_EXTENSION | |
1099 | $errmessage = get_string('uploadextension'); | |
1100 | break; | |
1101 | ||
1102 | default: | |
1103 | $errmessage = get_string('uploadproblem'); | |
1104 | } | |
1105 | ||
1106 | return $errmessage; | |
1107 | } | |
1108 | ||
5db608f4 | 1109 | /** |
1110 | * Recursive function formating an array in POST parameter | |
1111 | * @param array $arraydata - the array that we are going to format and add into &$data array | |
1112 | * @param string $currentdata - a row of the final postdata array at instant T | |
1113 | * when finish, it's assign to $data under this format: name[keyname][][]...[]='value' | |
1114 | * @param array $data - the final data array containing all POST parameters : 1 row = 1 parameter | |
1115 | */ | |
1116 | function format_array_postdata_for_curlcall($arraydata, $currentdata, &$data) { | |
1117 | foreach ($arraydata as $k=>$v) { | |
277c7a40 | 1118 | $newcurrentdata = $currentdata; |
5db608f4 | 1119 | if (is_array($v)) { //the value is an array, call the function recursively |
277c7a40 | 1120 | $newcurrentdata = $newcurrentdata.'['.urlencode($k).']'; |
1121 | format_array_postdata_for_curlcall($v, $newcurrentdata, $data); | |
5db608f4 | 1122 | } else { //add the POST parameter to the $data array |
277c7a40 | 1123 | $data[] = $newcurrentdata.'['.urlencode($k).']='.urlencode($v); |
5db608f4 | 1124 | } |
1125 | } | |
1126 | } | |
1127 | ||
1128 | /** | |
1129 | * Transform a PHP array into POST parameter | |
1130 | * (see the recursive function format_array_postdata_for_curlcall) | |
1131 | * @param array $postdata | |
1132 | * @return array containing all POST parameters (1 row = 1 POST parameter) | |
1133 | */ | |
1134 | function format_postdata_for_curlcall($postdata) { | |
1135 | $data = array(); | |
1136 | foreach ($postdata as $k=>$v) { | |
1137 | if (is_array($v)) { | |
1138 | $currentdata = urlencode($k); | |
1139 | format_array_postdata_for_curlcall($v, $currentdata, $data); | |
1140 | } else { | |
ea1780ad | 1141 | $data[] = urlencode($k).'='.urlencode($v); |
5db608f4 | 1142 | } |
1143 | } | |
1144 | $convertedpostdata = implode('&', $data); | |
1145 | return $convertedpostdata; | |
1146 | } | |
1147 | ||
8ee88311 | 1148 | /** |
5f8bdc17 | 1149 | * Fetches content of file from Internet (using proxy if defined). Uses cURL extension if present. |
599f06cf | 1150 | * Due to security concerns only downloads from http(s) sources are supported. |
1151 | * | |
d2b7803e | 1152 | * @category files |
599f06cf | 1153 | * @param string $url file url starting with http(s):// |
5ef082df | 1154 | * @param array $headers http headers, null if none. If set, should be an |
1155 | * associative array of header name => value pairs. | |
6bf55889 | 1156 | * @param array $postdata array means use POST request with given parameters |
1157 | * @param bool $fullresponse return headers, responses, etc in a similar way snoopy does | |
5ef082df | 1158 | * (if false, just returns content) |
1159 | * @param int $timeout timeout for complete download process including all file transfer | |
44e02d79 | 1160 | * (default 5 minutes) |
1161 | * @param int $connecttimeout timeout for connection to server; this is the timeout that | |
1162 | * usually happens if the remote server is completely down (default 20 seconds); | |
1163 | * may not work when using proxy | |
98eaf27e | 1164 | * @param bool $skipcertverify If true, the peer's SSL certificate will not be checked. |
5f1c825d SH |
1165 | * Only use this when already in a trusted location. |
1166 | * @param string $tofile store the downloaded content to file instead of returning it. | |
98eaf27e | 1167 | * @param bool $calctimeout false by default, true enables an extra head request to try and determine |
5f1c825d | 1168 | * filesize and appropriately larger timeout based on $CFG->curltimeoutkbitrate |
feff774a SL |
1169 | * @return stdClass|string|bool stdClass object if $fullresponse is true, false if request failed, true |
1170 | * if file downloaded into $tofile successfully or the file content as a string. | |
8ee88311 | 1171 | */ |
60b5a2fe | 1172 | function download_file_content($url, $headers=null, $postdata=null, $fullresponse=false, $timeout=300, $connecttimeout=20, $skipcertverify=false, $tofile=NULL, $calctimeout=false) { |
e27f0765 | 1173 | global $CFG; |
1174 | ||
220eef0e | 1175 | // Only http and https links supported. |
599f06cf | 1176 | if (!preg_match('|^https?://|i', $url)) { |
1177 | if ($fullresponse) { | |
365a5941 | 1178 | $response = new stdClass(); |
599f06cf | 1179 | $response->status = 0; |
1180 | $response->headers = array(); | |
1181 | $response->response_code = 'Invalid protocol specified in url'; | |
1182 | $response->results = ''; | |
1183 | $response->error = 'Invalid protocol specified in url'; | |
1184 | return $response; | |
1185 | } else { | |
1186 | return false; | |
1187 | } | |
1188 | } | |
1189 | ||
220eef0e | 1190 | $options = array(); |
599f06cf | 1191 | |
220eef0e PS |
1192 | $headers2 = array(); |
1193 | if (is_array($headers)) { | |
6bf55889 | 1194 | foreach ($headers as $key => $value) { |
220eef0e PS |
1195 | if (is_numeric($key)) { |
1196 | $headers2[] = $value; | |
1197 | } else { | |
1198 | $headers2[] = "$key: $value"; | |
1199 | } | |
6bf55889 | 1200 | } |
6bf55889 | 1201 | } |
1202 | ||
83947a36 | 1203 | if ($skipcertverify) { |
220eef0e PS |
1204 | $options['CURLOPT_SSL_VERIFYPEER'] = false; |
1205 | } else { | |
1206 | $options['CURLOPT_SSL_VERIFYPEER'] = true; | |
83947a36 | 1207 | } |
bb2c046d | 1208 | |
220eef0e PS |
1209 | $options['CURLOPT_CONNECTTIMEOUT'] = $connecttimeout; |
1210 | ||
1211 | $options['CURLOPT_FOLLOWLOCATION'] = 1; | |
1212 | $options['CURLOPT_MAXREDIRS'] = 5; | |
1213 | ||
1214 | // Use POST if requested. | |
6bf55889 | 1215 | if (is_array($postdata)) { |
5db608f4 | 1216 | $postdata = format_postdata_for_curlcall($postdata); |
220eef0e PS |
1217 | } else if (empty($postdata)) { |
1218 | $postdata = null; | |
5f8bdc17 | 1219 | } |
ea1780ad | 1220 | |
220eef0e PS |
1221 | // Optionally attempt to get more correct timeout by fetching the file size. |
1222 | if (!isset($CFG->curltimeoutkbitrate)) { | |
1223 | // Use very slow rate of 56kbps as a timeout speed when not set. | |
1224 | $bitrate = 56; | |
1225 | } else { | |
1226 | $bitrate = $CFG->curltimeoutkbitrate; | |
c2140b5d | 1227 | } |
220eef0e PS |
1228 | if ($calctimeout and !isset($postdata)) { |
1229 | $curl = new curl(); | |
1230 | $curl->setHeader($headers2); | |
c2140b5d | 1231 | |
220eef0e | 1232 | $curl->head($url, $postdata, $options); |
6bf55889 | 1233 | |
220eef0e PS |
1234 | $info = $curl->get_info(); |
1235 | $error_no = $curl->get_errno(); | |
1236 | if (!$error_no && $info['download_content_length'] > 0) { | |
1237 | // No curl errors - adjust for large files only - take max timeout. | |
1238 | $timeout = max($timeout, ceil($info['download_content_length'] * 8 / ($bitrate * 1024))); | |
5f8bdc17 | 1239 | } |
220eef0e | 1240 | } |
5f8bdc17 | 1241 | |
220eef0e PS |
1242 | $curl = new curl(); |
1243 | $curl->setHeader($headers2); | |
08ec989f | 1244 | |
220eef0e PS |
1245 | $options['CURLOPT_RETURNTRANSFER'] = true; |
1246 | $options['CURLOPT_NOBODY'] = false; | |
1247 | $options['CURLOPT_TIMEOUT'] = $timeout; | |
5f8bdc17 | 1248 | |
220eef0e PS |
1249 | if ($tofile) { |
1250 | $fh = fopen($tofile, 'w'); | |
1251 | if (!$fh) { | |
1252 | if ($fullresponse) { | |
1253 | $response = new stdClass(); | |
1254 | $response->status = 0; | |
1255 | $response->headers = array(); | |
1256 | $response->response_code = 'Can not write to file'; | |
1257 | $response->results = false; | |
1258 | $response->error = 'Can not write to file'; | |
1259 | return $response; | |
1260 | } else { | |
1261 | return false; | |
5f8bdc17 | 1262 | } |
8ee88311 | 1263 | } |
220eef0e | 1264 | $options['CURLOPT_FILE'] = $fh; |
8ee88311 | 1265 | } |
6bf55889 | 1266 | |
220eef0e PS |
1267 | if (isset($postdata)) { |
1268 | $content = $curl->post($url, $postdata, $options); | |
60b5a2fe | 1269 | } else { |
220eef0e | 1270 | $content = $curl->get($url, null, $options); |
60b5a2fe AB |
1271 | } |
1272 | ||
220eef0e PS |
1273 | if ($tofile) { |
1274 | fclose($fh); | |
1275 | @chmod($tofile, $CFG->filepermissions); | |
60b5a2fe AB |
1276 | } |
1277 | ||
220eef0e PS |
1278 | /* |
1279 | // Try to detect encoding problems. | |
6bf55889 | 1280 | if ((curl_errno($ch) == 23 or curl_errno($ch) == 61) and defined('CURLOPT_ENCODING')) { |
1281 | curl_setopt($ch, CURLOPT_ENCODING, 'none'); | |
3a1055a5 PS |
1282 | $result = curl_exec($ch); |
1283 | } | |
220eef0e | 1284 | */ |
3a1055a5 | 1285 | |
220eef0e PS |
1286 | $info = $curl->get_info(); |
1287 | $error_no = $curl->get_errno(); | |
1288 | $rawheaders = $curl->get_raw_response(); | |
6bf55889 | 1289 | |
220eef0e PS |
1290 | if ($error_no) { |
1291 | $error = $content; | |
1292 | if (!$fullresponse) { | |
599f06cf | 1293 | debugging("cURL request for \"$url\" failed with: $error ($error_no)", DEBUG_ALL); |
6bf55889 | 1294 | return false; |
1295 | } | |
5f8bdc17 | 1296 | |
220eef0e PS |
1297 | $response = new stdClass(); |
1298 | if ($error_no == 28) { | |
1299 | $response->status = '-100'; // Mimic snoopy. | |
599f06cf | 1300 | } else { |
220eef0e | 1301 | $response->status = '0'; |
599f06cf | 1302 | } |
220eef0e PS |
1303 | $response->headers = array(); |
1304 | $response->response_code = $error; | |
1305 | $response->results = false; | |
1306 | $response->error = $error; | |
1307 | return $response; | |
1308 | } | |
6bf55889 | 1309 | |
220eef0e PS |
1310 | if ($tofile) { |
1311 | $content = true; | |
08ec989f | 1312 | } |
8ee88311 | 1313 | |
220eef0e PS |
1314 | if (empty($info['http_code'])) { |
1315 | // For security reasons we support only true http connections (Location: file:// exploit prevention). | |
1316 | $response = new stdClass(); | |
1317 | $response->status = '0'; | |
1318 | $response->headers = array(); | |
1319 | $response->response_code = 'Unknown cURL error'; | |
1320 | $response->results = false; // do NOT change this, we really want to ignore the result! | |
1321 | $response->error = 'Unknown cURL error'; | |
3a1055a5 | 1322 | |
220eef0e PS |
1323 | } else { |
1324 | $response = new stdClass(); | |
1325 | $response->status = (string)$info['http_code']; | |
1326 | $response->headers = $rawheaders; | |
1327 | $response->results = $content; | |
1328 | $response->error = ''; | |
1329 | ||
1330 | // There might be multiple headers on redirect, find the status of the last one. | |
1331 | $firstline = true; | |
1332 | foreach ($rawheaders as $line) { | |
1333 | if ($firstline) { | |
1334 | $response->response_code = $line; | |
1335 | $firstline = false; | |
1336 | } | |
1337 | if (trim($line, "\r\n") === '') { | |
1338 | $firstline = true; | |
1339 | } | |
3a1055a5 PS |
1340 | } |
1341 | } | |
220eef0e PS |
1342 | |
1343 | if ($fullresponse) { | |
1344 | return $response; | |
3a1055a5 | 1345 | } |
220eef0e PS |
1346 | |
1347 | if ($info['http_code'] != 200) { | |
1348 | debugging("cURL request for \"$url\" failed, HTTP response code: ".$response->response_code, DEBUG_ALL); | |
1349 | return false; | |
1350 | } | |
1351 | return $response->results; | |
3a1055a5 PS |
1352 | } |
1353 | ||
3ce73b14 | 1354 | /** |
559276b1 MG |
1355 | * Returns a list of information about file types based on extensions. |
1356 | * | |
1357 | * The following elements expected in value array for each extension: | |
1358 | * 'type' - mimetype | |
1359 | * 'icon' - location of the icon file. If value is FILENAME, then either pix/f/FILENAME.gif | |
1360 | * or pix/f/FILENAME.png must be present in moodle and contain 16x16 filetype icon; | |
1361 | * also files with bigger sizes under names | |
1362 | * FILENAME-24, FILENAME-32, FILENAME-64, FILENAME-128, FILENAME-256 are recommended. | |
1363 | * 'groups' (optional) - array of filetype groups this filetype extension is part of; | |
1364 | * commonly used in moodle the following groups: | |
1365 | * - web_image - image that can be included as <img> in HTML | |
1366 | * - image - image that we can parse using GD to find it's dimensions, also used for portfolio format | |
1367 | * - video - file that can be imported as video in text editor | |
1368 | * - audio - file that can be imported as audio in text editor | |
1369 | * - archive - we can extract files from this archive | |
1370 | * - spreadsheet - used for portfolio format | |
1371 | * - document - used for portfolio format | |
1372 | * - presentation - used for portfolio format | |
1373 | * 'string' (optional) - the name of the string from lang/en/mimetypes.php that displays | |
1374 | * human-readable description for this filetype; | |
1375 | * Function {@link get_mimetype_description()} first looks at the presence of string for | |
1376 | * particular mimetype (value of 'type'), if not found looks for string specified in 'string' | |
1377 | * attribute, if not found returns the value of 'type'; | |
1378 | * 'defaulticon' (boolean, optional) - used by function {@link file_mimetype_icon()} to find | |
1379 | * an icon for mimetype. If an entry with 'defaulticon' is not found for a particular mimetype, | |
1380 | * this function will return first found icon; Especially usefull for types such as 'text/plain' | |
d2b7803e DC |
1381 | * |
1382 | * @category files | |
ba21c9d4 | 1383 | * @return array List of information about file types based on extensions. |
3ce73b14 | 1384 | * Associative array of extension (lower-case) to associative array |
1385 | * from 'element name' to data. Current element names are 'type' and 'icon'. | |
76ca1ff1 | 1386 | * Unknown types should use the 'xxx' entry which includes defaults. |
3ce73b14 | 1387 | */ |
559276b1 | 1388 | function &get_mimetypes_array() { |
91fed57a | 1389 | // Get types from the core_filetypes function, which includes caching. |
1390 | return core_filetypes::get_types(); | |
3ce73b14 | 1391 | } |
1392 | ||
0c4c3b9a JP |
1393 | /** |
1394 | * Determine a file's MIME type based on the given filename using the function mimeinfo. | |
1395 | * | |
1396 | * This function retrieves a file's MIME type for a file that will be sent to the user. | |
1397 | * This should only be used for file-sending purposes just like in send_stored_file, send_file, and send_temp_file. | |
1398 | * Should the file's MIME type cannot be determined by mimeinfo, it will return 'application/octet-stream' as a default | |
1399 | * MIME type which should tell the browser "I don't know what type of file this is, so just download it.". | |
1400 | * | |
1401 | * @param string $filename The file's filename. | |
1402 | * @return string The file's MIME type or 'application/octet-stream' if it cannot be determined. | |
1403 | */ | |
1404 | function get_mimetype_for_sending($filename = '') { | |
1405 | // Guess the file's MIME type using mimeinfo. | |
1406 | $mimetype = mimeinfo('type', $filename); | |
1407 | ||
1408 | // Use octet-stream as fallback if MIME type cannot be determined by mimeinfo. | |
1409 | if (!$mimetype || $mimetype === 'document/unknown') { | |
1410 | $mimetype = 'application/octet-stream'; | |
1411 | } | |
1412 | ||
1413 | return $mimetype; | |
1414 | } | |
1415 | ||
76ca1ff1 | 1416 | /** |
3ce73b14 | 1417 | * Obtains information about a filetype based on its extension. Will |
1418 | * use a default if no information is present about that particular | |
1419 | * extension. | |
ba21c9d4 | 1420 | * |
d2b7803e | 1421 | * @category files |
76ca1ff1 | 1422 | * @param string $element Desired information (usually 'icon' |
559276b1 MG |
1423 | * for icon filename or 'type' for MIME type. Can also be |
1424 | * 'icon24', ...32, 48, 64, 72, 80, 96, 128, 256) | |
76ca1ff1 | 1425 | * @param string $filename Filename we're looking up |
3ce73b14 | 1426 | * @return string Requested piece of information from array |
1427 | */ | |
1428 | function mimeinfo($element, $filename) { | |
0ef98843 | 1429 | global $CFG; |
559276b1 MG |
1430 | $mimeinfo = & get_mimetypes_array(); |
1431 | static $iconpostfixes = array(256=>'-256', 128=>'-128', 96=>'-96', 80=>'-80', 72=>'-72', 64=>'-64', 48=>'-48', 32=>'-32', 24=>'-24', 16=>''); | |
1432 | ||
1433 | $filetype = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); | |
1434 | if (empty($filetype)) { | |
1435 | $filetype = 'xxx'; // file without extension | |
1436 | } | |
1437 | if (preg_match('/^icon(\d*)$/', $element, $iconsizematch)) { | |
1438 | $iconsize = max(array(16, (int)$iconsizematch[1])); | |
1439 | $filenames = array($mimeinfo['xxx']['icon']); | |
1440 | if ($filetype != 'xxx' && isset($mimeinfo[$filetype]['icon'])) { | |
1441 | array_unshift($filenames, $mimeinfo[$filetype]['icon']); | |
1442 | } | |
1443 | // find the file with the closest size, first search for specific icon then for default icon | |
1444 | foreach ($filenames as $filename) { | |
1445 | foreach ($iconpostfixes as $size => $postfix) { | |
1446 | $fullname = $CFG->dirroot.'/pix/f/'.$filename.$postfix; | |
1447 | if ($iconsize >= $size && (file_exists($fullname.'.png') || file_exists($fullname.'.gif'))) { | |
1448 | return $filename.$postfix; | |
72aa74ce | 1449 | } |
0ef98843 | 1450 | } |
f1e0649c | 1451 | } |
559276b1 MG |
1452 | } else if (isset($mimeinfo[$filetype][$element])) { |
1453 | return $mimeinfo[$filetype][$element]; | |
1454 | } else if (isset($mimeinfo['xxx'][$element])) { | |
a370c895 | 1455 | return $mimeinfo['xxx'][$element]; // By default |
559276b1 MG |
1456 | } else { |
1457 | return null; | |
f1e0649c | 1458 | } |
1459 | } | |
1460 | ||
76ca1ff1 | 1461 | /** |
3ce73b14 | 1462 | * Obtains information about a filetype based on the MIME type rather than |
1463 | * the other way around. | |
ba21c9d4 | 1464 | * |
d2b7803e | 1465 | * @category files |
559276b1 | 1466 | * @param string $element Desired information ('extension', 'icon', 'icon-24', etc.) |
76ca1ff1 | 1467 | * @param string $mimetype MIME type we're looking up |
3ce73b14 | 1468 | * @return string Requested piece of information from array |
1469 | */ | |
1470 | function mimeinfo_from_type($element, $mimetype) { | |
559276b1 MG |
1471 | /* array of cached mimetype->extension associations */ |
1472 | static $cached = array(); | |
1473 | $mimeinfo = & get_mimetypes_array(); | |
1474 | ||
1475 | if (!array_key_exists($mimetype, $cached)) { | |
1082e7da | 1476 | $cached[$mimetype] = null; |
559276b1 MG |
1477 | foreach($mimeinfo as $filetype => $values) { |
1478 | if ($values['type'] == $mimetype) { | |
1479 | if ($cached[$mimetype] === null) { | |
e5635827 | 1480 | $cached[$mimetype] = '.'.$filetype; |
559276b1 MG |
1481 | } |
1482 | if (!empty($values['defaulticon'])) { | |
e5635827 | 1483 | $cached[$mimetype] = '.'.$filetype; |
559276b1 MG |
1484 | break; |
1485 | } | |
3ce73b14 | 1486 | } |
3ce73b14 | 1487 | } |
a8d6dda4 | 1488 | if (empty($cached[$mimetype])) { |
e5635827 | 1489 | $cached[$mimetype] = '.xxx'; |
559276b1 | 1490 | } |
a8d6dda4 MG |
1491 | } |
1492 | if ($element === 'extension') { | |
1493 | return $cached[$mimetype]; | |
559276b1 | 1494 | } else { |
e5635827 | 1495 | return mimeinfo($element, $cached[$mimetype]); |
559276b1 | 1496 | } |
3ce73b14 | 1497 | } |
b9709b76 | 1498 | |
42ead7d7 | 1499 | /** |
559276b1 | 1500 | * Return the relative icon path for a given file |
ba21c9d4 | 1501 | * |
559276b1 MG |
1502 | * Usage: |
1503 | * <code> | |
1504 | * // $file - instance of stored_file or file_info | |
1505 | * $icon = $OUTPUT->pix_url(file_file_icon($file))->out(); | |
1506 | * echo html_writer::empty_tag('img', array('src' => $icon, 'alt' => get_mimetype_description($file))); | |
1507 | * </code> | |
1508 | * or | |
1509 | * <code> | |
1510 | * echo $OUTPUT->pix_icon(file_file_icon($file), get_mimetype_description($file)); | |
1511 | * </code> | |
1512 | * | |
1513 | * @param stored_file|file_info|stdClass|array $file (in case of object attributes $file->filename | |
1514 | * and $file->mimetype are expected) | |
1515 | * @param int $size The size of the icon. Defaults to 16 can also be 24, 32, 64, 128, 256 | |
1516 | * @return string | |
42ead7d7 | 1517 | */ |
559276b1 MG |
1518 | function file_file_icon($file, $size = null) { |
1519 | if (!is_object($file)) { | |
1520 | $file = (object)$file; | |
1521 | } | |
1522 | if (isset($file->filename)) { | |
1523 | $filename = $file->filename; | |
1524 | } else if (method_exists($file, 'get_filename')) { | |
1525 | $filename = $file->get_filename(); | |
1526 | } else if (method_exists($file, 'get_visible_name')) { | |
1527 | $filename = $file->get_visible_name(); | |
1528 | } else { | |
1529 | $filename = ''; | |
637a60dc | 1530 | } |
559276b1 MG |
1531 | if (isset($file->mimetype)) { |
1532 | $mimetype = $file->mimetype; | |
1533 | } else if (method_exists($file, 'get_mimetype')) { | |
1534 | $mimetype = $file->get_mimetype(); | |
1535 | } else { | |
1536 | $mimetype = ''; | |
42ead7d7 | 1537 | } |
ae7f35b9 MG |
1538 | $mimetypes = &get_mimetypes_array(); |
1539 | if ($filename) { | |
1540 | $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); | |
1541 | if ($extension && !empty($mimetypes[$extension])) { | |
1542 | // if file name has known extension, return icon for this extension | |
1543 | return file_extension_icon($filename, $size); | |
1544 | } | |
0b46f19e | 1545 | } |
ae7f35b9 | 1546 | return file_mimetype_icon($mimetype, $size); |
559276b1 | 1547 | } |
637a60dc | 1548 | |
559276b1 MG |
1549 | /** |
1550 | * Return the relative icon path for a folder image | |
1551 | * | |
1552 | * Usage: | |
1553 | * <code> | |
1554 | * $icon = $OUTPUT->pix_url(file_folder_icon())->out(); | |
1555 | * echo html_writer::empty_tag('img', array('src' => $icon)); | |
1556 | * </code> | |
1557 | * or | |
1558 | * <code> | |
1559 | * echo $OUTPUT->pix_icon(file_folder_icon(32)); | |
1560 | * </code> | |
1561 | * | |
1562 | * @param int $iconsize The size of the icon. Defaults to 16 can also be 24, 32, 48, 64, 72, 80, 96, 128, 256 | |
1563 | * @return string | |
1564 | */ | |
1565 | function file_folder_icon($iconsize = null) { | |
1566 | global $CFG; | |
1567 | static $iconpostfixes = array(256=>'-256', 128=>'-128', 96=>'-96', 80=>'-80', 72=>'-72', 64=>'-64', 48=>'-48', 32=>'-32', 24=>'-24', 16=>''); | |
ae7f35b9 | 1568 | static $cached = array(); |
559276b1 | 1569 | $iconsize = max(array(16, (int)$iconsize)); |
ae7f35b9 MG |
1570 | if (!array_key_exists($iconsize, $cached)) { |
1571 | foreach ($iconpostfixes as $size => $postfix) { | |
1572 | $fullname = $CFG->dirroot.'/pix/f/folder'.$postfix; | |
1573 | if ($iconsize >= $size && (file_exists($fullname.'.png') || file_exists($fullname.'.gif'))) { | |
1574 | $cached[$iconsize] = 'f/folder'.$postfix; | |
1575 | break; | |
1576 | } | |
559276b1 | 1577 | } |
637a60dc | 1578 | } |
ae7f35b9 | 1579 | return $cached[$iconsize]; |
42ead7d7 | 1580 | } |
1581 | ||
9dffa7af | 1582 | /** |
1583 | * Returns the relative icon path for a given mime type | |
1584 | * | |
34ff25a6 | 1585 | * This function should be used in conjunction with $OUTPUT->pix_url to produce |
9dffa7af | 1586 | * a return the full path to an icon. |
1587 | * | |
1588 | * <code> | |
1589 | * $mimetype = 'image/jpg'; | |
559276b1 MG |
1590 | * $icon = $OUTPUT->pix_url(file_mimetype_icon($mimetype))->out(); |
1591 | * echo html_writer::empty_tag('img', array('src' => $icon, 'alt' => get_mimetype_description($mimetype))); | |
9dffa7af | 1592 | * </code> |
1593 | * | |
d2b7803e DC |
1594 | * @category files |
1595 | * @todo MDL-31074 When an $OUTPUT->icon method is available this function should be altered | |
9dffa7af | 1596 | * to conform with that. |
9dffa7af | 1597 | * @param string $mimetype The mimetype to fetch an icon for |
559276b1 | 1598 | * @param int $size The size of the icon. Defaults to 16 can also be 24, 32, 64, 128, 256 |
9dffa7af | 1599 | * @return string The relative path to the icon |
1600 | */ | |
ede72522 | 1601 | function file_mimetype_icon($mimetype, $size = NULL) { |
559276b1 | 1602 | return 'f/'.mimeinfo_from_type('icon'.$size, $mimetype); |
9dffa7af | 1603 | } |
1604 | ||
1605 | /** | |
2dcb7d0b | 1606 | * Returns the relative icon path for a given file name |
9dffa7af | 1607 | * |
34ff25a6 | 1608 | * This function should be used in conjunction with $OUTPUT->pix_url to produce |
9dffa7af | 1609 | * a return the full path to an icon. |
1610 | * | |
9dffa7af | 1611 | * <code> |
559276b1 MG |
1612 | * $filename = '.jpg'; |
1613 | * $icon = $OUTPUT->pix_url(file_extension_icon($filename))->out(); | |
1614 | * echo html_writer::empty_tag('img', array('src' => $icon, 'alt' => '...')); | |
9dffa7af | 1615 | * </code> |
1616 | * | |
d2b7803e | 1617 | * @todo MDL-31074 When an $OUTPUT->icon method is available this function should be altered |
9dffa7af | 1618 | * to conform with that. |
d2b7803e DC |
1619 | * @todo MDL-31074 Implement $size |
1620 | * @category files | |
1621 | * @param string $filename The filename to get the icon for | |
559276b1 | 1622 | * @param int $size The size of the icon. Defaults to 16 can also be 24, 32, 64, 128, 256 |
9dffa7af | 1623 | * @return string |
1624 | */ | |
ede72522 | 1625 | function file_extension_icon($filename, $size = NULL) { |
559276b1 | 1626 | return 'f/'.mimeinfo('icon'.$size, $filename); |
9dffa7af | 1627 | } |
1628 | ||
c0381e22 | 1629 | /** |
76ca1ff1 | 1630 | * Obtains descriptions for file types (e.g. 'Microsoft Word document') from the |
1631 | * mimetypes.php language file. | |
ba21c9d4 | 1632 | * |
559276b1 MG |
1633 | * @param mixed $obj - instance of stored_file or file_info or array/stdClass with field |
1634 | * 'filename' and 'mimetype', or just a string with mimetype (though it is recommended to | |
1635 | * have filename); In case of array/stdClass the field 'mimetype' is optional. | |
c0381e22 | 1636 | * @param bool $capitalise If true, capitalises first character of result |
76ca1ff1 | 1637 | * @return string Text description |
c0381e22 | 1638 | */ |
559276b1 | 1639 | function get_mimetype_description($obj, $capitalise=false) { |
ae7f35b9 | 1640 | $filename = $mimetype = ''; |
559276b1 MG |
1641 | if (is_object($obj) && method_exists($obj, 'get_filename') && method_exists($obj, 'get_mimetype')) { |
1642 | // this is an instance of stored_file | |
1643 | $mimetype = $obj->get_mimetype(); | |
1644 | $filename = $obj->get_filename(); | |
1645 | } else if (is_object($obj) && method_exists($obj, 'get_visible_name') && method_exists($obj, 'get_mimetype')) { | |
1646 | // this is an instance of file_info | |
1647 | $mimetype = $obj->get_mimetype(); | |
1648 | $filename = $obj->get_visible_name(); | |
1649 | } else if (is_array($obj) || is_object ($obj)) { | |
1650 | $obj = (array)$obj; | |
1651 | if (!empty($obj['filename'])) { | |
1652 | $filename = $obj['filename']; | |
559276b1 MG |
1653 | } |
1654 | if (!empty($obj['mimetype'])) { | |
1655 | $mimetype = $obj['mimetype']; | |
559276b1 MG |
1656 | } |
1657 | } else { | |
1658 | $mimetype = $obj; | |
ae7f35b9 MG |
1659 | } |
1660 | $mimetypefromext = mimeinfo('type', $filename); | |
1661 | if (empty($mimetype) || $mimetypefromext !== 'document/unknown') { | |
1662 | // if file has a known extension, overwrite the specified mimetype | |
1663 | $mimetype = $mimetypefromext; | |
559276b1 MG |
1664 | } |
1665 | $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); | |
1666 | if (empty($extension)) { | |
1667 | $mimetypestr = mimeinfo_from_type('string', $mimetype); | |
ae7f35b9 | 1668 | $extension = str_replace('.', '', mimeinfo_from_type('extension', $mimetype)); |
559276b1 MG |
1669 | } else { |
1670 | $mimetypestr = mimeinfo('string', $filename); | |
1671 | } | |
1672 | $chunks = explode('/', $mimetype, 2); | |
1673 | $chunks[] = ''; | |
1674 | $attr = array( | |
1675 | 'mimetype' => $mimetype, | |
1676 | 'ext' => $extension, | |
1677 | 'mimetype1' => $chunks[0], | |
1678 | 'mimetype2' => $chunks[1], | |
1679 | ); | |
1680 | $a = array(); | |
1681 | foreach ($attr as $key => $value) { | |
1682 | $a[$key] = $value; | |
1683 | $a[strtoupper($key)] = strtoupper($value); | |
1684 | $a[ucfirst($key)] = ucfirst($value); | |
1685 | } | |
ed0f5a95 | 1686 | |
1687 | // MIME types may include + symbol but this is not permitted in string ids. | |
1688 | $safemimetype = str_replace('+', '_', $mimetype); | |
1689 | $safemimetypestr = str_replace('+', '_', $mimetypestr); | |
91fed57a | 1690 | $customdescription = mimeinfo('customdescription', $filename); |
1691 | if ($customdescription) { | |
1692 | // Call format_string on the custom description so that multilang | |
b3d7eb6e | 1693 | // filter can be used (if enabled on system context). We use system |
1694 | // context because it is possible that the page context might not have | |
1695 | // been defined yet. | |
1696 | $result = format_string($customdescription, true, | |
1697 | array('context' => context_system::instance())); | |
91fed57a | 1698 | } else if (get_string_manager()->string_exists($safemimetype, 'mimetypes')) { |
ed0f5a95 | 1699 | $result = get_string($safemimetype, 'mimetypes', (object)$a); |
1700 | } else if (get_string_manager()->string_exists($safemimetypestr, 'mimetypes')) { | |
1701 | $result = get_string($safemimetypestr, 'mimetypes', (object)$a); | |
559276b1 MG |
1702 | } else if (get_string_manager()->string_exists('default', 'mimetypes')) { |
1703 | $result = get_string('default', 'mimetypes', (object)$a); | |
1b80c913 | 1704 | } else { |
559276b1 | 1705 | $result = $mimetype; |
76ca1ff1 | 1706 | } |
ede72522 | 1707 | if ($capitalise) { |
c0381e22 | 1708 | $result=ucfirst($result); |
1709 | } | |
1710 | return $result; | |
1711 | } | |
1712 | ||
559276b1 MG |
1713 | /** |
1714 | * Returns array of elements of type $element in type group(s) | |
1715 | * | |
1716 | * @param string $element name of the element we are interested in, usually 'type' or 'extension' | |
1717 | * @param string|array $groups one group or array of groups/extensions/mimetypes | |
1718 | * @return array | |
1719 | */ | |
1720 | function file_get_typegroup($element, $groups) { | |
1721 | static $cached = array(); | |
1722 | if (!is_array($groups)) { | |
1723 | $groups = array($groups); | |
1724 | } | |
1725 | if (!array_key_exists($element, $cached)) { | |
1726 | $cached[$element] = array(); | |
1727 | } | |
1728 | $result = array(); | |
1729 | foreach ($groups as $group) { | |
1730 | if (!array_key_exists($group, $cached[$element])) { | |
1731 | // retrieive and cache all elements of type $element for group $group | |
1732 | $mimeinfo = & get_mimetypes_array(); | |
1733 | $cached[$element][$group] = array(); | |
1734 | foreach ($mimeinfo as $extension => $value) { | |
e5635827 | 1735 | $value['extension'] = '.'.$extension; |
559276b1 MG |
1736 | if (empty($value[$element])) { |
1737 | continue; | |
1738 | } | |
ae7f35b9 | 1739 | if (($group === '.'.$extension || $group === $value['type'] || |
559276b1 MG |
1740 | (!empty($value['groups']) && in_array($group, $value['groups']))) && |
1741 | !in_array($value[$element], $cached[$element][$group])) { | |
1742 | $cached[$element][$group][] = $value[$element]; | |
1743 | } | |
1744 | } | |
1745 | } | |
1746 | $result = array_merge($result, $cached[$element][$group]); | |
1747 | } | |
41340f04 | 1748 | return array_values(array_unique($result)); |
559276b1 MG |
1749 | } |
1750 | ||
1751 | /** | |
1752 | * Checks if file with name $filename has one of the extensions in groups $groups | |
1753 | * | |
1754 | * @see get_mimetypes_array() | |
1755 | * @param string $filename name of the file to check | |
1756 | * @param string|array $groups one group or array of groups to check | |
1757 | * @param bool $checktype if true and extension check fails, find the mimetype and check if | |
1758 | * file mimetype is in mimetypes in groups $groups | |
1759 | * @return bool | |
1760 | */ | |
1761 | function file_extension_in_typegroup($filename, $groups, $checktype = false) { | |
1762 | $extension = pathinfo($filename, PATHINFO_EXTENSION); | |
e5635827 | 1763 | if (!empty($extension) && in_array('.'.strtolower($extension), file_get_typegroup('extension', $groups))) { |
559276b1 MG |
1764 | return true; |
1765 | } | |
1766 | return $checktype && file_mimetype_in_typegroup(mimeinfo('type', $filename), $groups); | |
1767 | } | |
1768 | ||
1769 | /** | |
1770 | * Checks if mimetype $mimetype belongs to one of the groups $groups | |
1771 | * | |
1772 | * @see get_mimetypes_array() | |
1773 | * @param string $mimetype | |
1774 | * @param string|array $groups one group or array of groups to check | |
1775 | * @return bool | |
1776 | */ | |
1777 | function file_mimetype_in_typegroup($mimetype, $groups) { | |
1778 | return !empty($mimetype) && in_array($mimetype, file_get_typegroup('type', $groups)); | |
1779 | } | |
1780 | ||
9e5fa330 | 1781 | /** |
d2b7803e | 1782 | * Requested file is not found or not accessible, does not return, terminates script |
ba21c9d4 | 1783 | * |
d2b7803e DC |
1784 | * @global stdClass $CFG |
1785 | * @global stdClass $COURSE | |
9e5fa330 | 1786 | */ |
1787 | function send_file_not_found() { | |
1788 | global $CFG, $COURSE; | |
3ca3d259 JL |
1789 | |
1790 | // Allow cross-origin requests only for Web Services. | |
1791 | // This allow to receive requests done by Web Workers or webapps in different domains. | |
1792 | if (WS_SERVER) { | |
1793 | header('Access-Control-Allow-Origin: *'); | |
1794 | } | |
1795 | ||
dbdc7355 | 1796 | send_header_404(); |
9e5fa330 | 1797 | print_error('filenotfound', 'error', $CFG->wwwroot.'/course/view.php?id='.$COURSE->id); //this is not displayed on IIS?? |
1798 | } | |
dbdc7355 DM |
1799 | /** |
1800 | * Helper function to send correct 404 for server. | |
1801 | */ | |
1802 | function send_header_404() { | |
1803 | if (substr(php_sapi_name(), 0, 3) == 'cgi') { | |
1804 | header("Status: 404 Not Found"); | |
1805 | } else { | |
1806 | header('HTTP/1.0 404 not found'); | |
1807 | } | |
1808 | } | |
9e5fa330 | 1809 | |
137885b7 | 1810 | /** |
1811 | * The readfile function can fail when files are larger than 2GB (even on 64-bit | |
1812 | * platforms). This wrapper uses readfile for small files and custom code for | |
1813 | * large ones. | |
1814 | * | |
1815 | * @param string $path Path to file | |
1816 | * @param int $filesize Size of file (if left out, will get it automatically) | |
1817 | * @return int|bool Size read (will always be $filesize) or false if failed | |
1818 | */ | |
1819 | function readfile_allow_large($path, $filesize = -1) { | |
1820 | // Automatically get size if not specified. | |
1821 | if ($filesize === -1) { | |
1822 | $filesize = filesize($path); | |
1823 | } | |
1824 | if ($filesize <= 2147483647) { | |
1825 | // If the file is up to 2^31 - 1, send it normally using readfile. | |
1826 | return readfile($path); | |
1827 | } else { | |
1828 | // For large files, read and output in 64KB chunks. | |
1829 | $handle = fopen($path, 'r'); | |
1830 | if ($handle === false) { | |
1831 | return false; | |
1832 | } | |
1833 | $left = $filesize; | |
1834 | while ($left > 0) { | |
1835 | $size = min($left, 65536); | |
1836 | $buffer = fread($handle, $size); | |
1837 | if ($buffer === false) { | |
1838 | return false; | |
1839 | } | |
1840 | echo $buffer; | |
1841 | $left -= $size; | |
1842 | } | |
1843 | return $filesize; | |
1844 | } | |
1845 | } | |
1846 | ||
cbad562e | 1847 | /** |
d5dd0540 PS |
1848 | * Enhanced readfile() with optional acceleration. |
1849 | * @param string|stored_file $file | |
1850 | * @param string $mimetype | |
1851 | * @param bool $accelerate | |
1852 | * @return void | |
cbad562e | 1853 | */ |
d5dd0540 PS |
1854 | function readfile_accel($file, $mimetype, $accelerate) { |
1855 | global $CFG; | |
1856 | ||
1857 | if ($mimetype === 'text/plain') { | |
1858 | // there is no encoding specified in text files, we need something consistent | |
1859 | header('Content-Type: text/plain; charset=utf-8'); | |
1860 | } else { | |
1861 | header('Content-Type: '.$mimetype); | |
596c7115 PS |
1862 | } |
1863 | ||
d5dd0540 PS |
1864 | $lastmodified = is_object($file) ? $file->get_timemodified() : filemtime($file); |
1865 | header('Last-Modified: '. gmdate('D, d M Y H:i:s', $lastmodified) .' GMT'); | |
116ee3e4 | 1866 | |
d5dd0540 | 1867 | if (is_object($file)) { |
78030f9d | 1868 | header('Etag: "' . $file->get_contenthash() . '"'); |
9cc5f32b | 1869 | if (isset($_SERVER['HTTP_IF_NONE_MATCH']) and trim($_SERVER['HTTP_IF_NONE_MATCH'], '"') === $file->get_contenthash()) { |
d5dd0540 PS |
1870 | header('HTTP/1.1 304 Not Modified'); |
1871 | return; | |
1872 | } | |
1873 | } | |
1874 | ||
1875 | // if etag present for stored file rely on it exclusively | |
1876 | if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) and (empty($_SERVER['HTTP_IF_NONE_MATCH']) or !is_object($file))) { | |
1877 | // get unixtime of request header; clip extra junk off first | |
1878 | $since = strtotime(preg_replace('/;.*$/', '', $_SERVER["HTTP_IF_MODIFIED_SINCE"])); | |
1879 | if ($since && $since >= $lastmodified) { | |
1880 | header('HTTP/1.1 304 Not Modified'); | |
1881 | return; | |
1882 | } | |
1883 | } | |
1884 | ||
1885 | if ($accelerate and !empty($CFG->xsendfile)) { | |
1886 | if (empty($CFG->disablebyteserving) and $mimetype !== 'text/plain') { | |
1887 | header('Accept-Ranges: bytes'); | |
1888 | } else { | |
1889 | header('Accept-Ranges: none'); | |
1890 | } | |
1891 | ||
1892 | if (is_object($file)) { | |
1893 | $fs = get_file_storage(); | |
1894 | if ($fs->xsendfile($file->get_contenthash())) { | |
1895 | return; | |
1896 | } | |
1897 | ||
1898 | } else { | |
1899 | require_once("$CFG->libdir/xsendfilelib.php"); | |
1900 | if (xsendfile($file)) { | |
1901 | return; | |
1902 | } | |
1903 | } | |
1904 | } | |
1905 | ||
1906 | $filesize = is_object($file) ? $file->get_filesize() : filesize($file); | |
1907 | ||
1908 | header('Last-Modified: '. gmdate('D, d M Y H:i:s', $lastmodified) .' GMT'); | |
1909 | ||
1910 | if ($accelerate and empty($CFG->disablebyteserving) and $mimetype !== 'text/plain') { | |
1911 | header('Accept-Ranges: bytes'); | |
1912 | ||
1913 | if (!empty($_SERVER['HTTP_RANGE']) and strpos($_SERVER['HTTP_RANGE'],'bytes=') !== FALSE) { | |
1914 | // byteserving stuff - for acrobat reader and download accelerators | |
1915 | // see: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35 | |
1916 | // inspired by: http://www.coneural.org/florian/papers/04_byteserving.php | |
1917 | $ranges = false; | |
1918 | if (preg_match_all('/(\d*)-(\d*)/', $_SERVER['HTTP_RANGE'], $ranges, PREG_SET_ORDER)) { | |
1919 | foreach ($ranges as $key=>$value) { | |
1920 | if ($ranges[$key][1] == '') { | |
1921 | //suffix case | |
1922 | $ranges[$key][1] = $filesize - $ranges[$key][2]; | |
1923 | $ranges[$key][2] = $filesize - 1; | |
1924 | } else if ($ranges[$key][2] == '' || $ranges[$key][2] > $filesize - 1) { | |
1925 | //fix range length | |
1926 | $ranges[$key][2] = $filesize - 1; | |
1927 | } | |
1928 | if ($ranges[$key][2] != '' && $ranges[$key][2] < $ranges[$key][1]) { | |
1929 | //invalid byte-range ==> ignore header | |
1930 | $ranges = false; | |
1931 | break; | |
1932 | } | |
1933 | //prepare multipart header | |
1934 | $ranges[$key][0] = "\r\n--".BYTESERVING_BOUNDARY."\r\nContent-Type: $mimetype\r\n"; | |
1935 | $ranges[$key][0] .= "Content-Range: bytes {$ranges[$key][1]}-{$ranges[$key][2]}/$filesize\r\n\r\n"; | |
1936 | } | |
1937 | } else { | |
1938 | $ranges = false; | |
1939 | } | |
1940 | if ($ranges) { | |
1941 | if (is_object($file)) { | |
1942 | $handle = $file->get_content_file_handle(); | |
1943 | } else { | |
1944 | $handle = fopen($file, 'rb'); | |
1945 | } | |
1946 | byteserving_send_file($handle, $mimetype, $ranges, $filesize); | |
1947 | } | |
1948 | } | |
1949 | } else { | |
1950 | // Do not byteserve | |
1951 | header('Accept-Ranges: none'); | |
1952 | } | |
1953 | ||
1954 | header('Content-Length: '.$filesize); | |
1955 | ||
1956 | if ($filesize > 10000000) { | |
1957 | // for large files try to flush and close all buffers to conserve memory | |
1958 | while(@ob_get_level()) { | |
1959 | if (!@ob_end_flush()) { | |
1960 | break; | |
1961 | } | |
cbad562e PS |
1962 | } |
1963 | } | |
1964 | ||
d5dd0540 PS |
1965 | // send the whole file content |
1966 | if (is_object($file)) { | |
1967 | $file->readfile(); | |
1968 | } else { | |
137885b7 | 1969 | readfile_allow_large($file, $filesize); |
d5dd0540 PS |
1970 | } |
1971 | } | |
1972 | ||
1973 | /** | |
1974 | * Similar to readfile_accel() but designed for strings. | |
1975 | * @param string $string | |
1976 | * @param string $mimetype | |
1977 | * @param bool $accelerate | |
1978 | * @return void | |
1979 | */ | |
1980 | function readstring_accel($string, $mimetype, $accelerate) { | |
1981 | global $CFG; | |
1982 | ||
1983 | if ($mimetype === 'text/plain') { | |
1984 | // there is no encoding specified in text files, we need something consistent | |
1985 | header('Content-Type: text/plain; charset=utf-8'); | |
1986 | } else { | |
1987 | header('Content-Type: '.$mimetype); | |
1988 | } | |
1989 | header('Last-Modified: '. gmdate('D, d M Y H:i:s', time()) .' GMT'); | |
1990 | header('Accept-Ranges: none'); | |
1991 | ||
1992 | if ($accelerate and !empty($CFG->xsendfile)) { | |
1993 | $fs = get_file_storage(); | |
1994 | if ($fs->xsendfile(sha1($string))) { | |
1995 | return; | |
1996 | } | |
1997 | } | |
cbad562e | 1998 | |
d5dd0540 PS |
1999 | header('Content-Length: '.strlen($string)); |
2000 | echo $string; | |
cbad562e PS |
2001 | } |
2002 | ||
c87c428e | 2003 | /** |
2004 | * Handles the sending of temporary file to user, download is forced. | |
d2b7803e | 2005 | * File is deleted after abort or successful sending, does not return, script terminated |
ba21c9d4 | 2006 | * |
c87c428e | 2007 | * @param string $path path to file, preferably from moodledata/temp/something; or content of file itself |
2008 | * @param string $filename proposed file name when saving file | |
d2b7803e | 2009 | * @param bool $pathisstring If the path is string |
c87c428e | 2010 | */ |
45c0d224 | 2011 | function send_temp_file($path, $filename, $pathisstring=false) { |
c87c428e | 2012 | global $CFG; |
2013 | ||
0c4c3b9a JP |
2014 | // Guess the file's MIME type. |
2015 | $mimetype = get_mimetype_for_sending($filename); | |
d5dd0540 | 2016 | |
c87c428e | 2017 | // close session - not needed anymore |
d79d5ac2 | 2018 | \core\session\manager::write_close(); |
c87c428e | 2019 | |
2020 | if (!$pathisstring) { | |
2021 | if (!file_exists($path)) { | |
dbdc7355 | 2022 | send_header_404(); |
45c0d224 | 2023 | print_error('filenotfound', 'error', $CFG->wwwroot.'/'); |
c87c428e | 2024 | } |
2025 | // executed after normal finish or abort | |
38fc0130 | 2026 | core_shutdown_manager::register_function('send_temp_file_finished', array($path)); |
c87c428e | 2027 | } |
2028 | ||
c87c428e | 2029 | // if user is using IE, urlencode the filename so that multibyte file name will show up correctly on popup |
378b3eac | 2030 | if (core_useragent::is_ie()) { |
c87c428e | 2031 | $filename = urlencode($filename); |
2032 | } | |
2033 | ||
66969d50 | 2034 | header('Content-Disposition: attachment; filename="'.$filename.'"'); |
1e31f118 | 2035 | if (is_https()) { // HTTPS sites - watch out for IE! KB812935 and KB316431. |
c3a3f540 | 2036 | header('Cache-Control: private, max-age=10, no-transform'); |
cbad562e PS |
2037 | header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT'); |
2038 | header('Pragma: '); | |
c87c428e | 2039 | } else { //normal http - prevent caching at all cost |
c3a3f540 | 2040 | header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0, no-transform'); |
cbad562e PS |
2041 | header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT'); |
2042 | header('Pragma: no-cache'); | |
c87c428e | 2043 | } |
c87c428e | 2044 | |
d5dd0540 | 2045 | // send the contents - we can not accelerate this because the file will be deleted asap |
c87c428e | 2046 | if ($pathisstring) { |
d5dd0540 | 2047 | readstring_accel($path, $mimetype, false); |
c87c428e | 2048 | } else { |
d5dd0540 PS |
2049 | readfile_accel($path, $mimetype, false); |
2050 | @unlink($path); | |
c87c428e | 2051 | } |
2052 | ||
2053 | die; //no more chars to output | |
2054 | } | |
2055 | ||
2056 | /** | |
34ff25a6 | 2057 | * Internal callback function used by send_temp_file() |
d2b7803e DC |
2058 | * |
2059 | * @param string $path | |
c87c428e | 2060 | */ |
2061 | function send_temp_file_finished($path) { | |
2062 | if (file_exists($path)) { | |
2063 | @unlink($path); | |
2064 | } | |
2065 | } | |
2066 | ||
76ca1ff1 | 2067 | /** |
2068 | * Handles the sending of file data to the user's browser, including support for | |
2069 | * byteranges etc. | |
ba21c9d4 | 2070 | * |
d2b7803e | 2071 | * @category files |
ba75ad94 | 2072 | * @param string $path Path of file on disk (including real filename), or actual content of file as string |
2073 | * @param string $filename Filename to send | |
0c431257 | 2074 | * @param int $lifetime Number of seconds before the file should expire from caches (null means $CFG->filelifetime) |
ba75ad94 | 2075 | * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only |
2076 | * @param bool $pathisstring If true (default false), $path is the content to send and not the pathname | |
2077 | * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin | |
2078 | * @param string $mimetype Include to specify the MIME type; leave blank to have it guess the type from $filename | |
b379f7d9 | 2079 | * @param bool $dontdie - return control to caller afterwards. this is not recommended and only used for cleanup tasks. |
2080 | * if this is passed as true, ignore_user_abort is called. if you don't want your processing to continue on cancel, | |
29e3d7e2 | 2081 | * you must detect this case when control is returned using connection_aborted. Please not that session is closed |
2082 | * and should not be reopened. | |
d2b7803e | 2083 | * @return null script execution stopped unless $dontdie is true |
b9709b76 | 2084 | */ |
0c431257 | 2085 | function send_file($path, $filename, $lifetime = null , $filter=0, $pathisstring=false, $forcedownload=false, $mimetype='', $dontdie=false) { |
d5dd0540 | 2086 | global $CFG, $COURSE; |
50f95991 | 2087 | |
b379f7d9 | 2088 | if ($dontdie) { |
15325f55 | 2089 | ignore_user_abort(true); |
b379f7d9 | 2090 | } |
2091 | ||
0c431257 PS |
2092 | if ($lifetime === 'default' or is_null($lifetime)) { |
2093 | $lifetime = $CFG->filelifetime; | |
c8a5c6a4 | 2094 | } |
2095 | ||
d79d5ac2 | 2096 | \core\session\manager::write_close(); // Unlock session during file serving. |
172dd12c | 2097 | |
0c4c3b9a JP |
2098 | // Use given MIME type if specified, otherwise guess it. |
2099 | if (!$mimetype || $mimetype === 'document/unknown') { | |
2100 | $mimetype = get_mimetype_for_sending($filename); | |
2101 | } | |
b5bbeaf0 | 2102 | |
4f047de2 | 2103 | // if user is using IE, urlencode the filename so that multibyte file name will show up correctly on popup |
378b3eac | 2104 | if (core_useragent::is_ie()) { |
4638009b | 2105 | $filename = rawurlencode($filename); |
4f047de2 | 2106 | } |
2107 | ||
4c8c65ec | 2108 | if ($forcedownload) { |
cbad562e | 2109 | header('Content-Disposition: attachment; filename="'.$filename.'"'); |
7f68cc65 DM |
2110 | } else if ($mimetype !== 'application/x-shockwave-flash') { |
2111 | // If this is an swf don't pass content-disposition with filename as this makes the flash player treat the file | |
2112 | // as an upload and enforces security that may prevent the file from being loaded. | |
2113 | ||
cbad562e | 2114 | header('Content-Disposition: inline; filename="'.$filename.'"'); |
4c8c65ec | 2115 | } |
2116 | ||
f1e0649c | 2117 | if ($lifetime > 0) { |
bb8ed60a | 2118 | $cacheability = ' public,'; |
29c43343 | 2119 | if (isloggedin() and !isguestuser()) { |
bb8ed60a MS |
2120 | // By default, under the conditions above, this file must be cache-able only by browsers. |
2121 | $cacheability = ' private,'; | |
29c43343 | 2122 | } |
d5dd0540 | 2123 | $nobyteserving = false; |
bb8ed60a | 2124 | header('Cache-Control:'.$cacheability.' max-age='.$lifetime.', no-transform'); |
cbad562e PS |
2125 | header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT'); |
2126 | header('Pragma: '); | |
4c8c65ec | 2127 | |
4c8c65ec | 2128 | } else { // Do not cache files in proxies and browsers |
d5dd0540 | 2129 | $nobyteserving = true; |
1e31f118 | 2130 | if (is_https()) { // HTTPS sites - watch out for IE! KB812935 and KB316431. |
c3a3f540 | 2131 | header('Cache-Control: private, max-age=10, no-transform'); |
cbad562e PS |
2132 | header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT'); |
2133 | header('Pragma: '); | |
85e00626 | 2134 | } else { //normal http - prevent caching at all cost |
c3a3f540 | 2135 | header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0, no-transform'); |
cbad562e PS |
2136 | header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT'); |
2137 | header('Pragma: no-cache'); | |
85e00626 | 2138 | } |
69faecce | 2139 | } |
f1e0649c | 2140 | |
b9709b76 | 2141 | if (empty($filter)) { |
cbad562e | 2142 | // send the contents |
f1e0649c | 2143 | if ($pathisstring) { |
d5dd0540 | 2144 | readstring_accel($path, $mimetype, !$dontdie); |
4c8c65ec | 2145 | } else { |
d5dd0540 | 2146 | readfile_accel($path, $mimetype, !$dontdie); |
f1e0649c | 2147 | } |
cbad562e | 2148 | |
d5dd0540 PS |
2149 | } else { |
2150 | // Try to put the file through filters | |
112aed60 | 2151 | if ($mimetype == 'text/html' || $mimetype == 'application/xhtml+xml') { |
365a5941 | 2152 | $options = new stdClass(); |
f1e0649c | 2153 | $options->noclean = true; |
a17c57b5 | 2154 | $options->nocache = true; // temporary workaround for MDL-5136 |
f1e0649c | 2155 | $text = $pathisstring ? $path : implode('', file($path)); |
76ca1ff1 | 2156 | |
3ace5ee4 | 2157 | $text = file_modify_html_header($text); |
60f9e36e | 2158 | $output = format_text($text, FORMAT_HTML, $options, $COURSE->id); |
f1e0649c | 2159 | |
d5dd0540 | 2160 | readstring_accel($output, $mimetype, false); |
c4cef1fe | 2161 | |
b9709b76 | 2162 | } else if (($mimetype == 'text/plain') and ($filter == 1)) { |
d5dd0540 | 2163 | // only filter text if filter all files is selected |
365a5941 | 2164 | $options = new stdClass(); |
f1e0649c | 2165 | $options->newlines = false; |
2166 | $options->noclean = true; | |
1a4596e4 | 2167 | $text = htmlentities($pathisstring ? $path : implode('', file($path)), ENT_QUOTES, 'UTF-8'); |
60f9e36e | 2168 | $output = '<pre>'. format_text($text, FORMAT_MOODLE, $options, $COURSE->id) .'</pre>'; |
f1e0649c | 2169 | |
d5dd0540 | 2170 | readstring_accel($output, $mimetype, false); |
c4cef1fe | 2171 | |
d5dd0540 | 2172 | } else { |
cbad562e | 2173 | // send the contents |
f1e0649c | 2174 | if ($pathisstring) { |
d5dd0540 PS |
2175 | readstring_accel($path, $mimetype, !$dontdie); |
2176 | } else { | |
2177 | readfile_accel($path, $mimetype, !$dontdie); | |
f1e0649c | 2178 | } |
2179 | } | |
2180 | } | |
b379f7d9 | 2181 | if ($dontdie) { |
2182 | return; | |
2183 | } | |
f1e0649c | 2184 | die; //no more chars to output!!! |
2185 | } | |
2186 | ||
172dd12c | 2187 | /** |
2188 | * Handles the sending of file data to the user's browser, including support for | |
2189 | * byteranges etc. | |
ba21c9d4 | 2190 | * |
796495fe DM |
2191 | * The $options parameter supports the following keys: |
2192 | * (string|null) preview - send the preview of the file (e.g. "thumb" for a thumbnail) | |
2193 | * (string|null) filename - overrides the implicit filename | |
2194 | * (bool) dontdie - return control to caller afterwards. this is not recommended and only used for cleanup tasks. | |
2195 | * if this is passed as true, ignore_user_abort is called. if you don't want your processing to continue on cancel, | |
2196 | * you must detect this case when control is returned using connection_aborted. Please not that session is closed | |
bb8ed60a MS |
2197 | * and should not be reopened |
2198 | * (string|null) cacheability - force the cacheability setting of the HTTP response, "private" or "public", | |
2199 | * when $lifetime is greater than 0. Cacheability defaults to "private" when logged in as other than guest; otherwise, | |
2200 | * defaults to "public". | |
796495fe | 2201 | * |
d2b7803e | 2202 | * @category files |
d2b7803e | 2203 | * @param stored_file $stored_file local file object |
0c431257 | 2204 | * @param int $lifetime Number of seconds before the file should expire from caches (null means $CFG->filelifetime) |
172dd12c | 2205 | * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only |
2206 | * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin | |
796495fe DM |
2207 | * @param array $options additional options affecting the file serving |
2208 | * @return null script execution stopped unless $options['dontdie'] is true | |
172dd12c | 2209 | */ |
0c431257 | 2210 | function send_stored_file($stored_file, $lifetime=null, $filter=0, $forcedownload=false, array $options=array()) { |
d5dd0540 | 2211 | global $CFG, $COURSE; |
172dd12c | 2212 | |
796495fe DM |
2213 | if (empty($options['filename'])) { |
2214 | $filename = null; | |
2215 | } else { | |
2216 | $filename = $options['filename']; | |
2217 | } | |
2218 | ||
2219 | if (empty($options['dontdie'])) { | |
2220 | $dontdie = false; | |
2221 | } else { | |
2222 | $dontdie = true; | |
2223 | } | |
2224 | ||
0c431257 PS |
2225 | if ($lifetime === 'default' or is_null($lifetime)) { |
2226 | $lifetime = $CFG->filelifetime; | |
2227 | } | |
2228 | ||
82c224ee DM |
2229 | if (!empty($options['preview'])) { |
2230 | // replace the file with its preview | |
2231 | $fs = get_file_storage(); | |
77a43933 DM |
2232 | $preview_file = $fs->get_file_preview($stored_file, $options['preview']); |
2233 | if (!$preview_file) { | |
2234 | // unable to create a preview of the file, send its default mime icon instead | |
2235 | if ($options['preview'] === 'tinyicon') { | |
2236 | $size = 24; | |
2237 | } else if ($options['preview'] === 'thumb') { | |
2238 | $size = 90; | |
2239 | } else { | |
2240 | $size = 256; | |
2241 | } | |
2242 | $fileicon = file_file_icon($stored_file, $size); | |
2243 | send_file($CFG->dirroot.'/pix/'.$fileicon.'.png', basename($fileicon).'.png'); | |
82c224ee DM |
2244 | } else { |
2245 | // preview images have fixed cache lifetime and they ignore forced download | |
2246 | // (they are generated by GD and therefore they are considered reasonably safe). | |
77a43933 | 2247 | $stored_file = $preview_file; |
82c224ee DM |
2248 | $lifetime = DAYSECS; |
2249 | $filter = 0; | |
2250 | $forcedownload = false; | |
2251 | } | |
2252 | } | |
2253 | ||
67233725 | 2254 | // handle external resource |
bc6f241c | 2255 | if ($stored_file && $stored_file->is_external_file() && !isset($options['sendcachedexternalfile'])) { |
67233725 DC |
2256 | $stored_file->send_file($lifetime, $filter, $forcedownload, $options); |
2257 | die; | |
2258 | } | |
2259 | ||
2f657978 PS |
2260 | if (!$stored_file or $stored_file->is_directory()) { |
2261 | // nothing to serve | |
2262 | if ($dontdie) { | |
2263 | return; | |
2264 | } | |
2265 | die; | |
2266 | } | |
2267 | ||
b379f7d9 | 2268 | if ($dontdie) { |
15325f55 | 2269 | ignore_user_abort(true); |
b379f7d9 | 2270 | } |
2271 | ||
d79d5ac2 | 2272 | \core\session\manager::write_close(); // Unlock session during file serving. |
172dd12c | 2273 | |
172dd12c | 2274 | $filename = is_null($filename) ? $stored_file->get_filename() : $filename; |
0c4c3b9a JP |
2275 | |
2276 | // Use given MIME type if specified. | |
2277 | $mimetype = $stored_file->get_mimetype(); | |
2278 | ||
2279 | // Otherwise guess it. | |
2280 | if (!$mimetype || $mimetype === 'document/unknown') { | |
2281 | $mimetype = get_mimetype_for_sending($filename); | |
2282 | } | |
b5bbeaf0 | 2283 | |
172dd12c | 2284 | // if user is using IE, urlencode the filename so that multibyte file name will show up correctly on popup |
378b3eac | 2285 | if (core_useragent::is_ie()) { |
172dd12c | 2286 | $filename = rawurlencode($filename); |
2287 | } | |
2288 | ||
2289 | if ($forcedownload) { | |
cbad562e | 2290 | header('Content-Disposition: attachment; filename="'.$filename.'"'); |
9528f643 DM |
2291 | } else if ($mimetype !== 'application/x-shockwave-flash') { |
2292 | // If this is an swf don't pass content-disposition with filename as this makes the flash player treat the file | |
2293 | // as an upload and enforces security that may prevent the file from being loaded. | |
2294 | ||
cbad562e | 2295 | header('Content-Disposition: inline; filename="'.$filename.'"'); |
172dd12c | 2296 | } |
2297 | ||
2298 | if ($lifetime > 0) { | |
bb8ed60a MS |
2299 | $cacheability = ' public,'; |
2300 | if (!empty($options['cacheability']) && ($options['cacheability'] === 'public')) { | |
2301 | // This file must be cache-able by both browsers and proxies. | |
2302 | $cacheability = ' public,'; | |
2303 | } else if (!empty($options['cacheability']) && ($options['cacheability'] === 'private')) { | |
2304 | // This file must be cache-able only by browsers. | |
2305 | $cacheability = ' private,'; | |
2306 | } else if (isloggedin() and !isguestuser()) { | |
2307 | $cacheability = ' private,'; | |
2308 | } | |
2309 | header('Cache-Control:'.$cacheability.' max-age='.$lifetime.', no-transform'); | |
cbad562e PS |
2310 | header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT'); |
2311 | header('Pragma: '); | |
172dd12c | 2312 | |
172dd12c | 2313 | } else { // Do not cache files in proxies and browsers |
1e31f118 | 2314 | if (is_https()) { // HTTPS sites - watch out for IE! KB812935 and KB316431. |
c3a3f540 | 2315 | header('Cache-Control: private, max-age=10, no-transform'); |
cbad562e PS |
2316 | header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT'); |
2317 | header('Pragma: '); | |
172dd12c | 2318 | } else { //normal http - prevent caching at all cost |
c3a3f540 | 2319 | header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0, no-transform'); |
cbad562e PS |
2320 | header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT'); |
2321 | header('Pragma: no-cache'); | |
172dd12c | 2322 | } |
172dd12c | 2323 | } |
2324 | ||
3ca3d259 JL |
2325 | // Allow cross-origin requests only for Web Services. |
2326 | // This allow to receive requests done by Web Workers or webapps in different domains. | |
2327 | if (WS_SERVER) { | |
2328 | header('Access-Control-Allow-Origin: *'); | |
2329 | } | |
2330 | ||
172dd12c | 2331 | if (empty($filter)) { |
cbad562e | 2332 | // send the contents |
d5dd0540 | 2333 | readfile_accel($stored_file, $mimetype, !$dontdie); |
172dd12c | 2334 | |
2335 | } else { // Try to put the file through filters | |
112aed60 | 2336 | if ($mimetype == 'text/html' || $mimetype == 'application/xhtml+xml') { |
365a5941 | 2337 | $options = new stdClass(); |
172dd12c | 2338 | $options->noclean = true; |
2339 | $options->nocache = true; // temporary workaround for MDL-5136 | |
2340 | $text = $stored_file->get_content(); | |
2341 | $text = file_modify_html_header($text); | |
2342 | $output = format_text($text, FORMAT_HTML, $options, $COURSE->id); | |
172dd12c | 2343 | |
d5dd0540 | 2344 | readstring_accel($output, $mimetype, false); |
c4cef1fe | 2345 | |
172dd12c | 2346 | } else if (($mimetype == 'text/plain') and ($filter == 1)) { |
c4cef1fe | 2347 | // only filter text if filter all files is selected |
365a5941 | 2348 | $options = new stdClass(); |
172dd12c | 2349 | $options->newlines = false; |
2350 | $options->noclean = true; | |
2351 | $text = $stored_file->get_content(); | |
2352 | $output = '<pre>'. format_text($text, FORMAT_MOODLE, $options, $COURSE->id) .'</pre>'; | |
172dd12c | 2353 | |
d5dd0540 | 2354 | readstring_accel($output, $mimetype, false); |
c4cef1fe | 2355 | |
172dd12c | 2356 | } else { // Just send it out raw |
d5dd0540 | 2357 | readfile_accel($stored_file, $mimetype, !$dontdie); |
172dd12c | 2358 | } |
2359 | } | |
b379f7d9 | 2360 | if ($dontdie) { |
2361 | return; | |
2362 | } | |
172dd12c | 2363 | die; //no more chars to output!!! |
2364 | } | |
2365 | ||
ba21c9d4 | 2366 | /** |
34ff25a6 | 2367 | * Retrieves an array of records from a CSV file and places |
ba21c9d4 | 2368 | * them into a given table structure |
2369 | * | |
d2b7803e DC |
2370 | * @global stdClass $CFG |
2371 | * @global moodle_database $DB | |
ba21c9d4 | 2372 | * @param string $file The path to a CSV file |
2373 | * @param string $table The table to retrieve columns from | |
2374 | * @return bool|array Returns an array of CSV records or false | |
2375 | */ | |
a43b5308 | 2376 | function get_records_csv($file, $table) { |
f33e1ed4 | 2377 | global $CFG, $DB; |
599f38f9 | 2378 | |
f33e1ed4 | 2379 | if (!$metacolumns = $DB->get_columns($table)) { |
599f38f9 | 2380 | return false; |
2381 | } | |
2382 | ||
a77b98eb | 2383 | if(!($handle = @fopen($file, 'r'))) { |
5a2a5331 | 2384 | print_error('get_records_csv failed to open '.$file); |
599f38f9 | 2385 | } |
2386 | ||
2387 | $fieldnames = fgetcsv($handle, 4096); | |
2388 | if(empty($fieldnames)) { | |
2389 | fclose($handle); | |
2390 | return false; | |
2391 | } | |
2392 | ||
2393 | $columns = array(); | |
2394 | ||
2395 | foreach($metacolumns as $metacolumn) { | |
2396 | $ord = array_search($metacolumn->name, $fieldnames); | |
2397 | if(is_int($ord)) { | |
2398 | $columns[$metacolumn->name] = $ord; | |
2399 | } | |
2400 | } | |
2401 | ||
2402 | $rows = array(); | |
2403 | ||
2404 | while (($data = fgetcsv($handle, 4096)) !== false) { | |
2405 | $item = new stdClass; | |
2406 | foreach($columns as $name => $ord) { | |
2407 | $item->$name = $data[$ord]; | |
2408 | } | |
2409 | $rows[] = $item; | |
2410 | } | |
2411 | ||
2412 | fclose($handle); | |
2413 | return $rows; | |
2414 | } | |
2415 | ||
ba21c9d4 | 2416 | /** |
d2b7803e | 2417 | * Create a file with CSV contents |
117bd748 | 2418 | * |
d2b7803e DC |
2419 | * @global stdClass $CFG |
2420 | * @global moodle_database $DB | |
ba21c9d4 | 2421 | * @param string $file The file to put the CSV content into |
2422 | * @param array $records An array of records to write to a CSV file | |
2423 | * @param string $table The table to get columns from | |
2424 | * @return bool success | |
2425 | */ | |
a77b98eb | 2426 | function put_records_csv($file, $records, $table = NULL) { |
f33e1ed4 | 2427 | global $CFG, $DB; |
a77b98eb | 2428 | |
a1e93da2 | 2429 | if (empty($records)) { |
a77b98eb | 2430 | return true; |
2431 | } | |
2432 | ||
2433 | $metacolumns = NULL; | |
f33e1ed4 | 2434 | if ($table !== NULL && !$metacolumns = $DB->get_columns($table)) { |
a77b98eb | 2435 | return false; |
2436 | } | |
2437 | ||
a1e93da2 | 2438 | echo "x"; |
2439 | ||
7aa06e6d | 2440 | if(!($fp = @fopen($CFG->tempdir.'/'.$file, 'w'))) { |
5a2a5331 | 2441 | print_error('put_records_csv failed to open '.$file); |
a77b98eb | 2442 | } |
2443 | ||
a43b5308 | 2444 | $proto = reset($records); |
2445 | if(is_object($proto)) { | |
2446 | $fields_records = array_keys(get_object_vars($proto)); | |
2447 | } | |
2448 | else if(is_array($proto)) { | |
2449 | $fields_records = array_keys($proto); | |
2450 | } | |
2451 | else { | |
2452 | return false; | |
2453 | } | |
a1e93da2 | 2454 | echo "x"; |
a77b98eb | 2455 | |
2456 | if(!empty($metacolumns)) { | |
2457 | $fields_table = array_map(create_function('$a', 'return $a->name;'), $metacolumns); | |
2458 | $fields = array_intersect($fields_records, $fields_table); | |
2459 | } | |
2460 | else { | |
2461 | $fields = $fields_records; | |
2462 | } | |
2463 | ||
2464 | fwrite($fp, implode(',', $fields)); | |
2465 | fwrite($fp, "\r\n"); | |
2466 | ||
2467 | foreach($records as $record) { | |
a43b5308 | 2468 | $array = (array)$record; |
a77b98eb | 2469 | $values = array(); |
2470 | foreach($fields as $field) { | |
a43b5308 | 2471 | if(strpos($array[$field], ',')) { |
2472 | $values[] = '"'.str_replace('"', '\"', $array[$field]).'"'; | |
a77b98eb | 2473 | } |
2474 | else { | |
a43b5308 | 2475 | $values[] = $array[$field]; |
a77b98eb | 2476 | } |
2477 | } | |
2478 | fwrite($fp, implode(',', $values)."\r\n"); | |
2479 | } | |
2480 | ||
2481 | fclose($fp); | |
eb459f71 | 2482 | @chmod($CFG->tempdir.'/'.$file, $CFG->filepermissions); |
a77b98eb | 2483 | return true; |
2484 | } | |
2485 | ||
f401cc97 | 2486 | |
34763a79 | 2487 | /** |
76ca1ff1 | 2488 | * Recursively delete the file or folder with path $location. That is, |
34763a79 | 2489 | * if it is a file delete it. If it is a folder, delete all its content |
db3a0b34 | 2490 | * then delete it. If $location does not exist to start, that is not |
2491 | * considered an error. | |
76ca1ff1 | 2492 | * |
eab8ed9f | 2493 | * @param string $location the path to remove. |
ba21c9d4 | 2494 | * @return bool |
34763a79 | 2495 | */ |
4c8c65ec | 2496 | function fulldelete($location) { |
1e06adb4 PS |
2497 | if (empty($location)) { |
2498 | // extra safety against wrong param | |
2499 | return false; | |
2500 | } | |
f401cc97 | 2501 | if (is_dir($location)) { |
80c27aab CF |
2502 | if (!$currdir = opendir($location)) { |
2503 | return false; | |
2504 | } | |
f401cc97 | 2505 | while (false !== ($file = readdir($currdir))) { |
2506 | if ($file <> ".." && $file <> ".") { | |
2507 | $fullfile = $location."/".$file; | |
4c8c65ec | 2508 | if (is_dir($fullfile)) { |
f401cc97 | 2509 | if (!fulldelete($fullfile)) { |
2510 | return false; | |
2511 | } | |
2512 | } else { | |
2513 | if (!unlink($fullfile)) { | |
2514 | return false; | |
2515 | } | |
4c8c65ec | 2516 | } |
f401cc97 | 2517 | } |
4c8c65ec | 2518 | } |
f401cc97 | 2519 | closedir($currdir); |
2520 | if (! rmdir($location)) { | |
2521 | return false; | |
2522 | } | |
2523 | ||
34763a79 | 2524 | } else if (file_exists($location)) { |
f401cc97 | 2525 | if (!unlink($location)) { |
2526 | return false; | |
2527 | } | |
2528 | } | |
2529 | return true; | |
2530 | } | |
2531 | ||
4c8c65ec | 2532 | /** |
2533 | * Send requested byterange of file. | |
ba21c9d4 | 2534 | * |
d2b7803e | 2535 | * @param resource $handle A file handle |
ba21c9d4 | 2536 | * @param string $mimetype The mimetype for the output |
2537 | * @param array $ranges An array of ranges to send | |
2538 | * @param string $filesize The size of the content if only one range is used | |
4c8c65ec | 2539 | */ |
172dd12c | 2540 | function byteserving_send_file($handle, $mimetype, $ranges, $filesize) { |
d5dd0540 | 2541 | // better turn off any kind of compression and buffering |
b19d75a2 | 2542 | ini_set('zlib.output_compression', 'Off'); |
d5dd0540 | 2543 | |
4c8c65ec | 2544 | $chunksize = 1*(1024*1024); // 1MB chunks - must be less than 2MB! |
4c8c65ec | 2545 | if ($handle === false) { |
2546 | die; | |
2547 | } | |
2548 | if (count($ranges) == 1) { //only one range requested | |
2549 | $length = $ranges[0][2] - $ranges[0][1] + 1; | |
cbad562e PS |
2550 | header('HTTP/1.1 206 Partial content'); |
2551 | header('Content-Length: '.$length); | |
2552 | header('Content-Range: bytes '.$ranges[0][1].'-'.$ranges[0][2].'/'.$filesize); | |
2553 | header('Content-Type: '.$mimetype); | |
c4cef1fe | 2554 | |
d5dd0540 PS |
2555 | while(@ob_get_level()) { |
2556 | if (!@ob_end_flush()) { | |
2557 | break; | |
2558 | } | |
2559 | } | |
c4cef1fe | 2560 | |
4c8c65ec | 2561 | fseek($handle, $ranges[0][1]); |
2562 | while (!feof($handle) && $length > 0) { | |
3ef7279f | 2563 | core_php_time_limit::raise(60*60); //reset time limit to 60 min - should be enough for 1 MB chunk |
4c8c65ec | 2564 | $buffer = fread($handle, ($chunksize < $length ? $chunksize : $length)); |
2565 | echo $buffer; | |
2566 | flush(); | |
2567 | $length -= strlen($buffer); | |
2568 | } | |
2569 | fclose($handle); | |
2570 | die; | |
2571 | } else { // multiple ranges requested - not tested much | |
2572 | $totallength = 0; | |
2573 | foreach($ranges as $range) { | |
aba588a7 | 2574 | $totallength += strlen($range[0]) + $range[2] - $range[1] + 1; |
4c8c65ec | 2575 | } |
aba588a7 | 2576 | $totallength += strlen("\r\n--".BYTESERVING_BOUNDARY."--\r\n"); |
cbad562e PS |
2577 | header('HTTP/1.1 206 Partial content'); |
2578 | header('Content-Length: '.$totallength); | |
2579 | header('Content-Type: multipart/byteranges; boundary='.BYTESERVING_BOUNDARY); | |
c4cef1fe | 2580 | |
d5dd0540 PS |
2581 | while(@ob_get_level()) { |
2582 | if (!@ob_end_flush()) { | |
2583 | break; | |
2584 | } | |
2585 | } | |
c4cef1fe | 2586 | |
4c8c65ec | 2587 | foreach($ranges as $range) { |
2588 | $length = $range[2] - $range[1] + 1; | |
2589 | echo $range[0]; | |
4c8c65ec | 2590 | fseek($handle, $range[1]); |
2591 | while (!feof($handle) && $length > 0) { | |
3ef7279f | 2592 | core_php_time_limit::raise(60*60); //reset time limit to 60 min - should be enough for 1 MB chunk |
4c8c65ec | 2593 | $buffer = fread($handle, ($chunksize < $length ? $chunksize : $length)); |
2594 | echo $buffer; | |
2595 | flush(); | |
2596 | $length -= strlen($buffer); | |
2597 | } | |
2598 | } | |
2599 | echo "\r\n--".BYTESERVING_BOUNDARY."--\r\n"; | |
2600 | fclose($handle); | |
2601 | die; | |
2602 | } | |
2603 | } | |
f401cc97 | 2604 | |
3ace5ee4 | 2605 | /** |
2606 | * add includes (js and css) into uploaded files | |
2607 | * before returning them, useful for themes and utf.js includes | |
ba21c9d4 | 2608 | * |
d2b7803e | 2609 | * @global stdClass $CFG |
ba21c9d4 | 2610 | * @param string $text text to search and replace |
2611 | * @return string text with added head includes | |
d2b7803e | 2612 | * @todo MDL-21120 |
3ace5ee4 | 2613 | */ |
2614 | function file_modify_html_header($text) { | |
2615 | // first look for <head> tag | |
2616 | global $CFG; | |
76ca1ff1 | 2617 | |
3ace5ee4 | 2618 | $stylesheetshtml = ''; |
8045696b MS |
2619 | /* |
2620 | foreach ($CFG->stylesheets as $stylesheet) { | |
78946b9b | 2621 | //TODO: MDL-21120 |
3ace5ee4 | 2622 | $stylesheetshtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n"; |
3ace5ee4 | 2623 | } |
8045696b MS |
2624 | */ |
2625 | // TODO The code below is actually a waste of CPU. When MDL-29738 will be implemented it should be re-evaluated too. | |
76ca1ff1 | 2626 | |
3ace5ee4 | 2627 | preg_match('/\<head\>|\<HEAD\>/', $text, $matches); |
2628 | if ($matches) { | |
8045696b | 2629 | $replacement = '<head>'.$stylesheetshtml; |
3ace5ee4 | 2630 | $text = preg_replace('/\<head\>|\<HEAD\>/', $replacement, $text, 1); |
76ca1ff1 | 2631 | return $text; |
3ace5ee4 | 2632 | } |
76ca1ff1 | 2633 | |
3ace5ee4 | 2634 | // if not, look for <html> tag, and stick <head> right after |
2635 | preg_match('/\<html\>|\<HTML\>/', $text, $matches); | |
2636 | if ($matches) { | |
2637 | // replace <html> tag with <html><head>includes</head> | |
8045696b | 2638 | $replacement = '<html>'."\n".'<head>'.$stylesheetshtml.'</head>'; |
3ace5ee4 | 2639 | $text = preg_replace('/\<html\>|\<HTML\>/', $replacement, $text, 1); |
76ca1ff1 | 2640 | return $text; |
3ace5ee4 | 2641 | } |
76ca1ff1 | 2642 | |
3ace5ee4 | 2643 | // if not, look for <body> tag, and stick <head> before body |
2644 | preg_match('/\<body\>|\<BODY\>/', $text, $matches); | |
2645 | if ($matches) { | |
8045696b | 2646 | $replacement = '<head>'.$stylesheetshtml.'</head>'."\n".'<body>'; |
3ace5ee4 | 2647 | $text = preg_replace('/\<body\>|\<BODY\>/', $replacement, $text, 1); |
76ca1ff1 | 2648 | return $text; |
2649 | } | |
2650 | ||
3ace5ee4 | 2651 | // if not, just stick a <head> tag at the beginning |
8045696b | 2652 | $text = '<head>'.$stylesheetshtml.'</head>'."\n".$text; |
3ace5ee4 | 2653 | return $text; |
2654 | } | |
2655 | ||
bb2c046d | 2656 | /** |
2657 | * RESTful cURL class | |
2658 | * | |
2659 | * This is a wrapper class for curl, it is quite easy to use: | |
ba21c9d4 | 2660 | * <code> |
bb2c046d | 2661 | * $c = new curl; |
2662 | * // enable cache | |
2663 | * $c = new curl(array('cache'=>true)); | |
2664 | * // enable cookie | |
2665 | * $c = new curl(array('cookie'=>true)); | |
2666 | * // enable proxy | |
2667 | * $c = new curl(array('proxy'=>true)); | |
2668 | * | |
2669 | * // HTTP GET Method | |
2670 | * $html = $c->get('http://example.com'); | |
2671 | * // HTTP POST Method | |
2672 | * $html = $c->post('http://example.com/', array('q'=>'words', 'name'=>'moodle')); | |
2673 | * // HTTP PUT Method | |
2674 | * $html = $c->put('http://example.com/', array('file'=>'/var/www/test.txt'); | |
ba21c9d4 | 2675 | * </code> |
bb2c046d | 2676 | * |
d2b7803e DC |
2677 | * @package core_files |
2678 | * @category files | |
2679 | * @copyright Dongsheng Cai <dongsheng@moodle.com> | |
2680 | * @license http://www.gnu.org/copyleft/gpl.html GNU Public License | |
bb2c046d | 2681 | */ |
bb2c046d | 2682 | class curl { |
d2b7803e | 2683 | /** @var bool Caches http request contents */ |
bb2c046d | 2684 | public $cache = false; |
220eef0e PS |
2685 | /** @var bool Uses proxy, null means automatic based on URL */ |
2686 | public $proxy = null; | |
d2b7803e | 2687 | /** @var string library version */ |
bb2c046d | 2688 | public $version = '0.4 dev'; |
d2b7803e | 2689 | /** @var array http's response */ |
bb2c046d | 2690 | public $response = array(); |
220eef0e PS |
2691 | /** @var array Raw response headers, needed for BC in download_file_content(). */ |
2692 | public $rawresponse = array(); | |
d2b7803e | 2693 | /** @var array http header */ |
bb2c046d | 2694 | public $header = array(); |
d2b7803e | 2695 | /** @var string cURL information */ |
bb2c046d | 2696 | public $info; |
d2b7803e | 2697 | /** @var string error */ |
bb2c046d | 2698 | public $error; |
a3c94686 MG |
2699 | /** @var int error code */ |
2700 | public $errno; | |
220eef0e PS |
2701 | /** @var bool use workaround for open_basedir restrictions, to be changed from unit tests only! */ |
2702 | public $emulateredirects = null; | |
117bd748 | 2703 | |
d2b7803e | 2704 | /** @var array cURL options */ |
9a1adda5 | 2705 | private $options; |
300fcae3 | 2706 | |
d2b7803e | 2707 | /** @var string Proxy host */ |
bb2c046d | 2708 | private $proxy_host = ''; |
d2b7803e | 2709 | /** @var string Proxy auth */ |
bb2c046d | 2710 | private $proxy_auth = ''; |
d2b7803e | 2711 | /** @var string Proxy type */ |
bb2c046d | 2712 | private $proxy_type = ''; |
d2b7803e | 2713 | /** @var bool Debug mode on */ |
bb2c046d | 2714 | private $debug = false; |
d2b7803e | 2715 | /** @var bool|string Path to cookie file */ |
bb2c046d | 2716 | private $cookie = false; |
220eef0e PS |
2717 | /** @var bool tracks multiple headers in response - redirect detection */ |
2718 | private $responsefinished = false; | |
bb2c046d | 2719 | |
ba21c9d4 | 2720 | /** |
220eef0e | 2721 | * Curl constructor. |
d2b7803e | 2722 | * |
220eef0e PS |
2723 | * Allowed settings are: |
2724 | * proxy: (bool) use proxy server, null means autodetect non-local from url | |
2725 | * debug: (bool) use debug output | |
2726 | * cookie: (string) path to cookie file, false if none | |
2727 | * cache: (bool) use cache | |
2728 | * module_cache: (string) type of cache | |
2729 | * | |
2730 | * @param array $settings | |
ba21c9d4 | 2731 | */ |
220eef0e | 2732 | public function __construct($settings = array()) { |
bb2c046d | 2733 | global $CFG; |
2734 | if (!function_exists('curl_init')) { | |
2735 | $this->error = 'cURL module must be enabled!'; | |
2736 | trigger_error($this->error, E_USER_ERROR); | |
2737 | return false; | |
2738 | } | |
220eef0e PS |
2739 | |
2740 | // All settings of this class should be init here. | |
bb2c046d | 2741 | $this->resetopt(); |
220eef0e | 2742 | if (!empty($settings['debug'])) { |
bb2c046d | 2743 | $this->debug = true; |
2744 | } | |
220eef0e PS |
2745 | if (!empty($settings['cookie'])) { |
2746 | if($settings['cookie'] === true) { | |
bb2c046d | 2747 | $this->cookie = $CFG->dataroot.'/curl_cookie.txt'; |
2748 | } else { | |
220eef0e | 2749 | $this->cookie = $settings['cookie']; |
bb2c046d | 2750 | } |
2751 | } | |
220eef0e | 2752 | if (!empty($settings['cache'])) { |
bb2c046d | 2753 | if (class_exists('curl_cache')) { |
220eef0e PS |
2754 | if (!empty($settings['module_cache'])) { |
2755 | $this->cache = new curl_cache($settings['module_cache']); | |
5430f05b | 2756 | } else { |
2757 | $this->cache = new curl_cache('misc'); | |
2758 | } | |
bb2c046d | 2759 | } |
2760 | } | |
d04ce87f | 2761 | if (!empty($CFG->proxyhost)) { |
2762 | if (empty($CFG->proxyport)) { | |
2763 | $this->proxy_host = $CFG->proxyhost; | |
2764 | } else { | |
2765 | $this->proxy_host = $CFG->proxyhost.':'.$CFG->proxyport; | |
2766 | } | |
2767 | if (!empty($CFG->proxyuser) and !empty($CFG->proxypassword)) { | |
2768 | $this->proxy_auth = $CFG->proxyuser.':'.$CFG->proxypassword; | |
2769 | $this->setopt(array( | |
2770 | 'proxyauth'=> CURLAUTH_BASIC | CURLAUTH_NTLM, | |
2771 | 'proxyuserpwd'=>$this->proxy_auth)); | |
2772 | } | |
2773 | if (!empty($CFG->proxytype)) { | |
2774 | if ($CFG->proxytype == 'SOCKS5') { | |
2775 | $this->proxy_type = CURLPROXY_SOCKS5; | |
bb2c046d | 2776 | } else { |
d04ce87f | 2777 | $this->proxy_type = CURLPROXY_HTTP; |
f6d3e2c4 | 2778 | $this->setopt(array('httpproxytunnel'=>false)); |
bb2c046d | 2779 | } |
d04ce87f | 2780 | $this->setopt(array('proxytype'=>$this->proxy_type)); |
bb2c046d | 2781 | } |
220eef0e PS |
2782 | |
2783 | if (isset($settings['proxy'])) { | |
2784 | $this->proxy = $settings['proxy']; | |
2785 | } | |
2786 | } else { | |
2787 | $this->proxy = false; | |
d04ce87f | 2788 | } |
220eef0e PS |
2789 | |
2790 | if (!isset($this->emulateredirects)) { | |
90627d9d | 2791 | $this->emulateredirects = ini_get('open_basedir'); |
bb2c046d | 2792 | } |
2793 | } | |
220eef0e | 2794 | |
ba21c9d4 | 2795 | /** |
2796 | * Resets the CURL options that have already been set | |
2797 | */ | |
9936c2a5 | 2798 | public function resetopt() { |
bb2c046d | 2799 | $this->options = array(); |
2800 | $this->options['CURLOPT_USERAGENT'] = 'MoodleBot/1.0'; | |
2801 | // True to include the header in the output | |
2802 | $this->options['CURLOPT_HEADER'] = 0; | |
2803 | // True to Exclude the body from the output | |
2804 | $this->options['CURLOPT_NOBODY'] = 0; | |
220eef0e PS |
2805 | // Redirect ny default. |
2806 | $this->options['CURLOPT_FOLLOWLOCATION'] = 1; | |
bb2c046d | 2807 | $this->options['CURLOPT_MAXREDIRS'] = 10; |
2808 | $this->options['CURLOPT_ENCODING'] = ''; | |
2809 | // TRUE to return the transfer as a string of the return | |
2810 | // value of curl_exec() instead of outputting it out directly. | |
2811 | $this->options['CURLOPT_RETURNTRANSFER'] = 1; | |
bb2c046d | 2812 | $this->options['CURLOPT_SSL_VERIFYPEER'] = 0; |
2813 | $this->options['CURLOPT_SSL_VERIFYHOST'] = 2; | |
6135bd45 | 2814 | $this->options['CURLOPT_CONNECTTIMEOUT'] = 30; |
c2140b5d PS |
2815 | |
2816 | if ($cacert = self::get_cacert()) { | |
2817 | $this->options['CURLOPT_CAINFO'] = $cacert; | |
2818 | } | |
2819 | } | |
2820 | ||
2821 | /** | |
2822 | * Get the location of ca certificates. | |
2823 | * @return string absolute file path or empty if default used | |
2824 | */ | |
2825 | public static function get_cacert() { | |
2826 | global $CFG; | |
2827 | ||
2828 | // Bundle in dataroot always wins. | |
2829 | if (is_readable("$CFG->dataroot/moodleorgca.crt")) { | |
2830 | return realpath("$CFG->dataroot/moodleorgca.crt"); | |
2831 | } | |
2832 | ||
2833 | // Next comes the default from php.ini | |
2834 | $cacert = ini_get('curl.cainfo'); | |
2835 | if (!empty($cacert) and is_readable($cacert)) { | |
2836 | return realpath($cacert); | |
2837 | } | |
2838 | ||
2839 | // Windows PHP does not have any certs, we need to use something. | |
2840 | if ($CFG->ostype === 'WINDOWS') { | |
2841 | if (is_readable("$CFG->libdir/cacert.pem")) { | |
2842 | return realpath("$CFG->libdir/cacert.pem"); | |
2843 | } | |
2844 | } | |
2845 | ||
2846 | // Use default, this should work fine on all properly configured *nix systems. | |
2847 | return null; | |
bb2c046d | 2848 | } |
2849 | ||
2850 | /** | |
2851 | * Reset Cookie | |
bb2c046d | 2852 | */ |
2853 | public function resetcookie() { | |
2854 | if (!empty($this->cookie)) { | |
2855 | if (is_file($this->cookie)) { | |
2856 | $fp = fopen($this->cookie, 'w'); | |
2857 | if (!empty($fp)) { | |
2858 | fwrite($fp, ''); | |
2859 | fclose($fp); | |
2860 | } | |
2861 | } | |
2862 | } | |
2863 | } | |
2864 | ||
2865 | /** | |
91c8cf99 | 2866 | * Set curl options. |
bb2c046d | 2867 | * |
91c8cf99 F |
2868 | * Do not use the curl constants to define the options, pass a string |
2869 | * corresponding to that constant. Ie. to set CURLOPT_MAXREDIRS, pass | |
2870 | * array('CURLOPT_MAXREDIRS' => 10) or array('maxredirs' => 10) to this method. | |
2871 | * | |
2872 | * @param array $options If array is null, this function will reset the options to default value. | |
2873 | * @return void | |
2874 | * @throws coding_exception If an option uses constant value instead of option name. | |
bb2c046d | 2875 | */ |
2876 | public function setopt($options = array()) { | |
2877 | if (is_array($options)) { | |
9936c2a5 | 2878 | foreach ($options as $name => $val) { |
91c8cf99 F |
2879 | if (!is_string($name)) { |
2880 | throw new coding_exception('Curl options should be defined using strings, not constant values.'); | |
2881 | } | |
bb2c046d | 2882 | if (stripos($name, 'CURLOPT_') === false) { |
2883 | $name = strtoupper('CURLOPT_'.$name); | |
220eef0e PS |
2884 | } else { |
2885 | $name = strtoupper($name); | |
bb2c046d | 2886 | } |
2887 | $this->options[$name] = $val; | |
2888 | } | |
2889 | } | |
2890 | } | |
d2b7803e | 2891 | |
bb2c046d | 2892 | /** |
2893 | * Reset http method | |
bb2c046d | 2894 | */ |
9936c2a5 | 2895 | public function cleanopt() { |
bb2c046d | 2896 | unset($this->options['CURLOPT_HTTPGET']); |
2897 | unset($this->options['CURLOPT_POST']); | |
2898 | unset($this->options['CURLOPT_POSTFIELDS']); | |
2899 | unset($this->options['CURLOPT_PUT']); | |
2900 | unset($this->options['CURLOPT_INFILE']); | |
2901 | unset($this->options['CURLOPT_INFILESIZE']); | |
2902 | unset($this->options['CURLOPT_CUSTOMREQUEST']); | |
a3c94686 | 2903 | unset($this->options['CURLOPT_FILE']); |
bb2c046d | 2904 | } |
2905 | ||
7e1e775f MG |
2906 | /** |
2907 | * Resets the HTTP Request headers (to prepare for the new request) | |
2908 | */ | |
2909 | public function resetHeader() { | |
2910 | $this->header = array(); | |
2911 | } | |
2912 | ||
bb2c046d | 2913 | /** |
2914 | * Set HTTP Request Header | |
2915 | * | |
d2b7803e | 2916 | * @param array $header |
bb2c046d | 2917 | */ |
2918 | public function setHeader($header) { | |
9936c2a5 | 2919 | if (is_array($header)) { |
bb2c046d | 2920 | foreach ($header as $v) { |
2921 | $this->setHeader($v); | |
2922 | } | |
2923 | } else { | |
220eef0e PS |
2924 | // Remove newlines, they are not allowed in headers. |
2925 | $this->header[] = preg_replace('/[\r\n]/', '', $header); | |
bb2c046d | 2926 | } |
2927 | } | |
d2b7803e | 2928 | |
bb2c046d | 2929 | /** |
220eef0e PS |
2930 | * Get HTTP Response Headers |
2931 | * @return array of arrays | |
bb2c046d | 2932 | */ |
9936c2a5 | 2933 | public function getResponse() { |
bb2c046d | 2934 | return $this->response; |
2935 | } | |
d2b7803e | 2936 | |
220eef0e PS |
2937 | /** |
2938 | * Get raw HTTP Response Headers | |
2939 | * @return array of strings | |
2940 | */ | |
2941 | public function get_raw_response() { | |
2942 | return $this->rawresponse; | |
2943 | } | |
2944 | ||
bb2c046d | 2945 | /** |
2946 | * private callback function | |
2947 | * Formatting HTTP Response Header | |
2948 | * | |
102230b2 FM |
2949 | * We only keep the last headers returned. For example during a redirect the |
2950 | * redirect headers will not appear in {@link self::getResponse()}, if you need | |
2951 | * to use those headers, refer to {@link self::get_raw_response()}. | |
2952 | * | |
d2b7803e | 2953 | * @param resource $ch Apparently not used |
ba21c9d4 | 2954 | * @param string $header |
2955 | * @return int The strlen of the header | |
bb2c046d | 2956 | */ |
9936c2a5 | 2957 | private function formatHeader($ch, $header) { |
220eef0e PS |
2958 | $this->rawresponse[] = $header; |
2959 | ||
2960 | if (trim($header, "\r\n") === '') { | |
102230b2 | 2961 | // This must be the last header. |
220eef0e PS |
2962 | $this->responsefinished = true; |
2963 | } | |
2964 | ||
bb2c046d | 2965 | if (strlen($header) > 2) { |
102230b2 FM |
2966 | if ($this->responsefinished) { |
2967 | // We still have headers after the supposedly last header, we must be | |
2968 | // in a redirect so let's empty the response to keep the last headers. | |
2969 | $this->responsefinished = false; | |
2970 | $this->response = array(); | |
2971 | } | |
bb2c046d | 2972 | list($key, $value) = explode(" ", rtrim($header, "\r\n"), 2); |
2973 | $key = rtrim($key, ':'); | |
2974 | if (!empty($this->response[$key])) { | |
9936c2a5 | 2975 | if (is_array($this->response[$key])) { |
bb2c046d | 2976 | $this->response[$key][] = $value; |
2977 | } else { | |
2978 | $tmp = $this->response[$key]; | |
2979 | $this->response[$key] = array(); | |
2980 | $this->response[$key][] = $tmp; | |
2981 | $this->response[$key][] = $value; | |
2982 | ||
2983 | } | |
2984 | } else { | |
2985 | $this->response[$key] = $value; | |
2986 | } | |
2987 | } | |
2988 | return strlen($header); | |
2989 | } | |
2990 | ||
2991 | /** | |
2992 | * Set options for individual curl instance | |
ba21c9d4 | 2993 | * |
d2b7803e | 2994 | * @param resource $curl A curl handle |
ba21c9d4 | 2995 | * @param array $options |
d2b7803e | 2996 | * @return resource The curl handle |
bb2c046d | 2997 | */ |
9a1adda5 | 2998 | private function apply_opt($curl, $options) { |
bb2c046d | 2999 | // Clean up |
3000 | $this->cleanopt(); | |
3001 | // set cookie | |
3002 | if (!empty($this->cookie) || !empty($options['cookie'])) { | |
3003 | $this->setopt(array('cookiejar'=>$this->cookie, | |
3004 | 'cookiefile'=>$this->cookie | |
3005 | )); | |
3006 | } | |
3007 | ||
220eef0e PS |
3008 | // Bypass proxy if required. |
3009 | if ($this->proxy === null) { | |
3010 | if (!empty($this->options['CURLOPT_URL']) and is_proxybypass($this->options['CURLOPT_URL'])) { | |
3011 | $proxy = false; | |
3012 | } else { | |
3013 | $proxy = true; | |
3014 | } | |
3015 | } else { | |
3016 | $proxy = (bool)$this->proxy; | |
3017 | } | |
3018 | ||
3019 | // Set proxy. | |
3020 | if ($proxy) { | |
3021 | $options['CURLOPT_PROXY'] = $this->proxy_host; | |
3022 | } else { | |
3023 | unset($this->options['CURLOPT_PROXY']); | |
bb2c046d | 3024 | } |
220eef0e | 3025 | |
bb2c046d | 3026 | $this->setopt($options); |
300fcae3 DC |
3027 | |
3028 | // Reset before set options. | |
bb2c046d | 3029 | curl_setopt($curl, CURLOPT_HEADERFUNCTION, array(&$this,'formatHeader')); |
300fcae3 DC |
3030 | |
3031 | // Setting the User-Agent based on options provided. | |
3032 | $useragent = ''; | |
3033 | ||
3034 | if (!empty($options['CURLOPT_USERAGENT'])) { | |
3035 | $useragent = $options['CURLOPT_USERAGENT']; | |
3036 | } else if (!empty($this->options['CURLOPT_USERAGENT'])) { | |
3037 | $useragent = $this->options['CURLOPT_USERAGENT']; | |
3038 | } else { | |
3039 | $useragent = 'MoodleBot/1.0'; | |
3040 | } | |
3041 | ||
3042 | // Set headers. | |
9936c2a5 | 3043 | if (empty($this->header)) { |
bb2c046d | 3044 | $this->setHeader(array( |
300fcae3 | 3045 | 'User-Agent: ' . $useragent, |
bb2c046d | 3046 | 'Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7', |
3047 | 'Connection: keep-alive' | |
3048 | )); | |
300fcae3 DC |
3049 | } else if (!in_array('User-Agent: ' . $useragent, $this->header)) { |
3050 | // Remove old User-Agent if one existed. | |
3051 | // We have to partial search since we don't know what the original User-Agent is. | |
3052 | if ($match = preg_grep('/User-Agent.*/', $this->header)) { | |
3053 | $key = array_keys($match)[0]; | |
3054 | unset($this->header[$key]); | |
3055 | } | |
3056 | $this->setHeader(array('User-Agent: ' . $useragent)); | |
bb2c046d | 3057 | } |
3058 | curl_setopt($curl, CURLOPT_HTTPHEADER, $this->header); | |
3059 | ||
9936c2a5 | 3060 | if ($this->debug) { |
bb2c046d | 3061 | echo '<h1>Options</h1>'; |
3062 | var_dump($this->options); | |
3063 | echo '<h1>Header</h1>'; | |
3064 | var_dump($this->header); | |
3065 | } | |
3066 | ||
220eef0e PS |
3067 | // Do not allow infinite redirects. |
3068 | if (!isset($this->options['CURLOPT_MAXREDIRS'])) { | |
3069 | $this->options['CURLOPT_MAXREDIRS'] = 0; | |
3070 | } else if ($this->options['CURLOPT_MAXREDIRS'] > 100) { | |
3071 | $this->options['CURLOPT_MAXREDIRS'] = 100; | |
3072 | } else { | |
3073 | $this->options['CURLOPT_MAXREDIRS'] = (int)$this->options['CURLOPT_MAXREDIRS']; | |
3074 | } | |
3075 | ||
3076 | // Make sure we always know if redirects expected. | |
3077 | if (!isset($this->options['CURLOPT_FOLLOWLOCATION'])) { | |
3078 | $this->options['CURLOPT_FOLLOWLOCATION'] = 0; | |
3079 | } | |
3080 | ||
38a73bf4 FM |
3081 | // Limit the protocols to HTTP and HTTPS. |
3082 | if (defined('CURLOPT_PROTOCOLS')) { | |
3083 | $this->options['CURLOPT_PROTOCOLS'] = (CURLPROTO_HTTP | CURLPROTO_HTTPS); | |
3084 | $this->options['CURLOPT_REDIR_PROTOCOLS'] = (CURLPROTO_HTTP | CURLPROTO_HTTPS); | |
3085 | } | |
3086 | ||
91c8cf99 | 3087 | // Set options. |
bb2c046d | 3088 | foreach($this->options as $name => $val) { |
220eef0e PS |
3089 | if ($name === 'CURLOPT_FOLLOWLOCATION' and $this->emulateredirects) { |
3090 | // The redirects are emulated elsewhere. | |
3091 | curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 0); | |
3092 | continue; | |
3093 | } | |
3094 | $name = constant($name); | |
bb2c046d | 3095 | curl_setopt($curl, $name, $val); |
3096 | } | |
220eef0e | 3097 | |
bb2c046d | 3098 | return $curl; |
3099 | } | |
d2b7803e | 3100 | |
ba21c9d4 | 3101 | /** |
bb2c046d | 3102 | * Download multiple files in parallel |
ba21c9d4 | 3103 | * |
3104 | * Calls {@link multi()} with specific download headers | |
3105 | * | |
3106 | * <code> | |
def4a8f5 JH |
3107 | * $c = new curl(); |
3108 | * $file1 = fopen('a', 'wb'); | |
3109 | * $file2 = fopen('b', 'wb'); | |
bb2c046d | 3110 | * $c->download(array( |
def4a8f5 JH |
3111 | * array('url'=>'http://localhost/', 'file'=>$file1), |
3112 | * array('url'=>'http://localhost/20/', 'file'=>$file2) | |
3113 | * )); | |
3114 | * fclose($file1); | |
3115 | * fclose($file2); | |
3116 | * </code> | |
3117 | * | |
3118 | * or | |
3119 | * | |
3120 | * <code> | |
3121 | * $c = new curl(); | |
3122 | * $c->download(array( | |
3123 | * array('url'=>'http://localhost/', 'filepath'=>'/tmp/file1.tmp'), | |
3124 | * array('url'=>'http://localhost/20/', 'filepath'=>'/tmp/file2.tmp') | |
bb2c046d | 3125 | * )); |
ba21c9d4 | 3126 | * </code> |
3127 | * | |
def4a8f5 JH |
3128 | * @param array $requests An array of files to request { |
3129 | * url => url to download the file [required] | |
3130 | * file => file handler, or | |
3131 | * filepath => file path | |
3132 | * } | |
3133 | * If 'file' and 'filepath' parameters are both specified in one request, the | |
3134 | * open file handle in the 'file' parameter will take precedence and 'filepath' | |
3135 | * will be ignored. | |
3136 | * | |
ba21c9d4 | 3137 | * @param array $options An array of options to set |
3138 | * @return array An array of results | |
bb2c046d | 3139 | */ |
3140 | public function download($requests, $options = array()) { | |
bb2c046d | 3141 | $options['RETURNTRANSFER'] = false; |
3142 | return $this->multi($requests, $options); | |
3143 | } | |
d2b7803e DC |
3144 | |
3145 | /** | |
9936c2a5 | 3146 | * Multi HTTP Requests |
bb2c046d | 3147 | * This function could run multi-requests in parallel. |
ba21c9d4 | 3148 | * |
3149 | * @param array $requests An array of files to request | |
3150 | * @param array $options An array of options to set | |
3151 | * @return array An array of results | |
bb2c046d | 3152 | */ |
3153 | protected function multi($requests, $options = array()) { | |
3154 | $count = count($requests); | |
3155 | $handles = array(); | |
3156 | $results = array(); | |
3157 | $main = curl_multi_init(); | |
3158 | for ($i = 0; $i < $count; $i++) { | |
def4a8f5 JH |
3159 | if (!empty($requests[$i]['filepath']) and empty($requests[$i]['file'])) { |
3160 | // open file | |
3161 | $requests[$i]['file'] = fopen($requests[$i]['filepath'], 'w'); | |
3162 | $requests[$i]['auto-handle'] = true; | |
3163 | } | |
9936c2a5 | 3164 | foreach($requests[$i] as $n=>$v) { |
def4a8f5 | 3165 | $options[$n] = $v; |
bb2c046d | 3166 | } |
def4a8f5 | 3167 | $handles[$i] = curl_init($requests[$i]['url']); |
bb2c046d | 3168 | $this->apply_opt($handles[$i], $options); |
3169 | curl_multi_add_handle($main, $handles[$i]); | |
3170 | } | |
3171 | $running = 0; | |
3172 | do { | |
3173 | curl_multi_exec($main, $running); | |
3174 | } while($running > 0); | |
3175 | for ($i = 0; $i < $count; $i++) { | |
ba4082f5 | 3176 | if (!empty($options['CURLOPT_RETURNTRANSFER'])) { |
bb2c046d | 3177 | $results[] = true; |
3178 | } else { | |
3179 | $results[] = curl_multi_getcontent($handles[$i]); | |
3180 | } | |
3181 | curl_multi_remove_handle($main, $handles[$i]); | |
3182 | } | |
3183 | curl_multi_close($main); | |
def4a8f5 JH |
3184 | |
3185 | for ($i = 0; $i < $count; $i++) { | |
3186 | if (!empty($requests[$i]['filepath']) and !empty($requests[$i]['auto-handle'])) { | |
3187 | // close file handler if file is opened in this function | |
3188 | fclose($requests[$i]['file']); | |
3189 | } | |
3190 | } | |
bb2c046d | 3191 | return $results; |
3192 | } | |
d2b7803e | 3193 | |
bb2c046d | 3194 | /** |
3195 | * Single HTTP Request | |
ba21c9d4 | 3196 | * |
3197 | * @param string $url The URL to request | |
3198 | * @param array $options | |
3199 | * @return bool | |
bb2c046d | 3200 | */ |
9936c2a5 | 3201 | protected function request($url, $options = array()) { |
ba72e295 FM |
3202 | // Set the URL as a curl option. |
3203 | $this->setopt(array('CURLOPT_URL' => $url)); | |
3204 | ||
3205 | // Create curl instance. | |
3206 | $curl = curl_init(); | |
220eef0e PS |
3207 | |
3208 | // Reset here so that the data is valid when result returned from cache. | |
3209 | $this->info = array(); | |
3210 | $this->error = ''; | |
3211 | $this->errno = 0; | |
3212 | $this->response = array(); | |
3213 | $this->rawresponse = array(); | |
3214 | $this->responsefinished = false; | |
3215 | ||
bb2c046d | 3216 | $this->apply_opt($curl, $options); |
3217 | if ($this->cache && $ret = $this->cache->get($this->options)) { | |
3218 | return $ret; | |
bb2c046d | 3219 | } |
3220 | ||
220eef0e | 3221 | $ret = curl_exec($curl); |
bb2c046d | 3222 | $this->info = curl_getinfo($curl); |
3223 | $this->error = curl_error($curl); | |
a3c94686 | 3224 | $this->errno = curl_errno($curl); |
220eef0e PS |
3225 | // Note: $this->response and $this->rawresponse are filled by $hits->formatHeader callback. |
3226 | ||
3227 | if ($this->emulateredirects and $this->options['CURLOPT_FOLLOWLOCATION'] and $this->info['http_code'] != 200) { | |
3228 | $redirects = 0; | |
3229 | ||
3230 | while($redirects <= $this->options['CURLOPT_MAXREDIRS']) { | |
3231 | ||
3232 | if ($this->info['http_code'] == 301) { | |
3233 | // Moved Permanently - repeat the same request on new URL. | |
3234 | ||
3235 | } else if ($this->info['http_code'] == 302) { | |
3236 | // Found - the standard redirect - repeat the same request on new URL. | |
3237 | ||
3238 | } else if ($this->info['http_code'] == 303) { | |
3239 | // 303 See Other - repeat only if GET, do not bother with POSTs. | |
3240 | if (empty($this->options['CURLOPT_HTTPGET'])) { | |
3241 | break; | |
3242 | } | |
3243 | ||
3244 | } else if ($this->info['http_code'] == 307) { | |
3245 | // Temporary Redirect - must repeat using the same request type. | |
3246 | ||
3247 | } else if ($this->info['http_code'] == 308) { | |
3248 | // Permanent Redirect - must repeat using the same request type. | |
3249 | ||
3250 | } else { | |
3251 | // Some other http code means do not retry! | |
3252 | break; | |
3253 | } | |
3254 | ||
3255 | $redirects++; | |
3256 | ||
3257 | $redirecturl = null; | |
3258 | if (isset($this->info['redirect_url'])) { | |
3259 | if (preg_match('|^https?://|i', $this->info['redirect_url'])) { | |
3260 | $redirecturl = $this->info['redirect_url']; | |
3261 | } | |
3262 | } | |
3263 | if (!$redirecturl) { | |
3264 | foreach ($this->response as $k => $v) { | |
3265 | if (strtolower($k) === 'location') { | |
3266 | $redirecturl = $v; | |
3267 | break; | |
3268 | } | |
3269 | } | |
3270 | if (preg_match('|^https?://|i', $redirecturl)) { | |
3271 | // Great, this is the correct location format! | |
3272 | ||
3273 | } else if ($redirecturl) { | |
3274 | $current = curl_getinfo($curl, CURLINFO_EFFECTIVE_URL); | |
3275 | if (strpos($redirecturl, '/') === 0) { | |
3276 | // Relative to server root - just guess. | |
3277 | $pos = strpos('/', $current, 8); | |
3278 | if ($pos === false) { | |
3279 | $redirecturl = $current.$redirecturl; | |
3280 | } else { | |
3281 | $redirecturl = substr($current, 0, $pos).$redirecturl; | |
3282 | } | |
3283 | } else { | |
3284 | // Relative to current script. | |
3285 | $redirecturl = dirname($current).'/'.$redirecturl; | |
3286 | } | |
3287 | } | |
3288 | } | |
3289 | ||
220eef0e PS |
3290 | curl_setopt($curl, CURLOPT_URL, $redirecturl); |
3291 | $ret = curl_exec($curl); | |
3292 | ||
3293 | $this->info = curl_getinfo($curl); | |