PageRenderTime 5ms CodeModel.GetById 40ms app.highlight 30ms RepoModel.GetById 1ms app.codeStats 1ms

/moodle/lib/filelib.php

#
PHP | 3934 lines | 2563 code | 441 blank | 930 comment | 706 complexity | eceb4d9bf9256f71cf4bd0c6bb597cac MD5 | raw file

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

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

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