PageRenderTime 97ms CodeModel.GetById 21ms app.highlight 59ms RepoModel.GetById 1ms app.codeStats 1ms

/lib/filelib.php

https://bitbucket.org/ngmares/moodle
PHP | 4318 lines | 2788 code | 456 blank | 1074 comment | 770 complexity | e7d049b0d138a717c9b285bec7bdf502 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1<?php
   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/>.
  16
  17/**
  18 * Functions for file handling.
  19 *
  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
  23 */
  24
  25defined('MOODLE_INTERNAL') || die();
  26
  27/**
  28 * BYTESERVING_BOUNDARY - string unique string constant.
  29 */
  30define('BYTESERVING_BOUNDARY', 's1k2o3d4a5k6s7');
  31
  32require_once("$CFG->libdir/filestorage/file_exceptions.php");
  33require_once("$CFG->libdir/filestorage/file_storage.php");
  34require_once("$CFG->libdir/filestorage/zip_packer.php");
  35require_once("$CFG->libdir/filebrowser/file_browser.php");
  36
  37/**
  38 * Encodes file serving url
  39 *
  40 * @deprecated use moodle_url factory methods instead
  41 *
  42 * @todo MDL-31071 deprecate this function
  43 * @global stdClass $CFG
  44 * @param string $urlbase
  45 * @param string $path /filearea/itemid/dir/dir/file.exe
  46 * @param bool $forcedownload
  47 * @param bool $https https url required
  48 * @return string encoded file url
  49 */
  50function file_encode_url($urlbase, $path, $forcedownload=false, $https=false) {
  51    global $CFG;
  52
  53//TODO: deprecate this
  54
  55    if ($CFG->slasharguments) {
  56        $parts = explode('/', $path);
  57        $parts = array_map('rawurlencode', $parts);
  58        $path  = implode('/', $parts);
  59        $return = $urlbase.$path;
  60        if ($forcedownload) {
  61            $return .= '?forcedownload=1';
  62        }
  63    } else {
  64        $path = rawurlencode($path);
  65        $return = $urlbase.'?file='.$path;
  66        if ($forcedownload) {
  67            $return .= '&amp;forcedownload=1';
  68        }
  69    }
  70
  71    if ($https) {
  72        $return = str_replace('http://', 'https://', $return);
  73    }
  74
  75    return $return;
  76}
  77
  78/**
  79 * Prepares 'editor' formslib element from data in database
  80 *
  81 * The passed $data record must contain field foobar, foobarformat and optionally foobartrust. This
  82 * function then copies the embedded files into draft area (assigning itemids automatically),
  83 * creates the form element foobar_editor and rewrites the URLs so the embedded images can be
  84 * displayed.
  85 * In your mform definition, you must have an 'editor' element called foobar_editor. Then you call
  86 * your mform's set_data() supplying the object returned by this function.
  87 *
  88 * @category files
  89 * @param stdClass $data database field that holds the html text with embedded media
  90 * @param string $field the name of the database field that holds the html text with embedded media
  91 * @param array $options editor options (like maxifiles, maxbytes etc.)
  92 * @param stdClass $context context of the editor
  93 * @param string $component
  94 * @param string $filearea file area name
  95 * @param int $itemid item id, required if item exists
  96 * @return stdClass modified data object
  97 */
  98function file_prepare_standard_editor($data, $field, array $options, $context=null, $component=null, $filearea=null, $itemid=null) {
  99    $options = (array)$options;
 100    if (!isset($options['trusttext'])) {
 101        $options['trusttext'] = false;
 102    }
 103    if (!isset($options['forcehttps'])) {
 104        $options['forcehttps'] = false;
 105    }
 106    if (!isset($options['subdirs'])) {
 107        $options['subdirs'] = false;
 108    }
 109    if (!isset($options['maxfiles'])) {
 110        $options['maxfiles'] = 0; // no files by default
 111    }
 112    if (!isset($options['noclean'])) {
 113        $options['noclean'] = false;
 114    }
 115
 116    //sanity check for passed context. This function doesn't expect $option['context'] to be set
 117    //But this function is called before creating editor hence, this is one of the best places to check
 118    //if context is used properly. This check notify developer that they missed passing context to editor.
 119    if (isset($context) && !isset($options['context'])) {
 120        //if $context is not null then make sure $option['context'] is also set.
 121        debugging('Context for editor is not set in editoroptions. Hence editor will not respect editor filters', DEBUG_DEVELOPER);
 122    } else if (isset($options['context']) && isset($context)) {
 123        //If both are passed then they should be equal.
 124        if ($options['context']->id != $context->id) {
 125            $exceptionmsg = 'Editor context ['.$options['context']->id.'] is not equal to passed context ['.$context->id.']';
 126            throw new coding_exception($exceptionmsg);
 127        }
 128    }
 129
 130    if (is_null($itemid) or is_null($context)) {
 131        $contextid = null;
 132        $itemid = null;
 133        if (!isset($data)) {
 134            $data = new stdClass();
 135        }
 136        if (!isset($data->{$field})) {
 137            $data->{$field} = '';
 138        }
 139        if (!isset($data->{$field.'format'})) {
 140            $data->{$field.'format'} = editors_get_preferred_format();
 141        }
 142        if (!$options['noclean']) {
 143            $data->{$field} = clean_text($data->{$field}, $data->{$field.'format'});
 144        }
 145
 146    } else {
 147        if ($options['trusttext']) {
 148            // noclean ignored if trusttext enabled
 149            if (!isset($data->{$field.'trust'})) {
 150                $data->{$field.'trust'} = 0;
 151            }
 152            $data = trusttext_pre_edit($data, $field, $context);
 153        } else {
 154            if (!$options['noclean']) {
 155                $data->{$field} = clean_text($data->{$field}, $data->{$field.'format'});
 156            }
 157        }
 158        $contextid = $context->id;
 159    }
 160
 161    if ($options['maxfiles'] != 0) {
 162        $draftid_editor = file_get_submitted_draft_itemid($field);
 163        $currenttext = file_prepare_draft_area($draftid_editor, $contextid, $component, $filearea, $itemid, $options, $data->{$field});
 164        $data->{$field.'_editor'} = array('text'=>$currenttext, 'format'=>$data->{$field.'format'}, 'itemid'=>$draftid_editor);
 165    } else {
 166        $data->{$field.'_editor'} = array('text'=>$data->{$field}, 'format'=>$data->{$field.'format'}, 'itemid'=>0);
 167    }
 168
 169    return $data;
 170}
 171
 172/**
 173 * Prepares the content of the 'editor' form element with embedded media files to be saved in database
 174 *
 175 * This function moves files from draft area to the destination area and
 176 * encodes URLs to the draft files so they can be safely saved into DB. The
 177 * form has to contain the 'editor' element named foobar_editor, where 'foobar'
 178 * is the name of the database field to hold the wysiwyg editor content. The
 179 * editor data comes as an array with text, format and itemid properties. This
 180 * function automatically adds $data properties foobar, foobarformat and
 181 * foobartrust, where foobar has URL to embedded files encoded.
 182 *
 183 * @category files
 184 * @param stdClass $data raw data submitted by the form
 185 * @param string $field name of the database field containing the html with embedded media files
 186 * @param array $options editor options (trusttext, subdirs, maxfiles, maxbytes etc.)
 187 * @param stdClass $context context, required for existing data
 188 * @param string $component file component
 189 * @param string $filearea file area name
 190 * @param int $itemid item id, required if item exists
 191 * @return stdClass modified data object
 192 */
 193function file_postupdate_standard_editor($data, $field, array $options, $context, $component=null, $filearea=null, $itemid=null) {
 194    $options = (array)$options;
 195    if (!isset($options['trusttext'])) {
 196        $options['trusttext'] = false;
 197    }
 198    if (!isset($options['forcehttps'])) {
 199        $options['forcehttps'] = false;
 200    }
 201    if (!isset($options['subdirs'])) {
 202        $options['subdirs'] = false;
 203    }
 204    if (!isset($options['maxfiles'])) {
 205        $options['maxfiles'] = 0; // no files by default
 206    }
 207    if (!isset($options['maxbytes'])) {
 208        $options['maxbytes'] = 0; // unlimited
 209    }
 210
 211    if ($options['trusttext']) {
 212        $data->{$field.'trust'} = trusttext_trusted($context);
 213    } else {
 214        $data->{$field.'trust'} = 0;
 215    }
 216
 217    $editor = $data->{$field.'_editor'};
 218
 219    if ($options['maxfiles'] == 0 or is_null($filearea) or is_null($itemid) or empty($editor['itemid'])) {
 220        $data->{$field} = $editor['text'];
 221    } else {
 222        $data->{$field} = file_save_draft_area_files($editor['itemid'], $context->id, $component, $filearea, $itemid, $options, $editor['text'], $options['forcehttps']);
 223    }
 224    $data->{$field.'format'} = $editor['format'];
 225
 226    return $data;
 227}
 228
 229/**
 230 * Saves text and files modified by Editor formslib element
 231 *
 232 * @category files
 233 * @param stdClass $data $database entry field
 234 * @param string $field name of data field
 235 * @param array $options various options
 236 * @param stdClass $context context - must already exist
 237 * @param string $component
 238 * @param string $filearea file area name
 239 * @param int $itemid must already exist, usually means data is in db
 240 * @return stdClass modified data obejct
 241 */
 242function file_prepare_standard_filemanager($data, $field, array $options, $context=null, $component=null, $filearea=null, $itemid=null) {
 243    $options = (array)$options;
 244    if (!isset($options['subdirs'])) {
 245        $options['subdirs'] = false;
 246    }
 247    if (is_null($itemid) or is_null($context)) {
 248        $itemid = null;
 249        $contextid = null;
 250    } else {
 251        $contextid = $context->id;
 252    }
 253
 254    $draftid_editor = file_get_submitted_draft_itemid($field.'_filemanager');
 255    file_prepare_draft_area($draftid_editor, $contextid, $component, $filearea, $itemid, $options);
 256    $data->{$field.'_filemanager'} = $draftid_editor;
 257
 258    return $data;
 259}
 260
 261/**
 262 * Saves files modified by File manager formslib element
 263 *
 264 * @todo MDL-31073 review this function
 265 * @category files
 266 * @param stdClass $data $database entry field
 267 * @param string $field name of data field
 268 * @param array $options various options
 269 * @param stdClass $context context - must already exist
 270 * @param string $component
 271 * @param string $filearea file area name
 272 * @param int $itemid must already exist, usually means data is in db
 273 * @return stdClass modified data obejct
 274 */
 275function file_postupdate_standard_filemanager($data, $field, array $options, $context, $component, $filearea, $itemid) {
 276    $options = (array)$options;
 277    if (!isset($options['subdirs'])) {
 278        $options['subdirs'] = false;
 279    }
 280    if (!isset($options['maxfiles'])) {
 281        $options['maxfiles'] = -1; // unlimited
 282    }
 283    if (!isset($options['maxbytes'])) {
 284        $options['maxbytes'] = 0; // unlimited
 285    }
 286
 287    if (empty($data->{$field.'_filemanager'})) {
 288        $data->$field = '';
 289
 290    } else {
 291        file_save_draft_area_files($data->{$field.'_filemanager'}, $context->id, $component, $filearea, $itemid, $options);
 292        $fs = get_file_storage();
 293
 294        if ($fs->get_area_files($context->id, $component, $filearea, $itemid)) {
 295            $data->$field = '1'; // TODO: this is an ugly hack (skodak)
 296        } else {
 297            $data->$field = '';
 298        }
 299    }
 300
 301    return $data;
 302}
 303
 304/**
 305 * Generate a draft itemid
 306 *
 307 * @category files
 308 * @global moodle_database $DB
 309 * @global stdClass $USER
 310 * @return int a random but available draft itemid that can be used to create a new draft
 311 * file area.
 312 */
 313function file_get_unused_draft_itemid() {
 314    global $DB, $USER;
 315
 316    if (isguestuser() or !isloggedin()) {
 317        // guests and not-logged-in users can not be allowed to upload anything!!!!!!
 318        print_error('noguest');
 319    }
 320
 321    $contextid = get_context_instance(CONTEXT_USER, $USER->id)->id;
 322
 323    $fs = get_file_storage();
 324    $draftitemid = rand(1, 999999999);
 325    while ($files = $fs->get_area_files($contextid, 'user', 'draft', $draftitemid)) {
 326        $draftitemid = rand(1, 999999999);
 327    }
 328
 329    return $draftitemid;
 330}
 331
 332/**
 333 * Initialise a draft file area from a real one by copying the files. A draft
 334 * area will be created if one does not already exist. Normally you should
 335 * get $draftitemid by calling file_get_submitted_draft_itemid('elementname');
 336 *
 337 * @category files
 338 * @global stdClass $CFG
 339 * @global stdClass $USER
 340 * @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.
 341 * @param int $contextid This parameter and the next two identify the file area to copy files from.
 342 * @param string $component
 343 * @param string $filearea helps indentify the file area.
 344 * @param int $itemid helps identify the file area. Can be null if there are no files yet.
 345 * @param array $options text and file options ('subdirs'=>false, 'forcehttps'=>false)
 346 * @param string $text some html content that needs to have embedded links rewritten to point to the draft area.
 347 * @return string|null returns string if $text was passed in, the rewritten $text is returned. Otherwise NULL.
 348 */
 349function file_prepare_draft_area(&$draftitemid, $contextid, $component, $filearea, $itemid, array $options=null, $text=null) {
 350    global $CFG, $USER, $CFG;
 351
 352    $options = (array)$options;
 353    if (!isset($options['subdirs'])) {
 354        $options['subdirs'] = false;
 355    }
 356    if (!isset($options['forcehttps'])) {
 357        $options['forcehttps'] = false;
 358    }
 359
 360    $usercontext = get_context_instance(CONTEXT_USER, $USER->id);
 361    $fs = get_file_storage();
 362
 363    if (empty($draftitemid)) {
 364        // create a new area and copy existing files into
 365        $draftitemid = file_get_unused_draft_itemid();
 366        $file_record = array('contextid'=>$usercontext->id, 'component'=>'user', 'filearea'=>'draft', 'itemid'=>$draftitemid);
 367        if (!is_null($itemid) and $files = $fs->get_area_files($contextid, $component, $filearea, $itemid)) {
 368            foreach ($files as $file) {
 369                if ($file->is_directory() and $file->get_filepath() === '/') {
 370                    // we need a way to mark the age of each draft area,
 371                    // by not copying the root dir we force it to be created automatically with current timestamp
 372                    continue;
 373                }
 374                if (!$options['subdirs'] and ($file->is_directory() or $file->get_filepath() !== '/')) {
 375                    continue;
 376                }
 377                $draftfile = $fs->create_file_from_storedfile($file_record, $file);
 378                // XXX: This is a hack for file manager (MDL-28666)
 379                // File manager needs to know the original file information before copying
 380                // to draft area, so we append these information in mdl_files.source field
 381                // {@link file_storage::search_references()}
 382                // {@link file_storage::search_references_count()}
 383                $sourcefield = $file->get_source();
 384                $newsourcefield = new stdClass;
 385                $newsourcefield->source = $sourcefield;
 386                $original = new stdClass;
 387                $original->contextid = $contextid;
 388                $original->component = $component;
 389                $original->filearea  = $filearea;
 390                $original->itemid    = $itemid;
 391                $original->filename  = $file->get_filename();
 392                $original->filepath  = $file->get_filepath();
 393                $newsourcefield->original = file_storage::pack_reference($original);
 394                $draftfile->set_source(serialize($newsourcefield));
 395                // End of file manager hack
 396            }
 397        }
 398        if (!is_null($text)) {
 399            // at this point there should not be any draftfile links yet,
 400            // because this is a new text from database that should still contain the @@pluginfile@@ links
 401            // this happens when developers forget to post process the text
 402            $text = str_replace("\"$CFG->httpswwwroot/draftfile.php", "\"$CFG->httpswwwroot/brokenfile.php#", $text);
 403        }
 404    } else {
 405        // nothing to do
 406    }
 407
 408    if (is_null($text)) {
 409        return null;
 410    }
 411
 412    // relink embedded files - editor can not handle @@PLUGINFILE@@ !
 413    return file_rewrite_pluginfile_urls($text, 'draftfile.php', $usercontext->id, 'user', 'draft', $draftitemid, $options);
 414}
 415
 416/**
 417 * Convert encoded URLs in $text from the @@PLUGINFILE@@/... form to an actual URL.
 418 *
 419 * @category files
 420 * @global stdClass $CFG
 421 * @param string $text The content that may contain ULRs in need of rewriting.
 422 * @param string $file The script that should be used to serve these files. pluginfile.php, draftfile.php, etc.
 423 * @param int $contextid This parameter and the next two identify the file area to use.
 424 * @param string $component
 425 * @param string $filearea helps identify the file area.
 426 * @param int $itemid helps identify the file area.
 427 * @param array $options text and file options ('forcehttps'=>false)
 428 * @return string the processed text.
 429 */
 430function file_rewrite_pluginfile_urls($text, $file, $contextid, $component, $filearea, $itemid, array $options=null) {
 431    global $CFG;
 432
 433    $options = (array)$options;
 434    if (!isset($options['forcehttps'])) {
 435        $options['forcehttps'] = false;
 436    }
 437
 438    if (!$CFG->slasharguments) {
 439        $file = $file . '?file=';
 440    }
 441
 442    $baseurl = "$CFG->wwwroot/$file/$contextid/$component/$filearea/";
 443
 444    if ($itemid !== null) {
 445        $baseurl .= "$itemid/";
 446    }
 447
 448    if ($options['forcehttps']) {
 449        $baseurl = str_replace('http://', 'https://', $baseurl);
 450    }
 451
 452    return str_replace('@@PLUGINFILE@@/', $baseurl, $text);
 453}
 454
 455/**
 456 * Returns information about files in a draft area.
 457 *
 458 * @global stdClass $CFG
 459 * @global stdClass $USER
 460 * @param int $draftitemid the draft area item id.
 461 * @return array with the following entries:
 462 *      'filecount' => number of files in the draft area.
 463 * (more information will be added as needed).
 464 */
 465function file_get_draft_area_info($draftitemid) {
 466    global $CFG, $USER;
 467
 468    $usercontext = get_context_instance(CONTEXT_USER, $USER->id);
 469    $fs = get_file_storage();
 470
 471    $results = array();
 472
 473    // The number of files
 474    $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid, 'id', false);
 475    $results['filecount'] = count($draftfiles);
 476    $results['filesize'] = 0;
 477    foreach ($draftfiles as $file) {
 478        $results['filesize'] += $file->get_filesize();
 479    }
 480
 481    return $results;
 482}
 483
 484/**
 485 * Get used space of files
 486 * @global moodle_database $DB
 487 * @global stdClass $USER
 488 * @return int total bytes
 489 */
 490function file_get_user_used_space() {
 491    global $DB, $USER;
 492
 493    $usercontext = get_context_instance(CONTEXT_USER, $USER->id);
 494    $sql = "SELECT SUM(files1.filesize) AS totalbytes FROM {files} files1
 495            JOIN (SELECT contenthash, filename, MAX(id) AS id
 496            FROM {files}
 497            WHERE contextid = ? AND component = ? AND filearea != ?
 498            GROUP BY contenthash, filename) files2 ON files1.id = files2.id";
 499    $params = array('contextid'=>$usercontext->id, 'component'=>'user', 'filearea'=>'draft');
 500    $record = $DB->get_record_sql($sql, $params);
 501    return (int)$record->totalbytes;
 502}
 503
 504/**
 505 * Convert any string to a valid filepath
 506 * @todo review this function
 507 * @param string $str
 508 * @return string path
 509 */
 510function file_correct_filepath($str) { //TODO: what is this? (skodak)
 511    if ($str == '/' or empty($str)) {
 512        return '/';
 513    } else {
 514        return '/'.trim($str, './@#$ ').'/';
 515    }
 516}
 517
 518/**
 519 * Generate a folder tree of draft area of current USER recursively
 520 *
 521 * @todo MDL-31073 use normal return value instead, this does not fit the rest of api here (skodak)
 522 * @param int $draftitemid
 523 * @param string $filepath
 524 * @param mixed $data
 525 */
 526function file_get_drafarea_folders($draftitemid, $filepath, &$data) {
 527    global $USER, $OUTPUT, $CFG;
 528    $data->children = array();
 529    $context = get_context_instance(CONTEXT_USER, $USER->id);
 530    $fs = get_file_storage();
 531    if ($files = $fs->get_directory_files($context->id, 'user', 'draft', $draftitemid, $filepath, false)) {
 532        foreach ($files as $file) {
 533            if ($file->is_directory()) {
 534                $item = new stdClass();
 535                $item->sortorder = $file->get_sortorder();
 536                $item->filepath = $file->get_filepath();
 537
 538                $foldername = explode('/', trim($item->filepath, '/'));
 539                $item->fullname = trim(array_pop($foldername), '/');
 540
 541                $item->id = uniqid();
 542                file_get_drafarea_folders($draftitemid, $item->filepath, $item);
 543                $data->children[] = $item;
 544            } else {
 545                continue;
 546            }
 547        }
 548    }
 549}
 550
 551/**
 552 * Listing all files (including folders) in current path (draft area)
 553 * used by file manager
 554 * @param int $draftitemid
 555 * @param string $filepath
 556 * @return stdClass
 557 */
 558function file_get_drafarea_files($draftitemid, $filepath = '/') {
 559    global $USER, $OUTPUT, $CFG;
 560
 561    $context = get_context_instance(CONTEXT_USER, $USER->id);
 562    $fs = get_file_storage();
 563
 564    $data = new stdClass();
 565    $data->path = array();
 566    $data->path[] = array('name'=>get_string('files'), 'path'=>'/');
 567
 568    // will be used to build breadcrumb
 569    $trail = '/';
 570    if ($filepath !== '/') {
 571        $filepath = file_correct_filepath($filepath);
 572        $parts = explode('/', $filepath);
 573        foreach ($parts as $part) {
 574            if ($part != '' && $part != null) {
 575                $trail .= ($part.'/');
 576                $data->path[] = array('name'=>$part, 'path'=>$trail);
 577            }
 578        }
 579    }
 580
 581    $list = array();
 582    $maxlength = 12;
 583    if ($files = $fs->get_directory_files($context->id, 'user', 'draft', $draftitemid, $filepath, false)) {
 584        foreach ($files as $file) {
 585            $item = new stdClass();
 586            $item->filename = $file->get_filename();
 587            $item->filepath = $file->get_filepath();
 588            $item->fullname = trim($item->filename, '/');
 589            $filesize = $file->get_filesize();
 590            $item->size = $filesize ? $filesize : null;
 591            $item->filesize = $filesize ? display_size($filesize) : '';
 592
 593            $item->sortorder = $file->get_sortorder();
 594            $item->author = $file->get_author();
 595            $item->license = $file->get_license();
 596            $item->datemodified = $file->get_timemodified();
 597            $item->datecreated = $file->get_timecreated();
 598            $item->isref = $file->is_external_file();
 599            if ($item->isref && $file->get_status() == 666) {
 600                $item->originalmissing = true;
 601            }
 602            // find the file this draft file was created from and count all references in local
 603            // system pointing to that file
 604            $source = @unserialize($file->get_source());
 605            if (isset($source->original)) {
 606                $item->refcount = $fs->search_references_count($source->original);
 607            }
 608
 609            if ($file->is_directory()) {
 610                $item->filesize = 0;
 611                $item->icon = $OUTPUT->pix_url(file_folder_icon(24))->out(false);
 612                $item->type = 'folder';
 613                $foldername = explode('/', trim($item->filepath, '/'));
 614                $item->fullname = trim(array_pop($foldername), '/');
 615                $item->thumbnail = $OUTPUT->pix_url(file_folder_icon(90))->out(false);
 616            } else {
 617                // do NOT use file browser here!
 618                $item->mimetype = get_mimetype_description($file);
 619                if (file_extension_in_typegroup($file->get_filename(), 'archive')) {
 620                    $item->type = 'zip';
 621                } else {
 622                    $item->type = 'file';
 623                }
 624                $itemurl = moodle_url::make_draftfile_url($draftitemid, $item->filepath, $item->filename);
 625                $item->url = $itemurl->out();
 626                $item->icon = $OUTPUT->pix_url(file_file_icon($file, 24))->out(false);
 627                $item->thumbnail = $OUTPUT->pix_url(file_file_icon($file, 90))->out(false);
 628                if ($imageinfo = $file->get_imageinfo()) {
 629                    $item->realthumbnail = $itemurl->out(false, array('preview' => 'thumb', 'oid' => $file->get_timemodified()));
 630                    $item->realicon = $itemurl->out(false, array('preview' => 'tinyicon', 'oid' => $file->get_timemodified()));
 631                    $item->image_width = $imageinfo['width'];
 632                    $item->image_height = $imageinfo['height'];
 633                }
 634            }
 635            $list[] = $item;
 636        }
 637    }
 638    $data->itemid = $draftitemid;
 639    $data->list = $list;
 640    return $data;
 641}
 642
 643/**
 644 * Returns draft area itemid for a given element.
 645 *
 646 * @category files
 647 * @param string $elname name of formlib editor element, or a hidden form field that stores the draft area item id, etc.
 648 * @return int the itemid, or 0 if there is not one yet.
 649 */
 650function file_get_submitted_draft_itemid($elname) {
 651    // this is a nasty hack, ideally all new elements should use arrays here or there should be a new parameter
 652    if (!isset($_REQUEST[$elname])) {
 653        return 0;
 654    }
 655    if (is_array($_REQUEST[$elname])) {
 656        $param = optional_param_array($elname, 0, PARAM_INT);
 657        if (!empty($param['itemid'])) {
 658            $param = $param['itemid'];
 659        } else {
 660            debugging('Missing itemid, maybe caused by unset maxfiles option', DEBUG_DEVELOPER);
 661            return false;
 662        }
 663
 664    } else {
 665        $param = optional_param($elname, 0, PARAM_INT);
 666    }
 667
 668    if ($param) {
 669        require_sesskey();
 670    }
 671
 672    return $param;
 673}
 674
 675/**
 676 * Restore the original source field from draft files
 677 *
 678 * @param stored_file $storedfile This only works with draft files
 679 * @return stored_file
 680 */
 681function file_restore_source_field_from_draft_file($storedfile) {
 682    $source = @unserialize($storedfile->get_source());
 683    if (!empty($source)) {
 684        if (is_object($source)) {
 685            $restoredsource = $source->source;
 686            $storedfile->set_source($restoredsource);
 687        } else {
 688            throw new moodle_exception('invalidsourcefield', 'error');
 689        }
 690    }
 691    return $storedfile;
 692}
 693/**
 694 * Saves files from a draft file area to a real one (merging the list of files).
 695 * Can rewrite URLs in some content at the same time if desired.
 696 *
 697 * @category files
 698 * @global stdClass $USER
 699 * @param int $draftitemid the id of the draft area to use. Normally obtained
 700 *      from file_get_submitted_draft_itemid('elementname') or similar.
 701 * @param int $contextid This parameter and the next two identify the file area to save to.
 702 * @param string $component
 703 * @param string $filearea indentifies the file area.
 704 * @param int $itemid helps identifies the file area.
 705 * @param array $options area options (subdirs=>false, maxfiles=-1, maxbytes=0)
 706 * @param string $text some html content that needs to have embedded links rewritten
 707 *      to the @@PLUGINFILE@@ form for saving in the database.
 708 * @param bool $forcehttps force https urls.
 709 * @return string|null if $text was passed in, the rewritten $text is returned. Otherwise NULL.
 710 */
 711function file_save_draft_area_files($draftitemid, $contextid, $component, $filearea, $itemid, array $options=null, $text=null, $forcehttps=false) {
 712    global $USER;
 713
 714    $usercontext = get_context_instance(CONTEXT_USER, $USER->id);
 715    $fs = get_file_storage();
 716
 717    $options = (array)$options;
 718    if (!isset($options['subdirs'])) {
 719        $options['subdirs'] = false;
 720    }
 721    if (!isset($options['maxfiles'])) {
 722        $options['maxfiles'] = -1; // unlimited
 723    }
 724    if (!isset($options['maxbytes'])) {
 725        $options['maxbytes'] = 0; // unlimited
 726    }
 727
 728    $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid, 'id');
 729    $oldfiles   = $fs->get_area_files($contextid, $component, $filearea, $itemid, 'id');
 730
 731    if (count($draftfiles) < 2) {
 732        // means there are no files - one file means root dir only ;-)
 733        $fs->delete_area_files($contextid, $component, $filearea, $itemid);
 734
 735    } else if (count($oldfiles) < 2) {
 736        $filecount = 0;
 737        // there were no files before - one file means root dir only ;-)
 738        foreach ($draftfiles as $file) {
 739            $file_record = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea, 'itemid'=>$itemid);
 740            if (!$options['subdirs']) {
 741                if ($file->get_filepath() !== '/' or $file->is_directory()) {
 742                    continue;
 743                }
 744            }
 745            if ($options['maxbytes'] and $options['maxbytes'] < $file->get_filesize()) {
 746                // oversized file - should not get here at all
 747                continue;
 748            }
 749            if ($options['maxfiles'] != -1 and $options['maxfiles'] <= $filecount) {
 750                // more files - should not get here at all
 751                break;
 752            }
 753            if (!$file->is_directory()) {
 754                $filecount++;
 755            }
 756
 757            if ($file->is_external_file()) {
 758                $repoid = $file->get_repository_id();
 759                if (!empty($repoid)) {
 760                    $file_record['repositoryid'] = $repoid;
 761                    $file_record['reference'] = $file->get_reference();
 762                }
 763            }
 764            file_restore_source_field_from_draft_file($file);
 765
 766            $fs->create_file_from_storedfile($file_record, $file);
 767        }
 768
 769    } else {
 770        // we have to merge old and new files - we want to keep file ids for files that were not changed
 771        // we change time modified for all new and changed files, we keep time created as is
 772
 773        $newhashes = array();
 774        foreach ($draftfiles as $file) {
 775            $newhash = $fs->get_pathname_hash($contextid, $component, $filearea, $itemid, $file->get_filepath(), $file->get_filename());
 776            file_restore_source_field_from_draft_file($file);
 777            $newhashes[$newhash] = $file;
 778        }
 779        $filecount = 0;
 780        foreach ($oldfiles as $oldfile) {
 781            $oldhash = $oldfile->get_pathnamehash();
 782            if (!isset($newhashes[$oldhash])) {
 783                // delete files not needed any more - deleted by user
 784                $oldfile->delete();
 785                continue;
 786            }
 787
 788            $newfile = $newhashes[$oldhash];
 789            // status changed, we delete old file, and create a new one
 790            if ($oldfile->get_status() != $newfile->get_status()) {
 791                // file was changed, use updated with new timemodified data
 792                $oldfile->delete();
 793                // This file will be added later
 794                continue;
 795            }
 796
 797            // Replaced file content
 798            if ($oldfile->get_contenthash() != $newfile->get_contenthash()) {
 799                $oldfile->replace_content_with($newfile);
 800            }
 801            // Updated author
 802            if ($oldfile->get_author() != $newfile->get_author()) {
 803                $oldfile->set_author($newfile->get_author());
 804            }
 805            // Updated license
 806            if ($oldfile->get_license() != $newfile->get_license()) {
 807                $oldfile->set_license($newfile->get_license());
 808            }
 809
 810            // Updated file source
 811            if ($oldfile->get_source() != $newfile->get_source()) {
 812                $oldfile->set_source($newfile->get_source());
 813            }
 814
 815            // Updated sort order
 816            if ($oldfile->get_sortorder() != $newfile->get_sortorder()) {
 817                $oldfile->set_sortorder($newfile->get_sortorder());
 818            }
 819
 820            // Update file size
 821            if ($oldfile->get_filesize() != $newfile->get_filesize()) {
 822                $oldfile->set_filesize($newfile->get_filesize());
 823            }
 824
 825            // Update file timemodified
 826            if ($oldfile->get_timemodified() != $newfile->get_timemodified()) {
 827                $oldfile->set_timemodified($newfile->get_timemodified());
 828            }
 829
 830            // unchanged file or directory - we keep it as is
 831            unset($newhashes[$oldhash]);
 832            if (!$oldfile->is_directory()) {
 833                $filecount++;
 834            }
 835        }
 836
 837        // Add fresh file or the file which has changed status
 838        // the size and subdirectory tests are extra safety only, the UI should prevent it
 839        foreach ($newhashes as $file) {
 840            $file_record = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea, 'itemid'=>$itemid, 'timemodified'=>time());
 841            if (!$options['subdirs']) {
 842                if ($file->get_filepath() !== '/' or $file->is_directory()) {
 843                    continue;
 844                }
 845            }
 846            if ($options['maxbytes'] and $options['maxbytes'] < $file->get_filesize()) {
 847                // oversized file - should not get here at all
 848                continue;
 849            }
 850            if ($options['maxfiles'] != -1 and $options['maxfiles'] <= $filecount) {
 851                // more files - should not get here at all
 852                break;
 853            }
 854            if (!$file->is_directory()) {
 855                $filecount++;
 856            }
 857
 858            if ($file->is_external_file()) {
 859                $repoid = $file->get_repository_id();
 860                if (!empty($repoid)) {
 861                    $file_record['repositoryid'] = $repoid;
 862                    $file_record['reference'] = $file->get_reference();
 863                }
 864            }
 865
 866            $fs->create_file_from_storedfile($file_record, $file);
 867        }
 868    }
 869
 870    // note: do not purge the draft area - we clean up areas later in cron,
 871    //       the reason is that user might press submit twice and they would loose the files,
 872    //       also sometimes we might want to use hacks that save files into two different areas
 873
 874    if (is_null($text)) {
 875        return null;
 876    } else {
 877        return file_rewrite_urls_to_pluginfile($text, $draftitemid, $forcehttps);
 878    }
 879}
 880
 881/**
 882 * Convert the draft file area URLs in some content to @@PLUGINFILE@@ tokens
 883 * ready to be saved in the database. Normally, this is done automatically by
 884 * {@link file_save_draft_area_files()}.
 885 *
 886 * @category files
 887 * @param string $text the content to process.
 888 * @param int $draftitemid the draft file area the content was using.
 889 * @param bool $forcehttps whether the content contains https URLs. Default false.
 890 * @return string the processed content.
 891 */
 892function file_rewrite_urls_to_pluginfile($text, $draftitemid, $forcehttps = false) {
 893    global $CFG, $USER;
 894
 895    $usercontext = get_context_instance(CONTEXT_USER, $USER->id);
 896
 897    $wwwroot = $CFG->wwwroot;
 898    if ($forcehttps) {
 899        $wwwroot = str_replace('http://', 'https://', $wwwroot);
 900    }
 901
 902    // relink embedded files if text submitted - no absolute links allowed in database!
 903    $text = str_ireplace("$wwwroot/draftfile.php/$usercontext->id/user/draft/$draftitemid/", '@@PLUGINFILE@@/', $text);
 904
 905    if (strpos($text, 'draftfile.php?file=') !== false) {
 906        $matches = array();
 907        preg_match_all("!$wwwroot/draftfile.php\?file=%2F{$usercontext->id}%2Fuser%2Fdraft%2F{$draftitemid}%2F[^'\",&<>|`\s:\\\\]+!iu", $text, $matches);
 908        if ($matches) {
 909            foreach ($matches[0] as $match) {
 910                $replace = str_ireplace('%2F', '/', $match);
 911                $text = str_replace($match, $replace, $text);
 912            }
 913        }
 914        $text = str_ireplace("$wwwroot/draftfile.php?file=/$usercontext->id/user/draft/$draftitemid/", '@@PLUGINFILE@@/', $text);
 915    }
 916
 917    return $text;
 918}
 919
 920/**
 921 * Set file sort order
 922 *
 923 * @global moodle_database $DB
 924 * @param int $contextid the context id
 925 * @param string $component file component
 926 * @param string $filearea file area.
 927 * @param int $itemid itemid.
 928 * @param string $filepath file path.
 929 * @param string $filename file name.
 930 * @param int $sortorder the sort order of file.
 931 * @return bool
 932 */
 933function file_set_sortorder($contextid, $component, $filearea, $itemid, $filepath, $filename, $sortorder) {
 934    global $DB;
 935    $conditions = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea, 'itemid'=>$itemid, 'filepath'=>$filepath, 'filename'=>$filename);
 936    if ($file_record = $DB->get_record('files', $conditions)) {
 937        $sortorder = (int)$sortorder;
 938        $file_record->sortorder = $sortorder;
 939        $DB->update_record('files', $file_record);
 940        return true;
 941    }
 942    return false;
 943}
 944
 945/**
 946 * reset file sort order number to 0
 947 * @global moodle_database $DB
 948 * @param int $contextid the context id
 949 * @param string $component
 950 * @param string $filearea file area.
 951 * @param int|bool $itemid itemid.
 952 * @return bool
 953 */
 954function file_reset_sortorder($contextid, $component, $filearea, $itemid=false) {
 955    global $DB;
 956
 957    $conditions = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea);
 958    if ($itemid !== false) {
 959        $conditions['itemid'] = $itemid;
 960    }
 961
 962    $file_records = $DB->get_records('files', $conditions);
 963    foreach ($file_records as $file_record) {
 964        $file_record->sortorder = 0;
 965        $DB->update_record('files', $file_record);
 966    }
 967    return true;
 968}
 969
 970/**
 971 * Returns description of upload error
 972 *
 973 * @param int $errorcode found in $_FILES['filename.ext']['error']
 974 * @return string error description string, '' if ok
 975 */
 976function file_get_upload_error($errorcode) {
 977
 978    switch ($errorcode) {
 979    case 0: // UPLOAD_ERR_OK - no error
 980        $errmessage = '';
 981        break;
 982
 983    case 1: // UPLOAD_ERR_INI_SIZE
 984        $errmessage = get_string('uploadserverlimit');
 985        break;
 986
 987    case 2: // UPLOAD_ERR_FORM_SIZE
 988        $errmessage = get_string('uploadformlimit');
 989        break;
 990
 991    case 3: // UPLOAD_ERR_PARTIAL
 992        $errmessage = get_string('uploadpartialfile');
 993        break;
 994
 995    case 4: // UPLOAD_ERR_NO_FILE
 996        $errmessage = get_string('uploadnofilefound');
 997        break;
 998
 999    // Note: there is no error with a value of 5
1000
1001    case 6: // UPLOAD_ERR_NO_TMP_DIR
1002        $errmessage = get_string('uploadnotempdir');
1003        break;
1004
1005    case 7: // UPLOAD_ERR_CANT_WRITE
1006        $errmessage = get_string('uploadcantwrite');
1007        break;
1008
1009    case 8: // UPLOAD_ERR_EXTENSION
1010        $errmessage = get_string('uploadextension');
1011        break;
1012
1013    default:
1014        $errmessage = get_string('uploadproblem');
1015    }
1016
1017    return $errmessage;
1018}
1019
1020/**
1021 * Recursive function formating an array in POST parameter
1022 * @param array $arraydata - the array that we are going to format and add into &$data array
1023 * @param string $currentdata - a row of the final postdata array at instant T
1024 *                when finish, it's assign to $data under this format: name[keyname][][]...[]='value'
1025 * @param array $data - the final data array containing all POST parameters : 1 row = 1 parameter
1026 */
1027function format_array_postdata_for_curlcall($arraydata, $currentdata, &$data) {
1028        foreach ($arraydata as $k=>$v) {
1029            $newcurrentdata = $currentdata;
1030            if (is_array($v)) { //the value is an array, call the function recursively
1031                $newcurrentdata = $newcurrentdata.'['.urlencode($k).']';
1032                format_array_postdata_for_curlcall($v, $newcurrentdata, $data);
1033            }  else { //add the POST parameter to the $data array
1034                $data[] = $newcurrentdata.'['.urlencode($k).']='.urlencode($v);
1035            }
1036        }
1037}
1038
1039/**
1040 * Transform a PHP array into POST parameter
1041 * (see the recursive function format_array_postdata_for_curlcall)
1042 * @param array $postdata
1043 * @return array containing all POST parameters  (1 row = 1 POST parameter)
1044 */
1045function format_postdata_for_curlcall($postdata) {
1046        $data = array();
1047        foreach ($postdata as $k=>$v) {
1048            if (is_array($v)) {
1049                $currentdata = urlencode($k);
1050                format_array_postdata_for_curlcall($v, $currentdata, $data);
1051            }  else {
1052                $data[] = urlencode($k).'='.urlencode($v);
1053            }
1054        }
1055        $convertedpostdata = implode('&', $data);
1056        return $convertedpostdata;
1057}
1058
1059/**
1060 * Fetches content of file from Internet (using proxy if defined). Uses cURL extension if present.
1061 * Due to security concerns only downloads from http(s) sources are supported.
1062 *
1063 * @todo MDL-31073 add version test for '7.10.5'
1064 * @category files
1065 * @param string $url file url starting with http(s)://
1066 * @param array $headers http headers, null if none. If set, should be an
1067 *   associative array of header name => value pairs.
1068 * @param array $postdata array means use POST request with given parameters
1069 * @param bool $fullresponse return headers, responses, etc in a similar way snoopy does
1070 *   (if false, just returns content)
1071 * @param int $timeout timeout for complete download process including all file transfer
1072 *   (default 5 minutes)
1073 * @param int $connecttimeout timeout for connection to server; this is the timeout that
1074 *   usually happens if the remote server is completely down (default 20 seconds);
1075 *   may not work when using proxy
1076 * @param bool $skipcertverify If true, the peer's SSL certificate will not be checked.
1077 *   Only use this when already in a trusted location.
1078 * @param string $tofile store the downloaded content to file instead of returning it.
1079 * @param bool $calctimeout false by default, true enables an extra head request to try and determine
1080 *   filesize and appropriately larger timeout based on $CFG->curltimeoutkbitrate
1081 * @return mixed false if request failed or content of the file as string if ok. True if file downloaded into $tofile successfully.
1082 */
1083function download_file_content($url, $headers=null, $postdata=null, $fullresponse=false, $timeout=300, $connecttimeout=20, $skipcertverify=false, $tofile=NULL, $calctimeout=false) {
1084    global $CFG;
1085
1086    // some extra security
1087    $newlines = array("\r", "\n");
1088    if (is_array($headers) ) {
1089        foreach ($headers as $key => $value) {
1090            $headers[$key] = str_replace($newlines, '', $value);
1091        }
1092    }
1093    $url = str_replace($newlines, '', $url);
1094    if (!preg_match('|^https?://|i', $url)) {
1095        if ($fullresponse) {
1096            $response = new stdClass();
1097            $response->status        = 0;
1098            $response->headers       = array();
1099            $response->response_code = 'Invalid protocol specified in url';
1100            $response->results       = '';
1101            $response->error         = 'Invalid protocol specified in url';
1102            return $response;
1103        } else {
1104            return false;
1105        }
1106    }
1107
1108    // check if proxy (if used) should be bypassed for this url
1109    $proxybypass = is_proxybypass($url);
1110
1111    if (!$ch = curl_init($url)) {
1112        debugging('Can not init curl.');
1113        return false;
1114    }
1115
1116    // set extra headers
1117    if (is_array($headers) ) {
1118        $headers2 = array();
1119        foreach ($headers as $key => $value) {
1120            $headers2[] = "$key: $value";
1121        }
1122        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers2);
1123    }
1124
1125    if ($skipcertverify) {
1126        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
1127    }
1128
1129    // use POST if requested
1130    if (is_array($postdata)) {
1131        $postdata = format_postdata_for_curlcall($postdata);
1132        curl_setopt($ch, CURLOPT_POST, true);
1133        curl_setopt($ch, CURLOPT_POSTFIELDS, $postdata);
1134    }
1135
1136    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
1137    curl_setopt($ch, CURLOPT_HEADER, false);
1138    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $connecttimeout);
1139
1140    if (!ini_get('open_basedir') and !ini_get('safe_mode')) {
1141        // TODO: add version test for '7.10.5'
1142        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
1143        curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
1144    }
1145
1146    if (!empty($CFG->proxyhost) and !$proxybypass) {
1147        // SOCKS supported in PHP5 only
1148        if (!empty($CFG->proxytype) and ($CFG->proxytype == 'SOCKS5')) {
1149            if (defined('CURLPROXY_SOCKS5')) {
1150                curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
1151            } else {
1152                curl_close($ch);
1153                if ($fullresponse) {
1154                    $response = new stdClass();
1155                    $response->status        = '0';
1156                    $response->headers       = array();
1157                    $response->response_code = 'SOCKS5 proxy is not supported in PHP4';
1158                    $response->results       = '';
1159                    $response->error         = 'SOCKS5 proxy is not supported in PHP4';
1160                    return $response;
1161                } else {
1162                    debugging("SOCKS5 proxy is not supported in PHP4.", DEBUG_ALL);
1163                    return false;
1164                }
1165            }
1166        }
1167
1168        curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, false);
1169
1170        if (empty($CFG->proxyport)) {
1171            curl_setopt($ch, CURLOPT_PROXY, $CFG->proxyhost);
1172        } else {
1173            curl_setopt($ch, CURLOPT_PROXY, $CFG->proxyhost.':'.$CFG->proxyport);
1174        }
1175
1176        if (!empty($CFG->proxyuser) and !empty($CFG->proxypassword)) {
1177            curl_setopt($ch, CURLOPT_PROXYUSERPWD, $CFG->proxyuser.':'.$CFG->proxypassword);
1178            if (defined('CURLOPT_PROXYAUTH')) {
1179                // any proxy authentication if PHP 5.1
1180                curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_BASIC | CURLAUTH_NTLM);
1181            }
1182        }
1183    }
1184
1185    // set up header and content handlers
1186    $received = new stdClass();
1187    $received->headers = array(); // received headers array
1188    $received->tofile  = $tofile;
1189    $received->fh      = null;
1190    curl_setopt($ch, CURLOPT_HEADERFUNCTION, partial('download_file_content_header_handler', $received));
1191    if ($tofile) {
1192        curl_setopt($ch, CURLOPT_WRITEFUNCTION, partial('download_file_content_write_handler', $received));
1193    }
1194
1195    if (!isset($CFG->curltimeoutkbitrate)) {
1196        //use very slow rate of 56kbps as a timeout speed when not set
1197        $bitrate = 56;
1198    } else {
1199        $bitrate = $CFG->curltimeoutkbitrate;
1200    }
1201
1202    // try to calculate the proper amount for timeout from remote file size.
1203    // if disabled or zero, we won't do any checks nor head requests.
1204    if ($calctimeout && $bitrate > 0) {
1205        //setup header request only options
1206        curl_setopt_array ($ch, array(
1207            CURLOPT_RETURNTRANSFER => false,
1208            CURLOPT_NOBODY         => true)
1209        );
1210
1211        curl_exec($ch);
1212        $info = curl_getinfo($ch);
1213        $err = curl_error($ch);
1214
1215        if ($err === '' && $info['download_content_length'] > 0) { //no curl errors
1216            $timeout = max($timeout, ceil($info['download_content_length'] * 8 / ($bitrate * 1024))); //adjust for large files only - take max timeout.
1217        }
1218        //reinstate affected curl options
1219        curl_setopt_array ($ch, array(
1220            CURLOPT_RETURNTRANSFER => true,
1221            CURLOPT_NOBODY         => false)
1222        );
1223    }
1224
1225    curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
1226    $result = curl_exec($ch);
1227
1228    // try to detect encoding problems
1229    if ((curl_errno($ch) == 23 or curl_errno($ch) == 61) and defined('CURLOPT_ENCODING')) {
1230        curl_setopt($ch, CURLOPT_ENCODING, 'none');
1231        $result = curl_exec($ch);
1232    }
1233
1234    if ($received->fh) {
1235        fclose($received->fh);
1236    }
1237
1238    if (curl_errno($ch)) {
1239        $error    = curl_error($ch);
1240        $error_no = curl_errno($ch);
1241        curl_close($ch);
1242
1243        if ($fullresponse) {
1244            $response = new stdClass();
1245            if ($error_no == 28) {
1246                $response->status    = '-100'; // mimic snoopy
1247            } else {
1248                $response->status    = '0';
1249            }
1250            $response->headers       = array();
1251            $response->response_code = $error;
1252            $response->results       = false;
1253            $response->error         = $error;
1254            return $response;
1255        } else {
1256            debugging("cURL request for \"$url\" failed with: $error ($error_no)", DEBUG_ALL);
1257            return false;
1258        }
1259
1260    } else {
1261        $info = curl_getinfo($ch);
1262        curl_close($ch);
1263
1264        if (empty($info['http_code'])) {
1265            // for security reasons we support only true http connections (Location: file:// exploit prevention)
1266            $response = new stdClass();
1267            $response->status        = '0';
1268            $response->headers       = array();
1269            $response->response_code = 'Unknown cURL error';
1270            $response->results       = false; // do NOT change this, we really want to ignore the result!
1271            $response->error         = 'Unknown cURL error';
1272
1273        } else {
1274            $response = new stdClass();;
1275            $response->status        = (string)$info['http_code'];
1276            $response->headers       = $received->headers;
1277            $response->response_code = $received->headers[0];
1278            $response->results       = $result;
1279            $response->error         = '';
1280        }
1281
1282        if ($fullresponse) {
1283            return $response;
1284        } else if ($info['http_code'] != 200) {
1285            debugging("cURL request for \"$url\" failed, HTTP response code: ".$response->response_code, DEBUG_ALL);
1286            return false;
1287        } else {
1288            return $response->results;
1289        }
1290    }
1291}
1292
1293/**
1294 * internal implementation
1295 * @param stdClass $received
1296 * @param resource $ch
1297 * @param mixed $header
1298 * @return int header length
1299 */
1300function download_file_content_header_handler($received, $ch, $header) {
1301    $received->headers[] = $header;
1302    return strlen($header);
1303}
1304
1305/**
1306 * internal implementation
1307 * @param stdClass $received
1308 * @param resource $ch
1309 * @param mixed $data
1310 */
1311function download_file_content_write_handler($received, $ch, $data) {
1312    if (!$received->fh) {
1313        $received->fh = fopen($received->tofile, 'w');
1314        if ($received->fh === false) {
1315

Large files files are truncated, but you can click here to view the full file