PageRenderTime 307ms CodeModel.GetById 113ms app.highlight 88ms RepoModel.GetById 82ms app.codeStats 2ms

/lib/filelib.php

https://github.com/dongsheng/moodle
PHP | 4548 lines | 2814 code | 503 blank | 1231 comment | 878 complexity | a795624598722a4c407fbe4d092588bb 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
  32/**
  33 * Unlimited area size constant
  34 */
  35define('FILE_AREA_MAX_BYTES_UNLIMITED', -1);
  36
  37require_once("$CFG->libdir/filestorage/file_exceptions.php");
  38require_once("$CFG->libdir/filestorage/file_storage.php");
  39require_once("$CFG->libdir/filestorage/zip_packer.php");
  40require_once("$CFG->libdir/filebrowser/file_browser.php");
  41
  42/**
  43 * Encodes file serving url
  44 *
  45 * @deprecated use moodle_url factory methods instead
  46 *
  47 * @todo MDL-31071 deprecate this function
  48 * @global stdClass $CFG
  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 */
  55function file_encode_url($urlbase, $path, $forcedownload=false, $https=false) {
  56    global $CFG;
  57
  58//TODO: deprecate this
  59
  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 .= '&amp;forcedownload=1';
  73        }
  74    }
  75
  76    if ($https) {
  77        $return = str_replace('http://', 'https://', $return);
  78    }
  79
  80    return $return;
  81}
  82
  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 */
  94function 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
 108/**
 109 * Prepares 'editor' formslib element from data in database
 110 *
 111 * The passed $data record must contain field foobar, foobarformat and optionally foobartrust. This
 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
 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 *
 118 * @category files
 119 * @param stdClass $data database field that holds the html text with embedded media
 120 * @param string $field the name of the database field that holds the html text with embedded media
 121 * @param array $options editor options (like maxifiles, maxbytes etc.)
 122 * @param stdClass $context context of the editor
 123 * @param string $component
 124 * @param string $filearea file area name
 125 * @param int $itemid item id, required if item exists
 126 * @return stdClass modified data object
 127 */
 128function file_prepare_standard_editor($data, $field, array $options, $context=null, $component=null, $filearea=null, $itemid=null) {
 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    }
 139    if (!isset($options['maxfiles'])) {
 140        $options['maxfiles'] = 0; // no files by default
 141    }
 142    if (!isset($options['noclean'])) {
 143        $options['noclean'] = false;
 144    }
 145
 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    }
 159
 160    if (is_null($itemid) or is_null($context)) {
 161        $contextid = null;
 162        $itemid = null;
 163        if (!isset($data)) {
 164            $data = new stdClass();
 165        }
 166        if (!isset($data->{$field})) {
 167            $data->{$field} = '';
 168        }
 169        if (!isset($data->{$field.'format'})) {
 170            $data->{$field.'format'} = editors_get_preferred_format();
 171        }
 172        if (!$options['noclean']) {
 173            $data->{$field} = clean_text($data->{$field}, $data->{$field.'format'});
 174        }
 175
 176    } else {
 177        if ($options['trusttext']) {
 178            // noclean ignored if trusttext enabled
 179            if (!isset($data->{$field.'trust'})) {
 180                $data->{$field.'trust'} = 0;
 181            }
 182            $data = trusttext_pre_edit($data, $field, $context);
 183        } else {
 184            if (!$options['noclean']) {
 185                $data->{$field} = clean_text($data->{$field}, $data->{$field.'format'});
 186            }
 187        }
 188        $contextid = $context->id;
 189    }
 190
 191    if ($options['maxfiles'] != 0) {
 192        $draftid_editor = file_get_submitted_draft_itemid($field);
 193        $currenttext = file_prepare_draft_area($draftid_editor, $contextid, $component, $filearea, $itemid, $options, $data->{$field});
 194        $data->{$field.'_editor'} = array('text'=>$currenttext, 'format'=>$data->{$field.'format'}, 'itemid'=>$draftid_editor);
 195    } else {
 196        $data->{$field.'_editor'} = array('text'=>$data->{$field}, 'format'=>$data->{$field.'format'}, 'itemid'=>0);
 197    }
 198
 199    return $data;
 200}
 201
 202/**
 203 * Prepares the content of the 'editor' form element with embedded media files to be saved in database
 204 *
 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
 211 * foobartrust, where foobar has URL to embedded files encoded.
 212 *
 213 * @category files
 214 * @param stdClass $data raw data submitted by the form
 215 * @param string $field name of the database field containing the html with embedded media files
 216 * @param array $options editor options (trusttext, subdirs, maxfiles, maxbytes etc.)
 217 * @param stdClass $context context, required for existing data
 218 * @param string $component file component
 219 * @param string $filearea file area name
 220 * @param int $itemid item id, required if item exists
 221 * @return stdClass modified data object
 222 */
 223function file_postupdate_standard_editor($data, $field, array $options, $context, $component=null, $filearea=null, $itemid=null) {
 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'])) {
 235        $options['maxfiles'] = 0; // no files by default
 236    }
 237    if (!isset($options['maxbytes'])) {
 238        $options['maxbytes'] = 0; // unlimited
 239    }
 240
 241    if ($options['trusttext']) {
 242        $data->{$field.'trust'} = trusttext_trusted($context);
 243    } else {
 244        $data->{$field.'trust'} = 0;
 245    }
 246
 247    $editor = $data->{$field.'_editor'};
 248
 249    if ($options['maxfiles'] == 0 or is_null($filearea) or is_null($itemid) or empty($editor['itemid'])) {
 250        $data->{$field} = $editor['text'];
 251    } else {
 252        $data->{$field} = file_save_draft_area_files($editor['itemid'], $context->id, $component, $filearea, $itemid, $options, $editor['text'], $options['forcehttps']);
 253    }
 254    $data->{$field.'format'} = $editor['format'];
 255
 256    return $data;
 257}
 258
 259/**
 260 * Saves text and files modified by Editor formslib element
 261 *
 262 * @category files
 263 * @param stdClass $data $database entry field
 264 * @param string $field name of data field
 265 * @param array $options various options
 266 * @param stdClass $context context - must already exist
 267 * @param string $component
 268 * @param string $filearea file area name
 269 * @param int $itemid must already exist, usually means data is in db
 270 * @return stdClass modified data obejct
 271 */
 272function file_prepare_standard_filemanager($data, $field, array $options, $context=null, $component=null, $filearea=null, $itemid=null) {
 273    $options = (array)$options;
 274    if (!isset($options['subdirs'])) {
 275        $options['subdirs'] = false;
 276    }
 277    if (is_null($itemid) or is_null($context)) {
 278        $itemid = null;
 279        $contextid = null;
 280    } else {
 281        $contextid = $context->id;
 282    }
 283
 284    $draftid_editor = file_get_submitted_draft_itemid($field.'_filemanager');
 285    file_prepare_draft_area($draftid_editor, $contextid, $component, $filearea, $itemid, $options);
 286    $data->{$field.'_filemanager'} = $draftid_editor;
 287
 288    return $data;
 289}
 290
 291/**
 292 * Saves files modified by File manager formslib element
 293 *
 294 * @todo MDL-31073 review this function
 295 * @category files
 296 * @param stdClass $data $database entry field
 297 * @param string $field name of data field
 298 * @param array $options various options
 299 * @param stdClass $context context - must already exist
 300 * @param string $component
 301 * @param string $filearea file area name
 302 * @param int $itemid must already exist, usually means data is in db
 303 * @return stdClass modified data obejct
 304 */
 305function file_postupdate_standard_filemanager($data, $field, array $options, $context, $component, $filearea, $itemid) {
 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
 317    if (empty($data->{$field.'_filemanager'})) {
 318        $data->$field = '';
 319
 320    } else {
 321        file_save_draft_area_files($data->{$field.'_filemanager'}, $context->id, $component, $filearea, $itemid, $options);
 322        $fs = get_file_storage();
 323
 324        if ($fs->get_area_files($context->id, $component, $filearea, $itemid)) {
 325            $data->$field = '1'; // TODO: this is an ugly hack (skodak)
 326        } else {
 327            $data->$field = '';
 328        }
 329    }
 330
 331    return $data;
 332}
 333
 334/**
 335 * Generate a draft itemid
 336 *
 337 * @category files
 338 * @global moodle_database $DB
 339 * @global stdClass $USER
 340 * @return int a random but available draft itemid that can be used to create a new draft
 341 * file area.
 342 */
 343function file_get_unused_draft_itemid() {
 344    global $DB, $USER;
 345
 346    if (isguestuser() or !isloggedin()) {
 347        // guests and not-logged-in users can not be allowed to upload anything!!!!!!
 348        print_error('noguest');
 349    }
 350
 351    $contextid = context_user::instance($USER->id)->id;
 352
 353    $fs = get_file_storage();
 354    $draftitemid = rand(1, 999999999);
 355    while ($files = $fs->get_area_files($contextid, 'user', 'draft', $draftitemid)) {
 356        $draftitemid = rand(1, 999999999);
 357    }
 358
 359    return $draftitemid;
 360}
 361
 362/**
 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');
 366 *
 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.
 372 * @param string $component
 373 * @param string $filearea helps indentify the file area.
 374 * @param int $itemid helps identify the file area. Can be null if there are no files yet.
 375 * @param array $options text and file options ('subdirs'=>false, 'forcehttps'=>false)
 376 * @param string $text some html content that needs to have embedded links rewritten to point to the draft area.
 377 * @return string|null returns string if $text was passed in, the rewritten $text is returned. Otherwise NULL.
 378 */
 379function file_prepare_draft_area(&$draftitemid, $contextid, $component, $filearea, $itemid, array $options=null, $text=null) {
 380    global $CFG, $USER, $CFG;
 381
 382    $options = (array)$options;
 383    if (!isset($options['subdirs'])) {
 384        $options['subdirs'] = false;
 385    }
 386    if (!isset($options['forcehttps'])) {
 387        $options['forcehttps'] = false;
 388    }
 389
 390    $usercontext = context_user::instance($USER->id);
 391    $fs = get_file_storage();
 392
 393    if (empty($draftitemid)) {
 394        // create a new area and copy existing files into
 395        $draftitemid = file_get_unused_draft_itemid();
 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)) {
 398            foreach ($files as $file) {
 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                }
 404                if (!$options['subdirs'] and ($file->is_directory() or $file->get_filepath() !== '/')) {
 405                    continue;
 406                }
 407                $draftfile = $fs->create_file_from_storedfile($file_record, $file);
 408                // XXX: This is a hack for file manager (MDL-28666)
 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
 426            }
 427        }
 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        }
 434    } else {
 435        // nothing to do
 436    }
 437
 438    if (is_null($text)) {
 439        return null;
 440    }
 441
 442    // relink embedded files - editor can not handle @@PLUGINFILE@@ !
 443    return file_rewrite_pluginfile_urls($text, 'draftfile.php', $usercontext->id, 'user', 'draft', $draftitemid, $options);
 444}
 445
 446/**
 447 * Convert encoded URLs in $text from the @@PLUGINFILE@@/... form to an actual URL.
 448 *
 449 * @category files
 450 * @global stdClass $CFG
 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.
 453 * @param int $contextid This parameter and the next two identify the file area to use.
 454 * @param string $component
 455 * @param string $filearea helps identify the file area.
 456 * @param int $itemid helps identify the file area.
 457 * @param array $options text and file options ('forcehttps'=>false)
 458 * @return string the processed text.
 459 */
 460function file_rewrite_pluginfile_urls($text, $file, $contextid, $component, $filearea, $itemid, array $options=null) {
 461    global $CFG;
 462
 463    $options = (array)$options;
 464    if (!isset($options['forcehttps'])) {
 465        $options['forcehttps'] = false;
 466    }
 467
 468    if (!$CFG->slasharguments) {
 469        $file = $file . '?file=';
 470    }
 471
 472    $baseurl = "$CFG->wwwroot/$file/$contextid/$component/$filearea/";
 473
 474    if ($itemid !== null) {
 475        $baseurl .= "$itemid/";
 476    }
 477
 478    if ($options['forcehttps']) {
 479        $baseurl = str_replace('http://', 'https://', $baseurl);
 480    }
 481
 482    return str_replace('@@PLUGINFILE@@/', $baseurl, $text);
 483}
 484
 485/**
 486 * Returns information about files in a draft area.
 487 *
 488 * @global stdClass $CFG
 489 * @global stdClass $USER
 490 * @param int $draftitemid the draft area item id.
 491 * @param string $filepath path to the directory from which the information have to be retrieved.
 492 * @return array with the following entries:
 493 *      'filecount' => number of files in the draft area.
 494 *      'filesize' => total size of the files in the draft area.
 495 *      'foldercount' => number of folders in the draft area.
 496 *      'filesize_without_references' => total size of the area excluding file references.
 497 * (more information will be added as needed).
 498 */
 499function file_get_draft_area_info($draftitemid, $filepath = '/') {
 500    global $CFG, $USER;
 501
 502    $usercontext = context_user::instance($USER->id);
 503    $fs = get_file_storage();
 504
 505    $results = array(
 506        'filecount' => 0,
 507        'foldercount' => 0,
 508        'filesize' => 0,
 509        'filesize_without_references' => 0
 510    );
 511
 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    }
 517    foreach ($draftfiles as $file) {
 518        if ($file->is_directory()) {
 519            $results['foldercount'] += 1;
 520        } else {
 521            $results['filecount'] += 1;
 522        }
 523
 524        $filesize = $file->get_filesize();
 525        $results['filesize'] += $filesize;
 526        if (!$file->is_external_file()) {
 527            $results['filesize_without_references'] += $filesize;
 528        }
 529    }
 530
 531    return $results;
 532}
 533
 534/**
 535 * Returns whether a draft area has exceeded/will exceed its size limit.
 536 *
 537 * Please note that the unlimited value for $areamaxbytes is -1 {@link FILE_AREA_MAX_BYTES_UNLIMITED}, not 0.
 538 *
 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.
 542 * @param bool $includereferences true to include the size of the references in the area size.
 543 * @return bool true if the area will/has exceeded its limit.
 544 * @since Moodle 2.4
 545 */
 546function file_is_draft_area_limit_reached($draftitemid, $areamaxbytes, $newfilesize = 0, $includereferences = false) {
 547    if ($areamaxbytes != FILE_AREA_MAX_BYTES_UNLIMITED) {
 548        $draftinfo = file_get_draft_area_info($draftitemid);
 549        $areasize = $draftinfo['filesize_without_references'];
 550        if ($includereferences) {
 551            $areasize = $draftinfo['filesize'];
 552        }
 553        if ($areasize + $newfilesize > $areamaxbytes) {
 554            return true;
 555        }
 556    }
 557    return false;
 558}
 559
 560/**
 561 * Get used space of files
 562 * @global moodle_database $DB
 563 * @global stdClass $USER
 564 * @return int total bytes
 565 */
 566function file_get_user_used_space() {
 567    global $DB, $USER;
 568
 569    $usercontext = context_user::instance($USER->id);
 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;
 578}
 579
 580/**
 581 * Convert any string to a valid filepath
 582 * @todo review this function
 583 * @param string $str
 584 * @return string path
 585 */
 586function file_correct_filepath($str) { //TODO: what is this? (skodak) - No idea (Fred)
 587    if ($str == '/' or empty($str)) {
 588        return '/';
 589    } else {
 590        return '/'.trim($str, '/').'/';
 591    }
 592}
 593
 594/**
 595 * Generate a folder tree of draft area of current USER recursively
 596 *
 597 * @todo MDL-31073 use normal return value instead, this does not fit the rest of api here (skodak)
 598 * @param int $draftitemid
 599 * @param string $filepath
 600 * @param mixed $data
 601 */
 602function file_get_drafarea_folders($draftitemid, $filepath, &$data) {
 603    global $USER, $OUTPUT, $CFG;
 604    $data->children = array();
 605    $context = context_user::instance($USER->id);
 606    $fs = get_file_storage();
 607    if ($files = $fs->get_directory_files($context->id, 'user', 'draft', $draftitemid, $filepath, false)) {
 608        foreach ($files as $file) {
 609            if ($file->is_directory()) {
 610                $item = new stdClass();
 611                $item->sortorder = $file->get_sortorder();
 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();
 618                file_get_drafarea_folders($draftitemid, $item->filepath, $item);
 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
 632 * @return stdClass
 633 */
 634function file_get_drafarea_files($draftitemid, $filepath = '/') {
 635    global $USER, $OUTPUT, $CFG;
 636
 637    $context = context_user::instance($USER->id);
 638    $fs = get_file_storage();
 639
 640    $data = new stdClass();
 641    $data->path = array();
 642    $data->path[] = array('name'=>get_string('files'), 'path'=>'/');
 643
 644    // will be used to build breadcrumb
 645    $trail = '/';
 646    if ($filepath !== '/') {
 647        $filepath = file_correct_filepath($filepath);
 648        $parts = explode('/', $filepath);
 649        foreach ($parts as $part) {
 650            if ($part != '' && $part != null) {
 651                $trail .= ($part.'/');
 652                $data->path[] = array('name'=>$part, 'path'=>$trail);
 653            }
 654        }
 655    }
 656
 657    $list = array();
 658    $maxlength = 12;
 659    if ($files = $fs->get_directory_files($context->id, 'user', 'draft', $draftitemid, $filepath, false)) {
 660        foreach ($files as $file) {
 661            $item = new stdClass();
 662            $item->filename = $file->get_filename();
 663            $item->filepath = $file->get_filepath();
 664            $item->fullname = trim($item->filename, '/');
 665            $filesize = $file->get_filesize();
 666            $item->size = $filesize ? $filesize : null;
 667            $item->filesize = $filesize ? display_size($filesize) : '';
 668
 669            $item->sortorder = $file->get_sortorder();
 670            $item->author = $file->get_author();
 671            $item->license = $file->get_license();
 672            $item->datemodified = $file->get_timemodified();
 673            $item->datecreated = $file->get_timecreated();
 674            $item->isref = $file->is_external_file();
 675            if ($item->isref && $file->get_status() == 666) {
 676                $item->originalmissing = true;
 677            }
 678            // find the file this draft file was created from and count all references in local
 679            // system pointing to that file
 680            $source = @unserialize($file->get_source());
 681            if (isset($source->original)) {
 682                $item->refcount = $fs->search_references_count($source->original);
 683            }
 684
 685            if ($file->is_directory()) {
 686                $item->filesize = 0;
 687                $item->icon = $OUTPUT->pix_url(file_folder_icon(24))->out(false);
 688                $item->type = 'folder';
 689                $foldername = explode('/', trim($item->filepath, '/'));
 690                $item->fullname = trim(array_pop($foldername), '/');
 691                $item->thumbnail = $OUTPUT->pix_url(file_folder_icon(90))->out(false);
 692            } else {
 693                // do NOT use file browser here!
 694                $item->mimetype = get_mimetype_description($file);
 695                if (file_extension_in_typegroup($file->get_filename(), 'archive')) {
 696                    $item->type = 'zip';
 697                } else {
 698                    $item->type = 'file';
 699                }
 700                $itemurl = moodle_url::make_draftfile_url($draftitemid, $item->filepath, $item->filename);
 701                $item->url = $itemurl->out();
 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);
 704                if ($imageinfo = $file->get_imageinfo()) {
 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()));
 707                    $item->image_width = $imageinfo['width'];
 708                    $item->image_height = $imageinfo['height'];
 709                }
 710            }
 711            $list[] = $item;
 712        }
 713    }
 714    $data->itemid = $draftitemid;
 715    $data->list = $list;
 716    return $data;
 717}
 718
 719/**
 720 * Returns draft area itemid for a given element.
 721 *
 722 * @category files
 723 * @param string $elname name of formlib editor element, or a hidden form field that stores the draft area item id, etc.
 724 * @return int the itemid, or 0 if there is not one yet.
 725 */
 726function file_get_submitted_draft_itemid($elname) {
 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;
 730    }
 731    if (is_array($_REQUEST[$elname])) {
 732        $param = optional_param_array($elname, 0, PARAM_INT);
 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        }
 739
 740    } else {
 741        $param = optional_param($elname, 0, PARAM_INT);
 742    }
 743
 744    if ($param) {
 745        require_sesskey();
 746    }
 747
 748    return $param;
 749}
 750
 751/**
 752 * Restore the original source field from draft files
 753 *
 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 *
 757 * @param stored_file $storedfile This only works with draft files
 758 * @return stored_file
 759 */
 760function file_restore_source_field_from_draft_file($storedfile) {
 761    $source = @unserialize($storedfile->get_source());
 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        }
 769    }
 770    return $storedfile;
 771}
 772/**
 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.
 775 *
 776 * @category files
 777 * @global stdClass $USER
 778 * @param int $draftitemid the id of the draft area to use. Normally obtained
 779 *      from file_get_submitted_draft_itemid('elementname') or similar.
 780 * @param int $contextid This parameter and the next two identify the file area to save to.
 781 * @param string $component
 782 * @param string $filearea indentifies the file area.
 783 * @param int $itemid helps identifies the file area.
 784 * @param array $options area options (subdirs=>false, maxfiles=-1, maxbytes=0)
 785 * @param string $text some html content that needs to have embedded links rewritten
 786 *      to the @@PLUGINFILE@@ form for saving in the database.
 787 * @param bool $forcehttps force https urls.
 788 * @return string|null if $text was passed in, the rewritten $text is returned. Otherwise NULL.
 789 */
 790function file_save_draft_area_files($draftitemid, $contextid, $component, $filearea, $itemid, array $options=null, $text=null, $forcehttps=false) {
 791    global $USER;
 792
 793    $usercontext = context_user::instance($USER->id);
 794    $fs = get_file_storage();
 795
 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    }
 803    if (!isset($options['maxbytes']) || $options['maxbytes'] == USER_CAN_IGNORE_FILE_SIZE_LIMITS) {
 804        $options['maxbytes'] = 0; // unlimited
 805    }
 806    if (!isset($options['areamaxbytes'])) {
 807        $options['areamaxbytes'] = FILE_AREA_MAX_BYTES_UNLIMITED; // Unlimited.
 808    }
 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    }
 816
 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
 824    $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid, 'id');
 825    $oldfiles   = $fs->get_area_files($contextid, $component, $filearea, $itemid, 'id');
 826
 827    // One file in filearea means it is empty (it has only top-level directory '.').
 828    if (count($draftfiles) > 1 || count($oldfiles) > 1) {
 829        // we have to merge old and new files - we want to keep file ids for files that were not changed
 830        // we change time modified for all new and changed files, we keep time created as is
 831
 832        $newhashes = array();
 833        $filecount = 0;
 834        foreach ($draftfiles as $file) {
 835            if (!$options['subdirs'] && $file->get_filepath() !== '/') {
 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            }
 852            $newhash = $fs->get_pathname_hash($contextid, $component, $filearea, $itemid, $file->get_filepath(), $file->get_filename());
 853            $newhashes[$newhash] = $file;
 854        }
 855
 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.
 858        foreach ($oldfiles as $oldfile) {
 859            $oldhash = $oldfile->get_pathnamehash();
 860            if (!isset($newhashes[$oldhash])) {
 861                // delete files not needed any more - deleted by user
 862                $oldfile->delete();
 863                continue;
 864            }
 865
 866            $newfile = $newhashes[$oldhash];
 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
 886            // status changed, we delete old file, and create a new one
 887            if ($oldfile->get_status() != $newfile->get_status()) {
 888                // file was changed, use updated with new timemodified data
 889                $oldfile->delete();
 890                // This file will be added later
 891                continue;
 892            }
 893
 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
 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.
 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);
 912            }
 913
 914            // Updated sort order
 915            if ($oldfile->get_sortorder() != $newfile->get_sortorder()) {
 916                $oldfile->set_sortorder($newfile->get_sortorder());
 917            }
 918
 919            // Update file timemodified
 920            if ($oldfile->get_timemodified() != $newfile->get_timemodified()) {
 921                $oldfile->set_timemodified($newfile->get_timemodified());
 922            }
 923
 924            // Replaced file content
 925            if (!$oldfile->is_directory() &&
 926                    ($oldfile->get_contenthash() != $newfile->get_contenthash() ||
 927                    $oldfile->get_filesize() != $newfile->get_filesize() ||
 928                    $oldfile->get_referencefileid() != $newfile->get_referencefileid() ||
 929                    $oldfile->get_userid() != $newfile->get_userid())) {
 930                $oldfile->replace_file_with($newfile);
 931            }
 932
 933            // unchanged file or directory - we keep it as is
 934            unset($newhashes[$oldhash]);
 935        }
 936
 937        // Add fresh file or the file which has changed status
 938        // the size and subdirectory tests are extra safety only, the UI should prevent it
 939        foreach ($newhashes as $file) {
 940            $file_record = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea, 'itemid'=>$itemid, 'timemodified'=>time());
 941            if ($source = @unserialize($file->get_source())) {
 942                // Field files.source for draftarea files contains serialised object with source and original information.
 943                // We only store the source part of it for non-draft file area.
 944                $file_record['source'] = $source->source;
 945            }
 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
 955            $fs->create_file_from_storedfile($file_record, $file);
 956        }
 957    }
 958
 959    // note: do not purge the draft area - we clean up areas later in cron,
 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
 962
 963    if (is_null($text)) {
 964        return null;
 965    } else {
 966        return file_rewrite_urls_to_pluginfile($text, $draftitemid, $forcehttps);
 967    }
 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()}.
 974 *
 975 * @category files
 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 */
 981function file_rewrite_urls_to_pluginfile($text, $draftitemid, $forcehttps = false) {
 982    global $CFG, $USER;
 983
 984    $usercontext = context_user::instance($USER->id);
 985
 986    $wwwroot = $CFG->wwwroot;
 987    if ($forcehttps) {
 988        $wwwroot = str_replace('http://', 'https://', $wwwroot);
 989    }
 990
 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);
1004    }
1005
1006    return $text;
1007}
1008
1009/**
1010 * Set file sort order
1011 *
1012 * @global moodle_database $DB
1013 * @param int $contextid the context id
1014 * @param string $component file component
1015 * @param string $filearea file area.
1016 * @param int $itemid itemid.
1017 * @param string $filepath file path.
1018 * @param string $filename file name.
1019 * @param int $sortorder the sort order of file.
1020 * @return bool
1021 */
1022function file_set_sortorder($contextid, $component, $filearea, $itemid, $filepath, $filename, $sortorder) {
1023    global $DB;
1024    $conditions = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea, 'itemid'=>$itemid, 'filepath'=>$filepath, 'filename'=>$filename);
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
1036 * @global moodle_database $DB
1037 * @param int $contextid the context id
1038 * @param string $component
1039 * @param string $filearea file area.
1040 * @param int|bool $itemid itemid.
1041 * @return bool
1042 */
1043function file_reset_sortorder($contextid, $component, $filearea, $itemid=false) {
1044    global $DB;
1045
1046    $conditions = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea);
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
1059/**
1060 * Returns description of upload error
1061 *
1062 * @param int $errorcode found in $_FILES['filename.ext']['error']
1063 * @return string error description string, '' if ok
1064 */
1065function file_get_upload_error($errorcode) {
1066
1067    switch ($errorcode) {
1068    case 0: // UPLOAD_ERR_OK - no error
1069        $errmessage = '';
1070        break;
1071
1072    case 1: // UPLOAD_ERR_INI_SIZE
1073        $errmessage = get_string('uploadserverlimit');
1074        break;
1075
1076    case 2: // UPLOAD_ERR_FORM_SIZE
1077        $errmessage = get_string('uploadformlimit');
1078        break;
1079
1080    case 3: // UPLOAD_ERR_PARTIAL
1081        $errmessage = get_string('uploadpartialfile');
1082        break;
1083
1084    case 4: // UPLOAD_ERR_NO_FILE
1085        $errmessage = get_string('uploadnofilefound');
1086        break;
1087
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
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 */
1116function format_array_postdata_for_curlcall($arraydata, $currentdata, &$data) {
1117        foreach ($arraydata as $k=>$v) {
1118            $newcurrentdata = $currentdata;
1119            if (is_array($v)) { //the value is an array, call the function recursively
1120                $newcurrentdata = $newcurrentdata.'['.urlencode($k).']';
1121                format_array_postdata_for_curlcall($v, $newcurrentdata, $data);
1122            }  else { //add the POST parameter to the $data array
1123                $data[] = $newcurrentdata.'['.urlencode($k).']='.urlencode($v);
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 */
1134function 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 {
1141                $data[] = urlencode($k).'='.urlencode($v);
1142            }
1143        }
1144        $convertedpostdata = implode('&', $data);
1145        return $convertedpostdata;
1146}
1147
1148/**
1149 * Fetches content of file from Internet (using proxy if defined). Uses cURL extension if present.
1150 * Due to security concerns only downloads from http(s) sources are supported.
1151 *
1152 * @category files
1153 * @param string $url file url starting with http(s)://
1154 * @param array $headers http headers, null if none. If set, should be an
1155 *   associative array of header name => value pairs.
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
1158 *   (if false, just returns content)
1159 * @param int $timeout timeout for complete download process including all file transfer
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
1164 * @param bool $skipcertverify If true, the peer's SSL certificate will not be checked.
1165 *   Only use this when already in a trusted location.
1166 * @param string $tofile store the downloaded content to file instead of returning it.
1167 * @param bool $calctimeout false by default, true enables an extra head request to try and determine
1168 *   filesize and appropriately larger timeout based on $CFG->curltimeoutkbitrate
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.
1171 */
1172function download_file_content($url, $headers=null, $postdata=null, $fullresponse=false, $timeout=300, $connecttimeout=20, $skipcertverify=false, $tofile=NULL, $calctimeout=false) {
1173    global $CFG;
1174
1175    // Only http and https links supported.
1176    if (!preg_match('|^https?://|i', $url)) {
1177        if ($fullresponse) {
1178            $response = new stdClass();
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
1190    $options = array();
1191
1192    $headers2 = array();
1193    if (is_array($headers)) {
1194        foreach ($headers as $key => $value) {
1195            if (is_numeric($key)) {
1196                $headers2[] = $value;
1197            } else {
1198                $headers2[] = "$key: $value";
1199            }
1200        }
1201    }
1202
1203    if ($skipcertverify) {
1204        $options['CURLOPT_SSL_VERIFYPEER'] = false;
1205    } else {
1206        $options['CURLOPT_SSL_VERIFYPEER'] = true;
1207    }
1208
1209    $options['CURLOPT_CONNECTTIMEOUT'] = $connecttimeout;
1210
1211    $options['CURLOPT_FOLLOWLOCATION'] = 1;
1212    $options['CURLOPT_MAXREDIRS'] = 5;
1213
1214    // Use POST if requested.
1215    if (is_array($postdata)) {
1216        $postdata = format_postdata_for_curlcall($postdata);
1217    } else if (empty($postdata)) {
1218        $postdata = null;
1219    }
1220
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;
1227    }
1228    if ($calctimeout and !isset($postdata)) {
1229        $curl = new curl();
1230        $curl->setHeader($headers2);
1231
1232        $curl->head($url, $postdata, $options);
1233
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)));
1239        }
1240    }
1241
1242    $curl = new curl();
1243    $curl->setHeader($headers2);
1244
1245    $options['CURLOPT_RETURNTRANSFER'] = true;
1246    $options['CURLOPT_NOBODY'] = false;
1247    $options['CURLOPT_TIMEOUT'] = $timeout;
1248
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;
1262            }
1263        }
1264        $options['CURLOPT_FILE'] = $fh;
1265    }
1266
1267    if (isset($postdata)) {
1268        $content = $curl->post($url, $postdata, $options);
1269    } else {
1270        $content = $curl->get($url, null, $options);
1271    }
1272
1273    if ($tofile) {
1274        fclose($fh);
1275        @chmod($tofile, $CFG->filepermissions);
1276    }
1277
1278/*
1279    // Try to detect encoding problems.
1280    if ((curl_errno($ch) == 23 or curl_errno($ch) == 61) and defined('CURLOPT_ENCODING')) {
1281        curl_setopt($ch, CURLOPT_ENCODING, 'none');
1282        $result = curl_exec($ch);
1283    }
1284*/
1285
1286    $info       = $curl->get_info();
1287    $error_no   = $curl->get_errno();

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