PageRenderTime 69ms CodeModel.GetById 14ms app.highlight 35ms RepoModel.GetById 1ms app.codeStats 2ms

/lib/filelib.php

http://github.com/moodle/moodle
PHP | 5050 lines | 3200 code | 516 blank | 1334 comment | 886 complexity | 25f8cd4a9dd8511ad47f99b84bd0804e 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/**
  34 * Do not process file merging when working with draft area files.
  35 */
  36define('IGNORE_FILE_MERGE', -1);
  37
  38/**
  39 * Unlimited area size constant
  40 */
  41define('FILE_AREA_MAX_BYTES_UNLIMITED', -1);
  42
  43require_once("$CFG->libdir/filestorage/file_exceptions.php");
  44require_once("$CFG->libdir/filestorage/file_storage.php");
  45require_once("$CFG->libdir/filestorage/zip_packer.php");
  46require_once("$CFG->libdir/filebrowser/file_browser.php");
  47
  48/**
  49 * Encodes file serving url
  50 *
  51 * @deprecated use moodle_url factory methods instead
  52 *
  53 * @todo MDL-31071 deprecate this function
  54 * @global stdClass $CFG
  55 * @param string $urlbase
  56 * @param string $path /filearea/itemid/dir/dir/file.exe
  57 * @param bool $forcedownload
  58 * @param bool $https https url required
  59 * @return string encoded file url
  60 */
  61function file_encode_url($urlbase, $path, $forcedownload=false, $https=false) {
  62    global $CFG;
  63
  64//TODO: deprecate this
  65
  66    if ($CFG->slasharguments) {
  67        $parts = explode('/', $path);
  68        $parts = array_map('rawurlencode', $parts);
  69        $path  = implode('/', $parts);
  70        $return = $urlbase.$path;
  71        if ($forcedownload) {
  72            $return .= '?forcedownload=1';
  73        }
  74    } else {
  75        $path = rawurlencode($path);
  76        $return = $urlbase.'?file='.$path;
  77        if ($forcedownload) {
  78            $return .= '&amp;forcedownload=1';
  79        }
  80    }
  81
  82    if ($https) {
  83        $return = str_replace('http://', 'https://', $return);
  84    }
  85
  86    return $return;
  87}
  88
  89/**
  90 * Detects if area contains subdirs,
  91 * this is intended for file areas that are attached to content
  92 * migrated from 1.x where subdirs were allowed everywhere.
  93 *
  94 * @param context $context
  95 * @param string $component
  96 * @param string $filearea
  97 * @param string $itemid
  98 * @return bool
  99 */
 100function file_area_contains_subdirs(context $context, $component, $filearea, $itemid) {
 101    global $DB;
 102
 103    if (!isset($itemid)) {
 104        // Not initialised yet.
 105        return false;
 106    }
 107
 108    // Detect if any directories are already present, this is necessary for content upgraded from 1.x.
 109    $select = "contextid = :contextid AND component = :component AND filearea = :filearea AND itemid = :itemid AND filepath <> '/' AND filename = '.'";
 110    $params = array('contextid'=>$context->id, 'component'=>$component, 'filearea'=>$filearea, 'itemid'=>$itemid);
 111    return $DB->record_exists_select('files', $select, $params);
 112}
 113
 114/**
 115 * Prepares 'editor' formslib element from data in database
 116 *
 117 * The passed $data record must contain field foobar, foobarformat and optionally foobartrust. This
 118 * function then copies the embedded files into draft area (assigning itemids automatically),
 119 * creates the form element foobar_editor and rewrites the URLs so the embedded images can be
 120 * displayed.
 121 * In your mform definition, you must have an 'editor' element called foobar_editor. Then you call
 122 * your mform's set_data() supplying the object returned by this function.
 123 *
 124 * @category files
 125 * @param stdClass $data database field that holds the html text with embedded media
 126 * @param string $field the name of the database field that holds the html text with embedded media
 127 * @param array $options editor options (like maxifiles, maxbytes etc.)
 128 * @param stdClass $context context of the editor
 129 * @param string $component
 130 * @param string $filearea file area name
 131 * @param int $itemid item id, required if item exists
 132 * @return stdClass modified data object
 133 */
 134function file_prepare_standard_editor($data, $field, array $options, $context=null, $component=null, $filearea=null, $itemid=null) {
 135    $options = (array)$options;
 136    if (!isset($options['trusttext'])) {
 137        $options['trusttext'] = false;
 138    }
 139    if (!isset($options['forcehttps'])) {
 140        $options['forcehttps'] = false;
 141    }
 142    if (!isset($options['subdirs'])) {
 143        $options['subdirs'] = false;
 144    }
 145    if (!isset($options['maxfiles'])) {
 146        $options['maxfiles'] = 0; // no files by default
 147    }
 148    if (!isset($options['noclean'])) {
 149        $options['noclean'] = false;
 150    }
 151
 152    //sanity check for passed context. This function doesn't expect $option['context'] to be set
 153    //But this function is called before creating editor hence, this is one of the best places to check
 154    //if context is used properly. This check notify developer that they missed passing context to editor.
 155    if (isset($context) && !isset($options['context'])) {
 156        //if $context is not null then make sure $option['context'] is also set.
 157        debugging('Context for editor is not set in editoroptions. Hence editor will not respect editor filters', DEBUG_DEVELOPER);
 158    } else if (isset($options['context']) && isset($context)) {
 159        //If both are passed then they should be equal.
 160        if ($options['context']->id != $context->id) {
 161            $exceptionmsg = 'Editor context ['.$options['context']->id.'] is not equal to passed context ['.$context->id.']';
 162            throw new coding_exception($exceptionmsg);
 163        }
 164    }
 165
 166    if (is_null($itemid) or is_null($context)) {
 167        $contextid = null;
 168        $itemid = null;
 169        if (!isset($data)) {
 170            $data = new stdClass();
 171        }
 172        if (!isset($data->{$field})) {
 173            $data->{$field} = '';
 174        }
 175        if (!isset($data->{$field.'format'})) {
 176            $data->{$field.'format'} = editors_get_preferred_format();
 177        }
 178        if (!$options['noclean']) {
 179            $data->{$field} = clean_text($data->{$field}, $data->{$field.'format'});
 180        }
 181
 182    } else {
 183        if ($options['trusttext']) {
 184            // noclean ignored if trusttext enabled
 185            if (!isset($data->{$field.'trust'})) {
 186                $data->{$field.'trust'} = 0;
 187            }
 188            $data = trusttext_pre_edit($data, $field, $context);
 189        } else {
 190            if (!$options['noclean']) {
 191                $data->{$field} = clean_text($data->{$field}, $data->{$field.'format'});
 192            }
 193        }
 194        $contextid = $context->id;
 195    }
 196
 197    if ($options['maxfiles'] != 0) {
 198        $draftid_editor = file_get_submitted_draft_itemid($field);
 199        $currenttext = file_prepare_draft_area($draftid_editor, $contextid, $component, $filearea, $itemid, $options, $data->{$field});
 200        $data->{$field.'_editor'} = array('text'=>$currenttext, 'format'=>$data->{$field.'format'}, 'itemid'=>$draftid_editor);
 201    } else {
 202        $data->{$field.'_editor'} = array('text'=>$data->{$field}, 'format'=>$data->{$field.'format'}, 'itemid'=>0);
 203    }
 204
 205    return $data;
 206}
 207
 208/**
 209 * Prepares the content of the 'editor' form element with embedded media files to be saved in database
 210 *
 211 * This function moves files from draft area to the destination area and
 212 * encodes URLs to the draft files so they can be safely saved into DB. The
 213 * form has to contain the 'editor' element named foobar_editor, where 'foobar'
 214 * is the name of the database field to hold the wysiwyg editor content. The
 215 * editor data comes as an array with text, format and itemid properties. This
 216 * function automatically adds $data properties foobar, foobarformat and
 217 * foobartrust, where foobar has URL to embedded files encoded.
 218 *
 219 * @category files
 220 * @param stdClass $data raw data submitted by the form
 221 * @param string $field name of the database field containing the html with embedded media files
 222 * @param array $options editor options (trusttext, subdirs, maxfiles, maxbytes etc.)
 223 * @param stdClass $context context, required for existing data
 224 * @param string $component file component
 225 * @param string $filearea file area name
 226 * @param int $itemid item id, required if item exists
 227 * @return stdClass modified data object
 228 */
 229function file_postupdate_standard_editor($data, $field, array $options, $context, $component=null, $filearea=null, $itemid=null) {
 230    $options = (array)$options;
 231    if (!isset($options['trusttext'])) {
 232        $options['trusttext'] = false;
 233    }
 234    if (!isset($options['forcehttps'])) {
 235        $options['forcehttps'] = false;
 236    }
 237    if (!isset($options['subdirs'])) {
 238        $options['subdirs'] = false;
 239    }
 240    if (!isset($options['maxfiles'])) {
 241        $options['maxfiles'] = 0; // no files by default
 242    }
 243    if (!isset($options['maxbytes'])) {
 244        $options['maxbytes'] = 0; // unlimited
 245    }
 246    if (!isset($options['removeorphaneddrafts'])) {
 247        $options['removeorphaneddrafts'] = false; // Don't remove orphaned draft files by default.
 248    }
 249
 250    if ($options['trusttext']) {
 251        $data->{$field.'trust'} = trusttext_trusted($context);
 252    } else {
 253        $data->{$field.'trust'} = 0;
 254    }
 255
 256    $editor = $data->{$field.'_editor'};
 257
 258    if ($options['maxfiles'] == 0 or is_null($filearea) or is_null($itemid) or empty($editor['itemid'])) {
 259        $data->{$field} = $editor['text'];
 260    } else {
 261        // Clean the user drafts area of any files not referenced in the editor text.
 262        if ($options['removeorphaneddrafts']) {
 263            file_remove_editor_orphaned_files($editor);
 264        }
 265        $data->{$field} = file_save_draft_area_files($editor['itemid'], $context->id, $component, $filearea, $itemid, $options, $editor['text'], $options['forcehttps']);
 266    }
 267    $data->{$field.'format'} = $editor['format'];
 268
 269    return $data;
 270}
 271
 272/**
 273 * Saves text and files modified by Editor formslib element
 274 *
 275 * @category files
 276 * @param stdClass $data $database entry field
 277 * @param string $field name of data field
 278 * @param array $options various options
 279 * @param stdClass $context context - must already exist
 280 * @param string $component
 281 * @param string $filearea file area name
 282 * @param int $itemid must already exist, usually means data is in db
 283 * @return stdClass modified data obejct
 284 */
 285function file_prepare_standard_filemanager($data, $field, array $options, $context=null, $component=null, $filearea=null, $itemid=null) {
 286    $options = (array)$options;
 287    if (!isset($options['subdirs'])) {
 288        $options['subdirs'] = false;
 289    }
 290    if (is_null($itemid) or is_null($context)) {
 291        $itemid = null;
 292        $contextid = null;
 293    } else {
 294        $contextid = $context->id;
 295    }
 296
 297    $draftid_editor = file_get_submitted_draft_itemid($field.'_filemanager');
 298    file_prepare_draft_area($draftid_editor, $contextid, $component, $filearea, $itemid, $options);
 299    $data->{$field.'_filemanager'} = $draftid_editor;
 300
 301    return $data;
 302}
 303
 304/**
 305 * Saves files modified by File manager formslib element
 306 *
 307 * @todo MDL-31073 review this function
 308 * @category files
 309 * @param stdClass $data $database entry field
 310 * @param string $field name of data field
 311 * @param array $options various options
 312 * @param stdClass $context context - must already exist
 313 * @param string $component
 314 * @param string $filearea file area name
 315 * @param int $itemid must already exist, usually means data is in db
 316 * @return stdClass modified data obejct
 317 */
 318function file_postupdate_standard_filemanager($data, $field, array $options, $context, $component, $filearea, $itemid) {
 319    $options = (array)$options;
 320    if (!isset($options['subdirs'])) {
 321        $options['subdirs'] = false;
 322    }
 323    if (!isset($options['maxfiles'])) {
 324        $options['maxfiles'] = -1; // unlimited
 325    }
 326    if (!isset($options['maxbytes'])) {
 327        $options['maxbytes'] = 0; // unlimited
 328    }
 329
 330    if (empty($data->{$field.'_filemanager'})) {
 331        $data->$field = '';
 332
 333    } else {
 334        file_save_draft_area_files($data->{$field.'_filemanager'}, $context->id, $component, $filearea, $itemid, $options);
 335        $fs = get_file_storage();
 336
 337        if ($fs->get_area_files($context->id, $component, $filearea, $itemid)) {
 338            $data->$field = '1'; // TODO: this is an ugly hack (skodak)
 339        } else {
 340            $data->$field = '';
 341        }
 342    }
 343
 344    return $data;
 345}
 346
 347/**
 348 * Generate a draft itemid
 349 *
 350 * @category files
 351 * @global moodle_database $DB
 352 * @global stdClass $USER
 353 * @return int a random but available draft itemid that can be used to create a new draft
 354 * file area.
 355 */
 356function file_get_unused_draft_itemid() {
 357    global $DB, $USER;
 358
 359    if (isguestuser() or !isloggedin()) {
 360        // guests and not-logged-in users can not be allowed to upload anything!!!!!!
 361        print_error('noguest');
 362    }
 363
 364    $contextid = context_user::instance($USER->id)->id;
 365
 366    $fs = get_file_storage();
 367    $draftitemid = rand(1, 999999999);
 368    while ($files = $fs->get_area_files($contextid, 'user', 'draft', $draftitemid)) {
 369        $draftitemid = rand(1, 999999999);
 370    }
 371
 372    return $draftitemid;
 373}
 374
 375/**
 376 * Initialise a draft file area from a real one by copying the files. A draft
 377 * area will be created if one does not already exist. Normally you should
 378 * get $draftitemid by calling file_get_submitted_draft_itemid('elementname');
 379 *
 380 * @category files
 381 * @global stdClass $CFG
 382 * @global stdClass $USER
 383 * @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.
 384 * @param int $contextid This parameter and the next two identify the file area to copy files from.
 385 * @param string $component
 386 * @param string $filearea helps indentify the file area.
 387 * @param int $itemid helps identify the file area. Can be null if there are no files yet.
 388 * @param array $options text and file options ('subdirs'=>false, 'forcehttps'=>false)
 389 * @param string $text some html content that needs to have embedded links rewritten to point to the draft area.
 390 * @return string|null returns string if $text was passed in, the rewritten $text is returned. Otherwise NULL.
 391 */
 392function file_prepare_draft_area(&$draftitemid, $contextid, $component, $filearea, $itemid, array $options=null, $text=null) {
 393    global $CFG, $USER, $CFG;
 394
 395    $options = (array)$options;
 396    if (!isset($options['subdirs'])) {
 397        $options['subdirs'] = false;
 398    }
 399    if (!isset($options['forcehttps'])) {
 400        $options['forcehttps'] = false;
 401    }
 402
 403    $usercontext = context_user::instance($USER->id);
 404    $fs = get_file_storage();
 405
 406    if (empty($draftitemid)) {
 407        // create a new area and copy existing files into
 408        $draftitemid = file_get_unused_draft_itemid();
 409        $file_record = array('contextid'=>$usercontext->id, 'component'=>'user', 'filearea'=>'draft', 'itemid'=>$draftitemid);
 410        if (!is_null($itemid) and $files = $fs->get_area_files($contextid, $component, $filearea, $itemid)) {
 411            foreach ($files as $file) {
 412                if ($file->is_directory() and $file->get_filepath() === '/') {
 413                    // we need a way to mark the age of each draft area,
 414                    // by not copying the root dir we force it to be created automatically with current timestamp
 415                    continue;
 416                }
 417                if (!$options['subdirs'] and ($file->is_directory() or $file->get_filepath() !== '/')) {
 418                    continue;
 419                }
 420                $draftfile = $fs->create_file_from_storedfile($file_record, $file);
 421                // XXX: This is a hack for file manager (MDL-28666)
 422                // File manager needs to know the original file information before copying
 423                // to draft area, so we append these information in mdl_files.source field
 424                // {@link file_storage::search_references()}
 425                // {@link file_storage::search_references_count()}
 426                $sourcefield = $file->get_source();
 427                $newsourcefield = new stdClass;
 428                $newsourcefield->source = $sourcefield;
 429                $original = new stdClass;
 430                $original->contextid = $contextid;
 431                $original->component = $component;
 432                $original->filearea  = $filearea;
 433                $original->itemid    = $itemid;
 434                $original->filename  = $file->get_filename();
 435                $original->filepath  = $file->get_filepath();
 436                $newsourcefield->original = file_storage::pack_reference($original);
 437                $draftfile->set_source(serialize($newsourcefield));
 438                // End of file manager hack
 439            }
 440        }
 441        if (!is_null($text)) {
 442            // at this point there should not be any draftfile links yet,
 443            // because this is a new text from database that should still contain the @@pluginfile@@ links
 444            // this happens when developers forget to post process the text
 445            $text = str_replace("\"$CFG->wwwroot/draftfile.php", "\"$CFG->wwwroot/brokenfile.php#", $text);
 446        }
 447    } else {
 448        // nothing to do
 449    }
 450
 451    if (is_null($text)) {
 452        return null;
 453    }
 454
 455    // relink embedded files - editor can not handle @@PLUGINFILE@@ !
 456    return file_rewrite_pluginfile_urls($text, 'draftfile.php', $usercontext->id, 'user', 'draft', $draftitemid, $options);
 457}
 458
 459/**
 460 * Convert encoded URLs in $text from the @@PLUGINFILE@@/... form to an actual URL.
 461 * Passing a new option reverse = true in the $options var will make the function to convert actual URLs in $text to encoded URLs
 462 * in the @@PLUGINFILE@@ form.
 463 *
 464 * @param   string  $text The content that may contain ULRs in need of rewriting.
 465 * @param   string  $file The script that should be used to serve these files. pluginfile.php, draftfile.php, etc.
 466 * @param   int     $contextid This parameter and the next two identify the file area to use.
 467 * @param   string  $component
 468 * @param   string  $filearea helps identify the file area.
 469 * @param   int     $itemid helps identify the file area.
 470 * @param   array   $options
 471 *          bool    $options.forcehttps Force the user of https
 472 *          bool    $options.reverse Reverse the behaviour of the function
 473 *          mixed   $options.includetoken Use a token for authentication. True for current user, int value for other user id.
 474 *          string  The processed text.
 475 */
 476function file_rewrite_pluginfile_urls($text, $file, $contextid, $component, $filearea, $itemid, array $options=null) {
 477    global $CFG, $USER;
 478
 479    $options = (array)$options;
 480    if (!isset($options['forcehttps'])) {
 481        $options['forcehttps'] = false;
 482    }
 483
 484    $baseurl = "{$CFG->wwwroot}/{$file}";
 485    if (!empty($options['includetoken'])) {
 486        $userid = $options['includetoken'] === true ? $USER->id : $options['includetoken'];
 487        $token = get_user_key('core_files', $userid);
 488        $finalfile = basename($file);
 489        $tokenfile = "token{$finalfile}";
 490        $file = substr($file, 0, strlen($file) - strlen($finalfile)) . $tokenfile;
 491        $baseurl = "{$CFG->wwwroot}/{$file}";
 492
 493        if (!$CFG->slasharguments) {
 494            $baseurl .= "?token={$token}&file=";
 495        } else {
 496            $baseurl .= "/{$token}";
 497        }
 498    }
 499
 500    $baseurl .= "/{$contextid}/{$component}/{$filearea}/";
 501
 502    if ($itemid !== null) {
 503        $baseurl .= "$itemid/";
 504    }
 505
 506    if ($options['forcehttps']) {
 507        $baseurl = str_replace('http://', 'https://', $baseurl);
 508    }
 509
 510    if (!empty($options['reverse'])) {
 511        return str_replace($baseurl, '@@PLUGINFILE@@/', $text);
 512    } else {
 513        return str_replace('@@PLUGINFILE@@/', $baseurl, $text);
 514    }
 515}
 516
 517/**
 518 * Returns information about files in a draft area.
 519 *
 520 * @global stdClass $CFG
 521 * @global stdClass $USER
 522 * @param int $draftitemid the draft area item id.
 523 * @param string $filepath path to the directory from which the information have to be retrieved.
 524 * @return array with the following entries:
 525 *      'filecount' => number of files in the draft area.
 526 *      'filesize' => total size of the files in the draft area.
 527 *      'foldercount' => number of folders in the draft area.
 528 *      'filesize_without_references' => total size of the area excluding file references.
 529 * (more information will be added as needed).
 530 */
 531function file_get_draft_area_info($draftitemid, $filepath = '/') {
 532    global $USER;
 533
 534    $usercontext = context_user::instance($USER->id);
 535    return file_get_file_area_info($usercontext->id, 'user', 'draft', $draftitemid, $filepath);
 536}
 537
 538/**
 539 * Returns information about files in an area.
 540 *
 541 * @param int $contextid context id
 542 * @param string $component component
 543 * @param string $filearea file area name
 544 * @param int $itemid item id or all files if not specified
 545 * @param string $filepath path to the directory from which the information have to be retrieved.
 546 * @return array with the following entries:
 547 *      'filecount' => number of files in the area.
 548 *      'filesize' => total size of the files in the area.
 549 *      'foldercount' => number of folders in the area.
 550 *      'filesize_without_references' => total size of the area excluding file references.
 551 * @since Moodle 3.4
 552 */
 553function file_get_file_area_info($contextid, $component, $filearea, $itemid = 0, $filepath = '/') {
 554
 555    $fs = get_file_storage();
 556
 557    $results = array(
 558        'filecount' => 0,
 559        'foldercount' => 0,
 560        'filesize' => 0,
 561        'filesize_without_references' => 0
 562    );
 563
 564    $draftfiles = $fs->get_directory_files($contextid, $component, $filearea, $itemid, $filepath, true, true);
 565
 566    foreach ($draftfiles as $file) {
 567        if ($file->is_directory()) {
 568            $results['foldercount'] += 1;
 569        } else {
 570            $results['filecount'] += 1;
 571        }
 572
 573        $filesize = $file->get_filesize();
 574        $results['filesize'] += $filesize;
 575        if (!$file->is_external_file()) {
 576            $results['filesize_without_references'] += $filesize;
 577        }
 578    }
 579
 580    return $results;
 581}
 582
 583/**
 584 * Returns whether a draft area has exceeded/will exceed its size limit.
 585 *
 586 * Please note that the unlimited value for $areamaxbytes is -1 {@link FILE_AREA_MAX_BYTES_UNLIMITED}, not 0.
 587 *
 588 * @param int $draftitemid the draft area item id.
 589 * @param int $areamaxbytes the maximum size allowed in this draft area.
 590 * @param int $newfilesize the size that would be added to the current area.
 591 * @param bool $includereferences true to include the size of the references in the area size.
 592 * @return bool true if the area will/has exceeded its limit.
 593 * @since Moodle 2.4
 594 */
 595function file_is_draft_area_limit_reached($draftitemid, $areamaxbytes, $newfilesize = 0, $includereferences = false) {
 596    if ($areamaxbytes != FILE_AREA_MAX_BYTES_UNLIMITED) {
 597        $draftinfo = file_get_draft_area_info($draftitemid);
 598        $areasize = $draftinfo['filesize_without_references'];
 599        if ($includereferences) {
 600            $areasize = $draftinfo['filesize'];
 601        }
 602        if ($areasize + $newfilesize > $areamaxbytes) {
 603            return true;
 604        }
 605    }
 606    return false;
 607}
 608
 609/**
 610 * Get used space of files
 611 * @global moodle_database $DB
 612 * @global stdClass $USER
 613 * @return int total bytes
 614 */
 615function file_get_user_used_space() {
 616    global $DB, $USER;
 617
 618    $usercontext = context_user::instance($USER->id);
 619    $sql = "SELECT SUM(files1.filesize) AS totalbytes FROM {files} files1
 620            JOIN (SELECT contenthash, filename, MAX(id) AS id
 621            FROM {files}
 622            WHERE contextid = ? AND component = ? AND filearea != ?
 623            GROUP BY contenthash, filename) files2 ON files1.id = files2.id";
 624    $params = array('contextid'=>$usercontext->id, 'component'=>'user', 'filearea'=>'draft');
 625    $record = $DB->get_record_sql($sql, $params);
 626    return (int)$record->totalbytes;
 627}
 628
 629/**
 630 * Convert any string to a valid filepath
 631 * @todo review this function
 632 * @param string $str
 633 * @return string path
 634 */
 635function file_correct_filepath($str) { //TODO: what is this? (skodak) - No idea (Fred)
 636    if ($str == '/' or empty($str)) {
 637        return '/';
 638    } else {
 639        return '/'.trim($str, '/').'/';
 640    }
 641}
 642
 643/**
 644 * Generate a folder tree of draft area of current USER recursively
 645 *
 646 * @todo MDL-31073 use normal return value instead, this does not fit the rest of api here (skodak)
 647 * @param int $draftitemid
 648 * @param string $filepath
 649 * @param mixed $data
 650 */
 651function file_get_drafarea_folders($draftitemid, $filepath, &$data) {
 652    global $USER, $OUTPUT, $CFG;
 653    $data->children = array();
 654    $context = context_user::instance($USER->id);
 655    $fs = get_file_storage();
 656    if ($files = $fs->get_directory_files($context->id, 'user', 'draft', $draftitemid, $filepath, false)) {
 657        foreach ($files as $file) {
 658            if ($file->is_directory()) {
 659                $item = new stdClass();
 660                $item->sortorder = $file->get_sortorder();
 661                $item->filepath = $file->get_filepath();
 662
 663                $foldername = explode('/', trim($item->filepath, '/'));
 664                $item->fullname = trim(array_pop($foldername), '/');
 665
 666                $item->id = uniqid();
 667                file_get_drafarea_folders($draftitemid, $item->filepath, $item);
 668                $data->children[] = $item;
 669            } else {
 670                continue;
 671            }
 672        }
 673    }
 674}
 675
 676/**
 677 * Listing all files (including folders) in current path (draft area)
 678 * used by file manager
 679 * @param int $draftitemid
 680 * @param string $filepath
 681 * @return stdClass
 682 */
 683function file_get_drafarea_files($draftitemid, $filepath = '/') {
 684    global $USER, $OUTPUT, $CFG;
 685
 686    $context = context_user::instance($USER->id);
 687    $fs = get_file_storage();
 688
 689    $data = new stdClass();
 690    $data->path = array();
 691    $data->path[] = array('name'=>get_string('files'), 'path'=>'/');
 692
 693    // will be used to build breadcrumb
 694    $trail = '/';
 695    if ($filepath !== '/') {
 696        $filepath = file_correct_filepath($filepath);
 697        $parts = explode('/', $filepath);
 698        foreach ($parts as $part) {
 699            if ($part != '' && $part != null) {
 700                $trail .= ($part.'/');
 701                $data->path[] = array('name'=>$part, 'path'=>$trail);
 702            }
 703        }
 704    }
 705
 706    $list = array();
 707    $maxlength = 12;
 708    if ($files = $fs->get_directory_files($context->id, 'user', 'draft', $draftitemid, $filepath, false)) {
 709        foreach ($files as $file) {
 710            $item = new stdClass();
 711            $item->filename = $file->get_filename();
 712            $item->filepath = $file->get_filepath();
 713            $item->fullname = trim($item->filename, '/');
 714            $filesize = $file->get_filesize();
 715            $item->size = $filesize ? $filesize : null;
 716            $item->filesize = $filesize ? display_size($filesize) : '';
 717
 718            $item->sortorder = $file->get_sortorder();
 719            $item->author = $file->get_author();
 720            $item->license = $file->get_license();
 721            $item->datemodified = $file->get_timemodified();
 722            $item->datecreated = $file->get_timecreated();
 723            $item->isref = $file->is_external_file();
 724            if ($item->isref && $file->get_status() == 666) {
 725                $item->originalmissing = true;
 726            }
 727            // find the file this draft file was created from and count all references in local
 728            // system pointing to that file
 729            $source = @unserialize($file->get_source());
 730            if (isset($source->original)) {
 731                $item->refcount = $fs->search_references_count($source->original);
 732            }
 733
 734            if ($file->is_directory()) {
 735                $item->filesize = 0;
 736                $item->icon = $OUTPUT->image_url(file_folder_icon(24))->out(false);
 737                $item->type = 'folder';
 738                $foldername = explode('/', trim($item->filepath, '/'));
 739                $item->fullname = trim(array_pop($foldername), '/');
 740                $item->thumbnail = $OUTPUT->image_url(file_folder_icon(90))->out(false);
 741            } else {
 742                // do NOT use file browser here!
 743                $item->mimetype = get_mimetype_description($file);
 744                if (file_extension_in_typegroup($file->get_filename(), 'archive')) {
 745                    $item->type = 'zip';
 746                } else {
 747                    $item->type = 'file';
 748                }
 749                $itemurl = moodle_url::make_draftfile_url($draftitemid, $item->filepath, $item->filename);
 750                $item->url = $itemurl->out();
 751                $item->icon = $OUTPUT->image_url(file_file_icon($file, 24))->out(false);
 752                $item->thumbnail = $OUTPUT->image_url(file_file_icon($file, 90))->out(false);
 753
 754                // The call to $file->get_imageinfo() fails with an exception if the file can't be read on the file system.
 755                // We still want to add such files to the list, so the owner can view and delete them if needed. So, we only call
 756                // get_imageinfo() on files that can be read, and we also spoof the file status based on whether it was found.
 757                // We'll use the same status types used by stored_file->get_status(), where 0 = OK. 1 = problem, as these will be
 758                // used by the widget to display a warning about the problem files.
 759                // The value of stored_file->get_status(), and the file record are unaffected by this. It's only superficially set.
 760                $item->status = $fs->get_file_system()->is_file_readable_remotely_by_storedfile($file) ? 0 : 1;
 761                if ($item->status == 0) {
 762                    if ($imageinfo = $file->get_imageinfo()) {
 763                        $item->realthumbnail = $itemurl->out(false, array('preview' => 'thumb',
 764                            'oid' => $file->get_timemodified()));
 765                        $item->realicon = $itemurl->out(false, array('preview' => 'tinyicon', 'oid' => $file->get_timemodified()));
 766                        $item->image_width = $imageinfo['width'];
 767                        $item->image_height = $imageinfo['height'];
 768                    }
 769                }
 770            }
 771            $list[] = $item;
 772        }
 773    }
 774    $data->itemid = $draftitemid;
 775    $data->list = $list;
 776    return $data;
 777}
 778
 779/**
 780 * Returns all of the files in the draftarea.
 781 *
 782 * @param  int $draftitemid The draft item ID
 783 * @param  string $filepath path for the uploaded files.
 784 * @return array An array of files associated with this draft item id.
 785 */
 786function file_get_all_files_in_draftarea(int $draftitemid, string $filepath = '/') : array {
 787    $files = [];
 788    $draftfiles = file_get_drafarea_files($draftitemid, $filepath);
 789    file_get_drafarea_folders($draftitemid, $filepath, $draftfiles);
 790
 791    if (!empty($draftfiles)) {
 792        foreach ($draftfiles->list as $draftfile) {
 793            if ($draftfile->type == 'file') {
 794                $files[] = $draftfile;
 795            }
 796        }
 797
 798        if (isset($draftfiles->children)) {
 799            foreach ($draftfiles->children as $draftfile) {
 800                $files = array_merge($files, file_get_all_files_in_draftarea($draftitemid, $draftfile->filepath));
 801            }
 802        }
 803    }
 804    return $files;
 805}
 806
 807/**
 808 * Returns draft area itemid for a given element.
 809 *
 810 * @category files
 811 * @param string $elname name of formlib editor element, or a hidden form field that stores the draft area item id, etc.
 812 * @return int the itemid, or 0 if there is not one yet.
 813 */
 814function file_get_submitted_draft_itemid($elname) {
 815    // this is a nasty hack, ideally all new elements should use arrays here or there should be a new parameter
 816    if (!isset($_REQUEST[$elname])) {
 817        return 0;
 818    }
 819    if (is_array($_REQUEST[$elname])) {
 820        $param = optional_param_array($elname, 0, PARAM_INT);
 821        if (!empty($param['itemid'])) {
 822            $param = $param['itemid'];
 823        } else {
 824            debugging('Missing itemid, maybe caused by unset maxfiles option', DEBUG_DEVELOPER);
 825            return false;
 826        }
 827
 828    } else {
 829        $param = optional_param($elname, 0, PARAM_INT);
 830    }
 831
 832    if ($param) {
 833        require_sesskey();
 834    }
 835
 836    return $param;
 837}
 838
 839/**
 840 * Restore the original source field from draft files
 841 *
 842 * Do not use this function because it makes field files.source inconsistent
 843 * for draft area files. This function will be deprecated in 2.6
 844 *
 845 * @param stored_file $storedfile This only works with draft files
 846 * @return stored_file
 847 */
 848function file_restore_source_field_from_draft_file($storedfile) {
 849    $source = @unserialize($storedfile->get_source());
 850    if (!empty($source)) {
 851        if (is_object($source)) {
 852            $restoredsource = $source->source;
 853            $storedfile->set_source($restoredsource);
 854        } else {
 855            throw new moodle_exception('invalidsourcefield', 'error');
 856        }
 857    }
 858    return $storedfile;
 859}
 860
 861/**
 862 * Removes those files from the user drafts filearea which are not referenced in the editor text.
 863 *
 864 * @param stdClass $editor The online text editor element from the submitted form data.
 865 */
 866function file_remove_editor_orphaned_files($editor) {
 867    global $CFG, $USER;
 868
 869    // Find those draft files included in the text, and generate their hashes.
 870    $context = context_user::instance($USER->id);
 871    $baseurl = $CFG->wwwroot . '/draftfile.php/' . $context->id . '/user/draft/' . $editor['itemid'] . '/';
 872    $pattern = "/" . preg_quote($baseurl, '/') . "(.+?)[\?\"']/";
 873    preg_match_all($pattern, $editor['text'], $matches);
 874    $usedfilehashes = [];
 875    foreach ($matches[1] as $matchedfilename) {
 876        $matchedfilename = urldecode($matchedfilename);
 877        $usedfilehashes[] = \file_storage::get_pathname_hash($context->id, 'user', 'draft', $editor['itemid'], '/',
 878                                                             $matchedfilename);
 879    }
 880
 881    // Now, compare the hashes of all draft files, and remove those which don't match used files.
 882    $fs = get_file_storage();
 883    $files = $fs->get_area_files($context->id, 'user', 'draft', $editor['itemid'], 'id', false);
 884    foreach ($files as $file) {
 885        $tmphash = $file->get_pathnamehash();
 886        if (!in_array($tmphash, $usedfilehashes)) {
 887            $file->delete();
 888        }
 889    }
 890}
 891
 892/**
 893 * Finds all draft areas used in a textarea and copies the files into the primary textarea. If a user copies and pastes
 894 * content from another draft area it's possible for a single textarea to reference multiple draft areas.
 895 *
 896 * @category files
 897 * @param int $draftitemid the id of the primary draft area.
 898 *            When set to -1 (probably, by a WebService) it won't process file merging, keeping the original state of the file area.
 899 * @param int $usercontextid the user's context id.
 900 * @param string $text some html content that needs to have files copied to the correct draft area.
 901 * @param bool $forcehttps force https urls.
 902 *
 903 * @return string $text html content modified with new draft links
 904 */
 905function file_merge_draft_areas($draftitemid, $usercontextid, $text, $forcehttps = false) {
 906    if (is_null($text)) {
 907        return null;
 908    }
 909
 910    // Do not merge files, leave it as it was.
 911    if ($draftitemid === IGNORE_FILE_MERGE) {
 912        return null;
 913    }
 914
 915    $urls = extract_draft_file_urls_from_text($text, $forcehttps, $usercontextid, 'user', 'draft');
 916
 917    // No draft areas to rewrite.
 918    if (empty($urls)) {
 919        return $text;
 920    }
 921
 922    foreach ($urls as $url) {
 923        // Do not process the "home" draft area.
 924        if ($url['itemid'] == $draftitemid) {
 925            continue;
 926        }
 927
 928        // Decode the filename.
 929        $filename = urldecode($url['filename']);
 930
 931        // Copy the file.
 932        file_copy_file_to_file_area($url, $filename, $draftitemid);
 933
 934        // Rewrite draft area.
 935        $text = file_replace_file_area_in_text($url, $draftitemid, $text, $forcehttps);
 936    }
 937    return $text;
 938}
 939
 940/**
 941 * Rewrites a file area in arbitrary text.
 942 *
 943 * @param array $file General information about the file.
 944 * @param int $newid The new file area itemid.
 945 * @param string $text The text to rewrite.
 946 * @param bool $forcehttps force https urls.
 947 * @return string The rewritten text.
 948 */
 949function file_replace_file_area_in_text($file, $newid, $text, $forcehttps = false) {
 950    global $CFG;
 951
 952    $wwwroot = $CFG->wwwroot;
 953    if ($forcehttps) {
 954        $wwwroot = str_replace('http://', 'https://', $wwwroot);
 955    }
 956
 957    $search = [
 958        $wwwroot,
 959        $file['urlbase'],
 960        $file['contextid'],
 961        $file['component'],
 962        $file['filearea'],
 963        $file['itemid'],
 964        $file['filename']
 965    ];
 966    $replace = [
 967        $wwwroot,
 968        $file['urlbase'],
 969        $file['contextid'],
 970        $file['component'],
 971        $file['filearea'],
 972        $newid,
 973        $file['filename']
 974    ];
 975
 976    $text = str_ireplace( implode('/', $search), implode('/', $replace), $text);
 977    return $text;
 978}
 979
 980/**
 981 * Copies a file from one file area to another.
 982 *
 983 * @param array $file Information about the file to be copied.
 984 * @param string $filename The filename.
 985 * @param int $itemid The new file area.
 986 */
 987function file_copy_file_to_file_area($file, $filename, $itemid) {
 988    $fs = get_file_storage();
 989
 990    // Load the current file in the old draft area.
 991    $fileinfo = array(
 992        'component' => $file['component'],
 993        'filearea' => $file['filearea'],
 994        'itemid' => $file['itemid'],
 995        'contextid' => $file['contextid'],
 996        'filepath' => '/',
 997        'filename' => $filename
 998    );
 999    $oldfile = $fs->get_file($fileinfo['contextid'], $fileinfo['component'], $fileinfo['filearea'],
1000        $fileinfo['itemid'], $fileinfo['filepath'], $fileinfo['filename']);
1001    $newfileinfo = array(
1002        'component' => $file['component'],
1003        'filearea' => $file['filearea'],
1004        'itemid' => $itemid,
1005        'contextid' => $file['contextid'],
1006        'filepath' => '/',
1007        'filename' => $filename
1008    );
1009
1010    $newcontextid = $newfileinfo['contextid'];
1011    $newcomponent = $newfileinfo['component'];
1012    $newfilearea = $newfileinfo['filearea'];
1013    $newitemid = $newfileinfo['itemid'];
1014    $newfilepath = $newfileinfo['filepath'];
1015    $newfilename = $newfileinfo['filename'];
1016
1017    // Check if the file exists.
1018    if (!$fs->file_exists($newcontextid, $newcomponent, $newfilearea, $newitemid, $newfilepath, $newfilename)) {
1019        $fs->create_file_from_storedfile($newfileinfo, $oldfile);
1020    }
1021}
1022
1023/**
1024 * Saves files from a draft file area to a real one (merging the list of files).
1025 * Can rewrite URLs in some content at the same time if desired.
1026 *
1027 * @category files
1028 * @global stdClass $USER
1029 * @param int $draftitemid the id of the draft area to use. Normally obtained
1030 *      from file_get_submitted_draft_itemid('elementname') or similar.
1031 *      When set to -1 (probably, by a WebService) it won't process file merging, keeping the original state of the file area.
1032 * @param int $contextid This parameter and the next two identify the file area to save to.
1033 * @param string $component
1034 * @param string $filearea indentifies the file area.
1035 * @param int $itemid helps identifies the file area.
1036 * @param array $options area options (subdirs=>false, maxfiles=-1, maxbytes=0)
1037 * @param string $text some html content that needs to have embedded links rewritten
1038 *      to the @@PLUGINFILE@@ form for saving in the database.
1039 * @param bool $forcehttps force https urls.
1040 * @return string|null if $text was passed in, the rewritten $text is returned. Otherwise NULL.
1041 */
1042function file_save_draft_area_files($draftitemid, $contextid, $component, $filearea, $itemid, array $options=null, $text=null, $forcehttps=false) {
1043    global $USER;
1044
1045    // Do not merge files, leave it as it was.
1046    if ($draftitemid === IGNORE_FILE_MERGE) {
1047        // Safely return $text, no need to rewrite pluginfile because this is mostly comming from an external client like the app.
1048        return $text;
1049    }
1050
1051    $usercontext = context_user::instance($USER->id);
1052    $fs = get_file_storage();
1053
1054    $options = (array)$options;
1055    if (!isset($options['subdirs'])) {
1056        $options['subdirs'] = false;
1057    }
1058    if (!isset($options['maxfiles'])) {
1059        $options['maxfiles'] = -1; // unlimited
1060    }
1061    if (!isset($options['maxbytes']) || $options['maxbytes'] == USER_CAN_IGNORE_FILE_SIZE_LIMITS) {
1062        $options['maxbytes'] = 0; // unlimited
1063    }
1064    if (!isset($options['areamaxbytes'])) {
1065        $options['areamaxbytes'] = FILE_AREA_MAX_BYTES_UNLIMITED; // Unlimited.
1066    }
1067    $allowreferences = true;
1068    if (isset($options['return_types']) && !($options['return_types'] & (FILE_REFERENCE | FILE_CONTROLLED_LINK))) {
1069        // we assume that if $options['return_types'] is NOT specified, we DO allow references.
1070        // this is not exactly right. BUT there are many places in code where filemanager options
1071        // are not passed to file_save_draft_area_files()
1072        $allowreferences = false;
1073    }
1074
1075    // Check if the user has copy-pasted from other draft areas. Those files will be located in different draft
1076    // areas and need to be copied into the current draft area.
1077    $text = file_merge_draft_areas($draftitemid, $usercontext->id, $text, $forcehttps);
1078
1079    // Check if the draft area has exceeded the authorised limit. This should never happen as validation
1080    // should have taken place before, unless the user is doing something nauthly. If so, let's just not save
1081    // anything at all in the next area.
1082    if (file_is_draft_area_limit_reached($draftitemid, $options['areamaxbytes'])) {
1083        return null;
1084    }
1085
1086    $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid, 'id');
1087    $oldfiles   = $fs->get_area_files($contextid, $component, $filearea, $itemid, 'id');
1088
1089    // One file in filearea means it is empty (it has only top-level directory '.').
1090    if (count($draftfiles) > 1 || count($oldfiles) > 1) {
1091        // we have to merge old and new files - we want to keep file ids for files that were not changed
1092        // we change time modified for all new and changed files, we keep time created as is
1093
1094        $newhashes = array();
1095        $filecount = 0;
1096        $context = context::instance_by_id($contextid, MUST_EXIST);
1097        foreach ($draftfiles as $file) {
1098            if (!$options['subdirs'] && $file->get_filepath() !== '/') {
1099                continue;
1100            }
1101            if (!$allowreferences && $file->is_external_file()) {
1102                continue;
1103            }
1104            if (!$file->is_directory()) {
1105                // Check to see if this file was uploaded by someone who can ignore the file size limits.
1106                $fileusermaxbytes = get_user_max_upload_file_size($context, $options['maxbytes'], 0, 0, $file->get_userid());
1107                if ($fileusermaxbytes != USER_CAN_IGNORE_FILE_SIZE_LIMITS
1108                        && ($options['maxbytes'] and $options['maxbytes'] < $file->get_filesize())) {
1109                    // Oversized file.
1110                    continue;
1111                }
1112                if ($options['maxfiles'] != -1 and $options['maxfiles'] <= $filecount) {
1113                    // more files - should not get here at all
1114                    continue;
1115                }
1116                $filecount++;
1117            }
1118            $newhash = $fs->get_pathname_hash($contextid, $component, $filearea, $itemid, $file->get_filepath(), $file->get_filename());
1119            $newhashes[$newhash] = $file;
1120        }
1121
1122        // Loop through oldfiles and decide which we need to delete and which to update.
1123        // After this cycle the array $newhashes will only contain the files that need to be added.
1124        foreach ($oldfiles as $oldfile) {
1125            $oldhash = $oldfile->get_pathnamehash();
1126            if (!isset($newhashes[$oldhash])) {
1127                // delete files not needed any more - deleted by user
1128                $oldfile->delete();
1129                continue;
1130            }
1131
1132            $newfile = $newhashes[$oldhash];
1133            // Now we know that we have $oldfile and $newfile for the same path.
1134            // Let's check if we can update this file or we need to delete and create.
1135            if ($newfile->is_directory()) {
1136                // Directories are always ok to just update.
1137            } else if (($source = @unserialize($newfile->get_source())) && isset($source->original)) {
1138                // File has the 'original' - we need to update the file (it may even have not been changed at all).
1139                $original = file_storage::unpack_reference($source->original);
1140                if ($original['filename'] !== $oldfile->get_filename() || $original['filepath'] !== $oldfile->get_filepath()) {
1141                    // Very odd, original points to another file. Delete and create file.
1142                    $oldfile->delete();
1143                    continue;
1144                }
1145            } else {
1146                // The same file name but absence of 'original' means that file was deteled and uploaded again.
1147                // By deleting and creating new file we properly manage all existing references.
1148                $oldfile->delete();
1149                continue;
1150            }
1151
1152            // status changed, we delete old file, and create a new one
1153            if ($oldfile->get_status() != $newfile->get_status()) {
1154                // file was changed, use updated with new timemodified data
1155                $oldfile->delete();
1156                // This file will be added later
1157                continue;
1158            }
1159
1160            // Updated author
1161            if ($oldfile->get_author() != $newfile->get_author()) {
1162                $oldfile->set_author($newfile->get_author());
1163            }
1164            // Updated license
1165            if ($oldfile->get_license() != $newfile->get_license()) {
1166                $oldfile->set_license($newfile->get_license());
1167            }
1168
1169            // Updated file source
1170            // Field files.source for draftarea files contains serialised object with source and original information.
1171            // We only store the source part of it for non-draft file area.
1172            $newsource = $newfile->get_source();
1173            if ($source = @unserialize($newfile->get_source())) {
1174                $newsource = $source->source;
1175            }
1176            if ($oldfile->get_source() !== $newsource) {
1177                $oldfile->set_source($newsource);
1178            }
1179
1180            // Updated sort order
1181            if ($oldfile->get_sortorder() != $newfile->get_sortorder()) {
1182                $oldfile->set_sortorder($newfile->get_sortorder());
1183            }
1184
1185            // Update file timemodified
1186            if ($oldfile->get_timemodified() != $newfile->get_timemodified()) {
1187                $oldfile->set_timemodified($newfile->get_timemodified());
1188            }
1189
1190            // Replaced file content
1191            if (!$oldfile->is_directory() &&
1192                    ($oldfile->get_contenthash() != $newfile->get_contenthash() ||
1193                    $oldfile->get_filesize() != $newfile->get_filesize() ||
1194                    $oldfile->get_referencefileid() != $newfile->get_referencefileid() ||
1195                    $oldfile->get_userid() != $newfile->get_userid())) {
1196                $oldfile->replace_file_with($newfile);
1197            }
1198
1199            // unchanged file or directory - we keep it as is
1200            unset($newhashes[$oldhash]);
1201        }
1202
1203        // Add fresh file or the file which has changed status
1204        // the size and subdirectory tests are extra safety only, the UI should prevent it
1205        foreach ($newhashes as $file) {
1206            $file_record = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea, 'itemid'=>$itemid, 'timemodified'=>time());
1207            if ($source = @unserialize($file->get_source())) {
1208                // Field files.source for draftarea files contains serialised object with source and original information.
1209                // We only store the source part of it for non-draft file area.
1210                $file_record['source'] = $source->source;
1211            }
1212
1213            if ($file->is_external_file()) {
1214                $repoid = $file->get_repository_id();
1215                if (!empty($repoid)) {
1216                    $context = context::instance_by_id($contextid, MUST_EXIST);
1217                    $repo = repository::get_repository_by_id($repoid, $context);
1218                    if (!empty($options)) {
1219                        $repo->options = $options;
1220                    }
1221                    $file_record['repositoryid'] = $repoid;
1222                    // This hook gives the repo a place to do some house cleaning, and update the $reference before it's saved
1223                    // to the file store. E.g. transfer ownership of the file to a system account etc.
1224                    $reference = $repo->reference_file_selected($file->get_reference(), $context, $component, $filearea, $itemid);
1225
1226                    $file_record['reference'] = $reference;
1227                }
1228            }
1229
1230            $fs->create_file_from_storedfile($file_record, $file);
1231        }
1232    }
1233
1234    // note: do not purge the draft area - we clean up areas later in cron,
1235    //       the reason is that user might press submit twice and they would loose the files,
1236    //       also sometimes we might want to use hacks that save files into two different areas
1237
1238    if (is_null($text)) {
1239        return null;
1240    } else {
1241        return file_rewrite_urls_to_pluginfile($text, $draftitemid, $forcehttps);
1242    }
1243

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