Search moodle.org's
Developer Documentation


  • Bug fixes for general core bugs in 2.8.x ended 9 November 2015 (12 months).
  • Bug fixes for security issues in 2.8.x ended 9 May 2016 (18 months).
  • minimum PHP 5.4.4 (always use latest PHP 5.4.x or 5.5.x on Windows - http://windows.php.net/download/), PHP 7 is NOT supported
  • /lib/ -> filelib.php (source)

    Differences Between: [Versions 28 and 29] [Versions 28 and 30] [Versions 28 and 31] [Versions 28 and 32] [Versions 28 and 33] [Versions 28 and 34] [Versions 28 and 35] [Versions 28 and 36] [Versions 28 and 37]

       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  
      25  defined('MOODLE_INTERNAL') || die();
      26  
      27  /**
      28   * BYTESERVING_BOUNDARY - string unique string constant.
      29   */
      30  define('BYTESERVING_BOUNDARY', 's1k2o3d4a5k6s7');
      31  
      32  /**
      33   * Unlimited area size constant
      34   */
      35  define('FILE_AREA_MAX_BYTES_UNLIMITED', -1);
      36  
      37  require_once("$CFG->libdir/filestorage/file_exceptions.php");
      38  require_once("$CFG->libdir/filestorage/file_storage.php");
      39  require_once("$CFG->libdir/filestorage/zip_packer.php");
      40  require_once("$CFG->libdir/filebrowser/file_browser.php");
      41  
      42  /**
      43   * Encodes file serving url
      44   *
      45   * @deprecated use moodle_url factory methods instead
      46   *
      47   * @todo MDL-31071 deprecate this function
      48   * @global stdClass $CFG
      49   * @param string $urlbase
      50   * @param string $path /filearea/itemid/dir/dir/file.exe
      51   * @param bool $forcedownload
      52   * @param bool $https https url required
      53   * @return string encoded file url
      54   */
      55  function file_encode_url($urlbase, $path, $forcedownload=false, $https=false) {
      56      global $CFG;
      57  
      58  //TODO: deprecate this
      59  
      60      if ($CFG->slasharguments) {
      61          $parts = explode('/', $path);
      62          $parts = array_map('rawurlencode', $parts);
      63          $path  = implode('/', $parts);
      64          $return = $urlbase.$path;
      65          if ($forcedownload) {
      66              $return .= '?forcedownload=1';
      67          }
      68      } else {
      69          $path = rawurlencode($path);
      70          $return = $urlbase.'?file='.$path;
      71          if ($forcedownload) {
      72              $return .= '&amp;forcedownload=1';
      73          }
      74      }
      75  
      76      if ($https) {
      77          $return = str_replace('http://', 'https://', $return);
      78      }
      79  
      80      return $return;
      81  }
      82  
      83  /**
      84   * Detects if area contains subdirs,
      85   * this is intended for file areas that are attached to content
      86   * migrated from 1.x where subdirs were allowed everywhere.
      87   *
      88   * @param context $context
      89   * @param string $component
      90   * @param string $filearea
      91   * @param string $itemid
      92   * @return bool
      93   */
      94  function file_area_contains_subdirs(context $context, $component, $filearea, $itemid) {
      95      global $DB;
      96  
      97      if (!isset($itemid)) {
      98          // Not initialised yet.
      99          return false;
     100      }
     101  
     102      // Detect if any directories are already present, this is necessary for content upgraded from 1.x.
     103      $select = "contextid = :contextid AND component = :component AND filearea = :filearea AND itemid = :itemid AND filepath <> '/' AND filename = '.'";
     104      $params = array('contextid'=>$context->id, 'component'=>$component, 'filearea'=>$filearea, 'itemid'=>$itemid);
     105      return $DB->record_exists_select('files', $select, $params);
     106  }
     107  
     108  /**
     109   * Prepares 'editor' formslib element from data in database
     110   *
     111   * The passed $data record must contain field foobar, foobarformat and optionally foobartrust. This
     112   * function then copies the embedded files into draft area (assigning itemids automatically),
     113   * creates the form element foobar_editor and rewrites the URLs so the embedded images can be
     114   * displayed.
     115   * In your mform definition, you must have an 'editor' element called foobar_editor. Then you call
     116   * your mform's set_data() supplying the object returned by this function.
     117   *
     118   * @category files
     119   * @param stdClass $data database field that holds the html text with embedded media
     120   * @param string $field the name of the database field that holds the html text with embedded media
     121   * @param array $options editor options (like maxifiles, maxbytes etc.)
     122   * @param stdClass $context context of the editor
     123   * @param string $component
     124   * @param string $filearea file area name
     125   * @param int $itemid item id, required if item exists
     126   * @return stdClass modified data object
     127   */
     128  function file_prepare_standard_editor($data, $field, array $options, $context=null, $component=null, $filearea=null, $itemid=null) {
     129      $options = (array)$options;
     130      if (!isset($options['trusttext'])) {
     131          $options['trusttext'] = false;
     132      }
     133      if (!isset($options['forcehttps'])) {
     134          $options['forcehttps'] = false;
     135      }
     136      if (!isset($options['subdirs'])) {
     137          $options['subdirs'] = false;
     138      }
     139      if (!isset($options['maxfiles'])) {
     140          $options['maxfiles'] = 0; // no files by default
     141      }
     142      if (!isset($options['noclean'])) {
     143          $options['noclean'] = false;
     144      }
     145  
     146      //sanity check for passed context. This function doesn't expect $option['context'] to be set
     147      //But this function is called before creating editor hence, this is one of the best places to check
     148      //if context is used properly. This check notify developer that they missed passing context to editor.
     149      if (isset($context) && !isset($options['context'])) {
     150          //if $context is not null then make sure $option['context'] is also set.
     151          debugging('Context for editor is not set in editoroptions. Hence editor will not respect editor filters', DEBUG_DEVELOPER);
     152      } else if (isset($options['context']) && isset($context)) {
     153          //If both are passed then they should be equal.
     154          if ($options['context']->id != $context->id) {
     155              $exceptionmsg = 'Editor context ['.$options['context']->id.'] is not equal to passed context ['.$context->id.']';
     156              throw new coding_exception($exceptionmsg);
     157          }
     158      }
     159  
     160      if (is_null($itemid) or is_null($context)) {
     161          $contextid = null;
     162          $itemid = null;
     163          if (!isset($data)) {
     164              $data = new stdClass();
     165          }
     166          if (!isset($data->{$field})) {
     167              $data->{$field} = '';
     168          }
     169          if (!isset($data->{$field.'format'})) {
     170              $data->{$field.'format'} = editors_get_preferred_format();
     171          }
     172          if (!$options['noclean']) {
     173              $data->{$field} = clean_text($data->{$field}, $data->{$field.'format'});
     174          }
     175  
     176      } else {
     177          if ($options['trusttext']) {
     178              // noclean ignored if trusttext enabled
     179              if (!isset($data->{$field.'trust'})) {
     180                  $data->{$field.'trust'} = 0;
     181              }
     182              $data = trusttext_pre_edit($data, $field, $context);
     183          } else {
     184              if (!$options['noclean']) {
     185                  $data->{$field} = clean_text($data->{$field}, $data->{$field.'format'});
     186              }
     187          }
     188          $contextid = $context->id;
     189      }
     190  
     191      if ($options['maxfiles'] != 0) {
     192          $draftid_editor = file_get_submitted_draft_itemid($field);
     193          $currenttext = file_prepare_draft_area($draftid_editor, $contextid, $component, $filearea, $itemid, $options, $data->{$field});
     194          $data->{$field.'_editor'} = array('text'=>$currenttext, 'format'=>$data->{$field.'format'}, 'itemid'=>$draftid_editor);
     195      } else {
     196          $data->{$field.'_editor'} = array('text'=>$data->{$field}, 'format'=>$data->{$field.'format'}, 'itemid'=>0);
     197      }
     198  
     199      return $data;
     200  }
     201  
     202  /**
     203   * Prepares the content of the 'editor' form element with embedded media files to be saved in database
     204   *
     205   * This function moves files from draft area to the destination area and
     206   * encodes URLs to the draft files so they can be safely saved into DB. The
     207   * form has to contain the 'editor' element named foobar_editor, where 'foobar'
     208   * is the name of the database field to hold the wysiwyg editor content. The
     209   * editor data comes as an array with text, format and itemid properties. This
     210   * function automatically adds $data properties foobar, foobarformat and
     211   * foobartrust, where foobar has URL to embedded files encoded.
     212   *
     213   * @category files
     214   * @param stdClass $data raw data submitted by the form
     215   * @param string $field name of the database field containing the html with embedded media files
     216   * @param array $options editor options (trusttext, subdirs, maxfiles, maxbytes etc.)
     217   * @param stdClass $context context, required for existing data
     218   * @param string $component file component
     219   * @param string $filearea file area name
     220   * @param int $itemid item id, required if item exists
     221   * @return stdClass modified data object
     222   */
     223  function file_postupdate_standard_editor($data, $field, array $options, $context, $component=null, $filearea=null, $itemid=null) {
     224      $options = (array)$options;
     225      if (!isset($options['trusttext'])) {
     226          $options['trusttext'] = false;
     227      }
     228      if (!isset($options['forcehttps'])) {
     229          $options['forcehttps'] = false;
     230      }
     231      if (!isset($options['subdirs'])) {
     232          $options['subdirs'] = false;
     233      }
     234      if (!isset($options['maxfiles'])) {
     235          $options['maxfiles'] = 0; // no files by default
     236      }
     237      if (!isset($options['maxbytes'])) {
     238          $options['maxbytes'] = 0; // unlimited
     239      }
     240  
     241      if ($options['trusttext']) {
     242          $data->{$field.'trust'} = trusttext_trusted($context);
     243      } else {
     244          $data->{$field.'trust'} = 0;
     245      }
     246  
     247      $editor = $data->{$field.'_editor'};
     248  
     249      if ($options['maxfiles'] == 0 or is_null($filearea) or is_null($itemid) or empty($editor['itemid'])) {
     250          $data->{$field} = $editor['text'];
     251      } else {
     252          $data->{$field} = file_save_draft_area_files($editor['itemid'], $context->id, $component, $filearea, $itemid, $options, $editor['text'], $options['forcehttps']);
     253      }
     254      $data->{$field.'format'} = $editor['format'];
     255  
     256      return $data;
     257  }
     258  
     259  /**
     260   * Saves text and files modified by Editor formslib element
     261   *
     262   * @category files
     263   * @param stdClass $data $database entry field
     264   * @param string $field name of data field
     265   * @param array $options various options
     266   * @param stdClass $context context - must already exist
     267   * @param string $component
     268   * @param string $filearea file area name
     269   * @param int $itemid must already exist, usually means data is in db
     270   * @return stdClass modified data obejct
     271   */
     272  function file_prepare_standard_filemanager($data, $field, array $options, $context=null, $component=null, $filearea=null, $itemid=null) {
     273      $options = (array)$options;
     274      if (!isset($options['subdirs'])) {
     275          $options['subdirs'] = false;
     276      }
     277      if (is_null($itemid) or is_null($context)) {
     278          $itemid = null;
     279          $contextid = null;
     280      } else {
     281          $contextid = $context->id;
     282      }
     283  
     284      $draftid_editor = file_get_submitted_draft_itemid($field.'_filemanager');
     285      file_prepare_draft_area($draftid_editor, $contextid, $component, $filearea, $itemid, $options);
     286      $data->{$field.'_filemanager'} = $draftid_editor;
     287  
     288      return $data;
     289  }
     290  
     291  /**
     292   * Saves files modified by File manager formslib element
     293   *
     294   * @todo MDL-31073 review this function
     295   * @category files
     296   * @param stdClass $data $database entry field
     297   * @param string $field name of data field
     298   * @param array $options various options
     299   * @param stdClass $context context - must already exist
     300   * @param string $component
     301   * @param string $filearea file area name
     302   * @param int $itemid must already exist, usually means data is in db
     303   * @return stdClass modified data obejct
     304   */
     305  function file_postupdate_standard_filemanager($data, $field, array $options, $context, $component, $filearea, $itemid) {
     306      $options = (array)$options;
     307      if (!isset($options['subdirs'])) {
     308          $options['subdirs'] = false;
     309      }
     310      if (!isset($options['maxfiles'])) {
     311          $options['maxfiles'] = -1; // unlimited
     312      }
     313      if (!isset($options['maxbytes'])) {
     314          $options['maxbytes'] = 0; // unlimited
     315      }
     316  
     317      if (empty($data->{$field.'_filemanager'})) {
     318          $data->$field = '';
     319  
     320      } else {
     321          file_save_draft_area_files($data->{$field.'_filemanager'}, $context->id, $component, $filearea, $itemid, $options);
     322          $fs = get_file_storage();
     323  
     324          if ($fs->get_area_files($context->id, $component, $filearea, $itemid)) {
     325              $data->$field = '1'; // TODO: this is an ugly hack (skodak)
     326          } else {
     327              $data->$field = '';
     328          }
     329      }
     330  
     331      return $data;
     332  }
     333  
     334  /**
     335   * Generate a draft itemid
     336   *
     337   * @category files
     338   * @global moodle_database $DB
     339   * @global stdClass $USER
     340   * @return int a random but available draft itemid that can be used to create a new draft
     341   * file area.
     342   */
     343  function file_get_unused_draft_itemid() {
     344      global $DB, $USER;
     345  
     346      if (isguestuser() or !isloggedin()) {
     347          // guests and not-logged-in users can not be allowed to upload anything!!!!!!
     348          print_error('noguest');
     349      }
     350  
     351      $contextid = context_user::instance($USER->id)->id;
     352  
     353      $fs = get_file_storage();
     354      $draftitemid = rand(1, 999999999);
     355      while ($files = $fs->get_area_files($contextid, 'user', 'draft', $draftitemid)) {
     356          $draftitemid = rand(1, 999999999);
     357      }
     358  
     359      return $draftitemid;
     360  }
     361  
     362  /**
     363   * Initialise a draft file area from a real one by copying the files. A draft
     364   * area will be created if one does not already exist. Normally you should
     365   * get $draftitemid by calling file_get_submitted_draft_itemid('elementname');
     366   *
     367   * @category files
     368   * @global stdClass $CFG
     369   * @global stdClass $USER
     370   * @param int $draftitemid the id of the draft area to use, or 0 to create a new one, in which case this parameter is updated.
     371   * @param int $contextid This parameter and the next two identify the file area to copy files from.
     372   * @param string $component
     373   * @param string $filearea helps indentify the file area.
     374   * @param int $itemid helps identify the file area. Can be null if there are no files yet.
     375   * @param array $options text and file options ('subdirs'=>false, 'forcehttps'=>false)
     376   * @param string $text some html content that needs to have embedded links rewritten to point to the draft area.
     377   * @return string|null returns string if $text was passed in, the rewritten $text is returned. Otherwise NULL.
     378   */
     379  function file_prepare_draft_area(&$draftitemid, $contextid, $component, $filearea, $itemid, array $options=null, $text=null) {
     380      global $CFG, $USER, $CFG;
     381  
     382      $options = (array)$options;
     383      if (!isset($options['subdirs'])) {
     384          $options['subdirs'] = false;
     385      }
     386      if (!isset($options['forcehttps'])) {
     387          $options['forcehttps'] = false;
     388      }
     389  
     390      $usercontext = context_user::instance($USER->id);
     391      $fs = get_file_storage();
     392  
     393      if (empty($draftitemid)) {
     394          // create a new area and copy existing files into
     395          $draftitemid = file_get_unused_draft_itemid();
     396          $file_record = array('contextid'=>$usercontext->id, 'component'=>'user', 'filearea'=>'draft', 'itemid'=>$draftitemid);
     397          if (!is_null($itemid) and $files = $fs->get_area_files($contextid, $component, $filearea, $itemid)) {
     398              foreach ($files as $file) {
     399                  if ($file->is_directory() and $file->get_filepath() === '/') {
     400                      // we need a way to mark the age of each draft area,
     401                      // by not copying the root dir we force it to be created automatically with current timestamp
     402                      continue;
     403                  }
     404                  if (!$options['subdirs'] and ($file->is_directory() or $file->get_filepath() !== '/')) {
     405                      continue;
     406                  }
     407                  $draftfile = $fs->create_file_from_storedfile($file_record, $file);
     408                  // XXX: This is a hack for file manager (MDL-28666)
     409                  // File manager needs to know the original file information before copying
     410                  // to draft area, so we append these information in mdl_files.source field
     411                  // {@link file_storage::search_references()}
     412                  // {@link file_storage::search_references_count()}
     413                  $sourcefield = $file->get_source();
     414                  $newsourcefield = new stdClass;
     415                  $newsourcefield->source = $sourcefield;
     416                  $original = new stdClass;
     417                  $original->contextid = $contextid;
     418                  $original->component = $component;
     419                  $original->filearea  = $filearea;
     420                  $original->itemid    = $itemid;
     421                  $original->filename  = $file->get_filename();
     422                  $original->filepath  = $file->get_filepath();
     423                  $newsourcefield->original = file_storage::pack_reference($original);
     424                  $draftfile->set_source(serialize($newsourcefield));
     425                  // End of file manager hack
     426              }
     427          }
     428          if (!is_null($text)) {
     429              // at this point there should not be any draftfile links yet,
     430              // because this is a new text from database that should still contain the @@pluginfile@@ links
     431              // this happens when developers forget to post process the text
     432              $text = str_replace("\"$CFG->httpswwwroot/draftfile.php", "\"$CFG->httpswwwroot/brokenfile.php#", $text);
     433          }
     434      } else {
     435          // nothing to do
     436      }
     437  
     438      if (is_null($text)) {
     439          return null;
     440      }
     441  
     442      // relink embedded files - editor can not handle @@PLUGINFILE@@ !
     443      return file_rewrite_pluginfile_urls($text, 'draftfile.php', $usercontext->id, 'user', 'draft', $draftitemid, $options);
     444  }
     445  
     446  /**
     447   * Convert encoded URLs in $text from the @@PLUGINFILE@@/... form to an actual URL.
     448   *
     449   * @category files
     450   * @global stdClass $CFG
     451   * @param string $text The content that may contain ULRs in need of rewriting.
     452   * @param string $file The script that should be used to serve these files. pluginfile.php, draftfile.php, etc.
     453   * @param int $contextid This parameter and the next two identify the file area to use.
     454   * @param string $component
     455   * @param string $filearea helps identify the file area.
     456   * @param int $itemid helps identify the file area.
     457   * @param array $options text and file options ('forcehttps'=>false)
     458   * @return string the processed text.
     459   */
     460  function file_rewrite_pluginfile_urls($text, $file, $contextid, $component, $filearea, $itemid, array $options=null) {
     461      global $CFG;
     462  
     463      $options = (array)$options;
     464      if (!isset($options['forcehttps'])) {
     465          $options['forcehttps'] = false;
     466      }
     467  
     468      if (!$CFG->slasharguments) {
     469          $file = $file . '?file=';
     470      }
     471  
     472      $baseurl = "$CFG->wwwroot/$file/$contextid/$component/$filearea/";
     473  
     474      if ($itemid !== null) {
     475          $baseurl .= "$itemid/";
     476      }
     477  
     478      if ($options['forcehttps']) {
     479          $baseurl = str_replace('http://', 'https://', $baseurl);
     480      }
     481  
     482      return str_replace('@@PLUGINFILE@@/', $baseurl, $text);
     483  }
     484  
     485  /**
     486   * Returns information about files in a draft area.
     487   *
     488   * @global stdClass $CFG
     489   * @global stdClass $USER
     490   * @param int $draftitemid the draft area item id.
     491   * @param string $filepath path to the directory from which the information have to be retrieved.
     492   * @return array with the following entries:
     493   *      'filecount' => number of files in the draft area.
     494   *      'filesize' => total size of the files in the draft area.
     495   *      'foldercount' => number of folders in the draft area.
     496   *      'filesize_without_references' => total size of the area excluding file references.
     497   * (more information will be added as needed).
     498   */
     499  function file_get_draft_area_info($draftitemid, $filepath = '/') {
     500      global $CFG, $USER;
     501  
     502      $usercontext = context_user::instance($USER->id);
     503      $fs = get_file_storage();
     504  
     505      $results = array(
     506          'filecount' => 0,
     507          'foldercount' => 0,
     508          'filesize' => 0,
     509          'filesize_without_references' => 0
     510      );
     511  
     512      if ($filepath != '/') {
     513          $draftfiles = $fs->get_directory_files($usercontext->id, 'user', 'draft', $draftitemid, $filepath, true, true);
     514      } else {
     515          $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid, 'id', true);
     516      }
     517      foreach ($draftfiles as $file) {
     518          if ($file->is_directory()) {
     519              $results['foldercount'] += 1;
     520          } else {
     521              $results['filecount'] += 1;
     522          }
     523  
     524          $filesize = $file->get_filesize();
     525          $results['filesize'] += $filesize;
     526          if (!$file->is_external_file()) {
     527              $results['filesize_without_references'] += $filesize;
     528          }
     529      }
     530  
     531      return $results;
     532  }
     533  
     534  /**
     535   * Returns whether a draft area has exceeded/will exceed its size limit.
     536   *
     537   * Please note that the unlimited value for $areamaxbytes is -1 {@link FILE_AREA_MAX_BYTES_UNLIMITED}, not 0.
     538   *
     539   * @param int $draftitemid the draft area item id.
     540   * @param int $areamaxbytes the maximum size allowed in this draft area.
     541   * @param int $newfilesize the size that would be added to the current area.
     542   * @param bool $includereferences true to include the size of the references in the area size.
     543   * @return bool true if the area will/has exceeded its limit.
     544   * @since Moodle 2.4
     545   */
     546  function file_is_draft_area_limit_reached($draftitemid, $areamaxbytes, $newfilesize = 0, $includereferences = false) {
     547      if ($areamaxbytes != FILE_AREA_MAX_BYTES_UNLIMITED) {
     548          $draftinfo = file_get_draft_area_info($draftitemid);
     549          $areasize = $draftinfo['filesize_without_references'];
     550          if ($includereferences) {
     551              $areasize = $draftinfo['filesize'];
     552          }
     553          if ($areasize + $newfilesize > $areamaxbytes) {
     554              return true;
     555          }
     556      }
     557      return false;
     558  }
     559  
     560  /**
     561   * Get used space of files
     562   * @global moodle_database $DB
     563   * @global stdClass $USER
     564   * @return int total bytes
     565   */
     566  function file_get_user_used_space() {
     567      global $DB, $USER;
     568  
     569      $usercontext = context_user::instance($USER->id);
     570      $sql = "SELECT SUM(files1.filesize) AS totalbytes FROM {files} files1
     571              JOIN (SELECT contenthash, filename, MAX(id) AS id
     572              FROM {files}
     573              WHERE contextid = ? AND component = ? AND filearea != ?
     574              GROUP BY contenthash, filename) files2 ON files1.id = files2.id";
     575      $params = array('contextid'=>$usercontext->id, 'component'=>'user', 'filearea'=>'draft');
     576      $record = $DB->get_record_sql($sql, $params);
     577      return (int)$record->totalbytes;
     578  }
     579  
     580  /**
     581   * Convert any string to a valid filepath
     582   * @todo review this function
     583   * @param string $str
     584   * @return string path
     585   */
     586  function file_correct_filepath($str) { //TODO: what is this? (skodak) - No idea (Fred)
     587      if ($str == '/' or empty($str)) {
     588          return '/';
     589      } else {
     590          return '/'.trim($str, '/').'/';
     591      }
     592  }
     593  
     594  /**
     595   * Generate a folder tree of draft area of current USER recursively
     596   *
     597   * @todo MDL-31073 use normal return value instead, this does not fit the rest of api here (skodak)
     598   * @param int $draftitemid
     599   * @param string $filepath
     600   * @param mixed $data
     601   */
     602  function file_get_drafarea_folders($draftitemid, $filepath, &$data) {
     603      global $USER, $OUTPUT, $CFG;
     604      $data->children = array();
     605      $context = context_user::instance($USER->id);
     606      $fs = get_file_storage();
     607      if ($files = $fs->get_directory_files($context->id, 'user', 'draft', $draftitemid, $filepath, false)) {
     608          foreach ($files as $file) {
     609              if ($file->is_directory()) {
     610                  $item = new stdClass();
     611                  $item->sortorder = $file->get_sortorder();
     612                  $item->filepath = $file->get_filepath();
     613  
     614                  $foldername = explode('/', trim($item->filepath, '/'));
     615                  $item->fullname = trim(array_pop($foldername), '/');
     616  
     617                  $item->id = uniqid();
     618                  file_get_drafarea_folders($draftitemid, $item->filepath, $item);
     619                  $data->children[] = $item;
     620              } else {
     621                  continue;
     622              }
     623          }
     624      }
     625  }
     626  
     627  /**
     628   * Listing all files (including folders) in current path (draft area)
     629   * used by file manager
     630   * @param int $draftitemid
     631   * @param string $filepath
     632   * @return stdClass
     633   */
     634  function file_get_drafarea_files($draftitemid, $filepath = '/') {
     635      global $USER, $OUTPUT, $CFG;
     636  
     637      $context = context_user::instance($USER->id);
     638      $fs = get_file_storage();
     639  
     640      $data = new stdClass();
     641      $data->path = array();
     642      $data->path[] = array('name'=>get_string('files'), 'path'=>'/');
     643  
     644      // will be used to build breadcrumb
     645      $trail = '/';
     646      if ($filepath !== '/') {
     647          $filepath = file_correct_filepath($filepath);
     648          $parts = explode('/', $filepath);
     649          foreach ($parts as $part) {
     650              if ($part != '' && $part != null) {
     651                  $trail .= ($part.'/');
     652                  $data->path[] = array('name'=>$part, 'path'=>$trail);
     653              }
     654          }
     655      }
     656  
     657      $list = array();
     658      $maxlength = 12;
     659      if ($files = $fs->get_directory_files($context->id, 'user', 'draft', $draftitemid, $filepath, false)) {
     660          foreach ($files as $file) {
     661              $item = new stdClass();
     662              $item->filename = $file->get_filename();
     663              $item->filepath = $file->get_filepath();
     664              $item->fullname = trim($item->filename, '/');
     665              $filesize = $file->get_filesize();
     666              $item->size = $filesize ? $filesize : null;
     667              $item->filesize = $filesize ? display_size($filesize) : '';
     668  
     669              $item->sortorder = $file->get_sortorder();
     670              $item->author = $file->get_author();
     671              $item->license = $file->get_license();
     672              $item->datemodified = $file->get_timemodified();
     673              $item->datecreated = $file->get_timecreated();
     674              $item->isref = $file->is_external_file();
     675              if ($item->isref && $file->get_status() == 666) {
     676                  $item->originalmissing = true;
     677              }
     678              // find the file this draft file was created from and count all references in local
     679              // system pointing to that file
     680              $source = @unserialize($file->get_source());
     681              if (isset($source->original)) {
     682                  $item->refcount = $fs->search_references_count($source->original);
     683              }
     684  
     685              if ($file->is_directory()) {
     686                  $item->filesize = 0;
     687                  $item->icon = $OUTPUT->pix_url(file_folder_icon(24))->out(false);
     688                  $item->type = 'folder';
     689                  $foldername = explode('/', trim($item->filepath, '/'));
     690                  $item->fullname = trim(array_pop($foldername), '/');
     691                  $item->thumbnail = $OUTPUT->pix_url(file_folder_icon(90))->out(false);
     692              } else {
     693                  // do NOT use file browser here!
     694                  $item->mimetype = get_mimetype_description($file);
     695                  if (file_extension_in_typegroup($file->get_filename(), 'archive')) {
     696                      $item->type = 'zip';
     697                  } else {
     698                      $item->type = 'file';
     699                  }
     700                  $itemurl = moodle_url::make_draftfile_url($draftitemid, $item->filepath, $item->filename);
     701                  $item->url = $itemurl->out();
     702                  $item->icon = $OUTPUT->pix_url(file_file_icon($file, 24))->out(false);
     703                  $item->thumbnail = $OUTPUT->pix_url(file_file_icon($file, 90))->out(false);
     704                  if ($imageinfo = $file->get_imageinfo()) {
     705                      $item->realthumbnail = $itemurl->out(false, array('preview' => 'thumb', 'oid' => $file->get_timemodified()));
     706                      $item->realicon = $itemurl->out(false, array('preview' => 'tinyicon', 'oid' => $file->get_timemodified()));
     707                      $item->image_width = $imageinfo['width'];
     708                      $item->image_height = $imageinfo['height'];
     709                  }
     710              }
     711              $list[] = $item;
     712          }
     713      }
     714      $data->itemid = $draftitemid;
     715      $data->list = $list;
     716      return $data;
     717  }
     718  
     719  /**
     720   * Returns draft area itemid for a given element.
     721   *
     722   * @category files
     723   * @param string $elname name of formlib editor element, or a hidden form field that stores the draft area item id, etc.
     724   * @return int the itemid, or 0 if there is not one yet.
     725   */
     726  function file_get_submitted_draft_itemid($elname) {
     727      // this is a nasty hack, ideally all new elements should use arrays here or there should be a new parameter
     728      if (!isset($_REQUEST[$elname])) {
     729          return 0;
     730      }
     731      if (is_array($_REQUEST[$elname])) {
     732          $param = optional_param_array($elname, 0, PARAM_INT);
     733          if (!empty($param['itemid'])) {
     734              $param = $param['itemid'];
     735          } else {
     736              debugging('Missing itemid, maybe caused by unset maxfiles option', DEBUG_DEVELOPER);
     737              return false;
     738          }
     739  
     740      } else {
     741          $param = optional_param($elname, 0, PARAM_INT);
     742      }
     743  
     744      if ($param) {
     745          require_sesskey();
     746      }
     747  
     748      return $param;
     749  }
     750  
     751  /**
     752   * Restore the original source field from draft files
     753   *
     754   * Do not use this function because it makes field files.source inconsistent
     755   * for draft area files. This function will be deprecated in 2.6
     756   *
     757   * @param stored_file $storedfile This only works with draft files
     758   * @return stored_file
     759   */
     760  function file_restore_source_field_from_draft_file($storedfile) {
     761      $source = @unserialize($storedfile->get_source());
     762      if (!empty($source)) {
     763          if (is_object($source)) {
     764              $restoredsource = $source->source;
     765              $storedfile->set_source($restoredsource);
     766          } else {
     767              throw new moodle_exception('invalidsourcefield', 'error');
     768          }
     769      }
     770      return $storedfile;
     771  }
     772  /**
     773   * Saves files from a draft file area to a real one (merging the list of files).
     774   * Can rewrite URLs in some content at the same time if desired.
     775   *
     776   * @category files
     777   * @global stdClass $USER
     778   * @param int $draftitemid the id of the draft area to use. Normally obtained
     779   *      from file_get_submitted_draft_itemid('elementname') or similar.
     780   * @param int $contextid This parameter and the next two identify the file area to save to.
     781   * @param string $component
     782   * @param string $filearea indentifies the file area.
     783   * @param int $itemid helps identifies the file area.
     784   * @param array $options area options (subdirs=>false, maxfiles=-1, maxbytes=0)
     785   * @param string $text some html content that needs to have embedded links rewritten
     786   *      to the @@PLUGINFILE@@ form for saving in the database.
     787   * @param bool $forcehttps force https urls.
     788   * @return string|null if $text was passed in, the rewritten $text is returned. Otherwise NULL.
     789   */
     790  function file_save_draft_area_files($draftitemid, $contextid, $component, $filearea, $itemid, array $options=null, $text=null, $forcehttps=false) {
     791      global $USER;
     792  
     793      $usercontext = context_user::instance($USER->id);
     794      $fs = get_file_storage();
     795  
     796      $options = (array)$options;
     797      if (!isset($options['subdirs'])) {
     798          $options['subdirs'] = false;
     799      }
     800      if (!isset($options['maxfiles'])) {
     801          $options['maxfiles'] = -1; // unlimited
     802      }
     803      if (!isset($options['maxbytes']) || $options['maxbytes'] == USER_CAN_IGNORE_FILE_SIZE_LIMITS) {
     804          $options['maxbytes'] = 0; // unlimited
     805      }
     806      if (!isset($options['areamaxbytes'])) {
     807          $options['areamaxbytes'] = FILE_AREA_MAX_BYTES_UNLIMITED; // Unlimited.
     808      }
     809      $allowreferences = true;
     810      if (isset($options['return_types']) && !($options['return_types'] & FILE_REFERENCE)) {
     811          // we assume that if $options['return_types'] is NOT specified, we DO allow references.
     812          // this is not exactly right. BUT there are many places in code where filemanager options
     813          // are not passed to file_save_draft_area_files()
     814          $allowreferences = false;
     815      }
     816  
     817      // Check if the draft area has exceeded the authorised limit. This should never happen as validation
     818      // should have taken place before, unless the user is doing something nauthly. If so, let's just not save
     819      // anything at all in the next area.
     820      if (file_is_draft_area_limit_reached($draftitemid, $options['areamaxbytes'])) {
     821          return null;
     822      }
     823  
     824      $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid, 'id');
     825      $oldfiles   = $fs->get_area_files($contextid, $component, $filearea, $itemid, 'id');
     826  
     827      // One file in filearea means it is empty (it has only top-level directory '.').
     828      if (count($draftfiles) > 1 || count($oldfiles) > 1) {
     829          // we have to merge old and new files - we want to keep file ids for files that were not changed
     830          // we change time modified for all new and changed files, we keep time created as is
     831  
     832          $newhashes = array();
     833          $filecount = 0;
     834          foreach ($draftfiles as $file) {
     835              if (!$options['subdirs'] && $file->get_filepath() !== '/') {
     836                  continue;
     837              }
     838              if (!$allowreferences && $file->is_external_file()) {
     839                  continue;
     840              }
     841              if (!$file->is_directory()) {
     842                  if ($options['maxbytes'] and $options['maxbytes'] < $file->get_filesize()) {
     843                      // oversized file - should not get here at all
     844                      continue;
     845                  }
     846                  if ($options['maxfiles'] != -1 and $options['maxfiles'] <= $filecount) {
     847                      // more files - should not get here at all
     848                      continue;
     849                  }
     850                  $filecount++;
     851              }
     852              $newhash = $fs->get_pathname_hash($contextid, $component, $filearea, $itemid, $file->get_filepath(), $file->get_filename());
     853              $newhashes[$newhash] = $file;
     854          }
     855  
     856          // Loop through oldfiles and decide which we need to delete and which to update.
     857          // After this cycle the array $newhashes will only contain the files that need to be added.
     858          foreach ($oldfiles as $oldfile) {
     859              $oldhash = $oldfile->get_pathnamehash();
     860              if (!isset($newhashes[$oldhash])) {
     861                  // delete files not needed any more - deleted by user
     862                  $oldfile->delete();
     863                  continue;
     864              }
     865  
     866              $newfile = $newhashes[$oldhash];
     867              // Now we know that we have $oldfile and $newfile for the same path.
     868              // Let's check if we can update this file or we need to delete and create.
     869              if ($newfile->is_directory()) {
     870                  // Directories are always ok to just update.
     871              } else if (($source = @unserialize($newfile->get_source())) && isset($source->original)) {
     872                  // File has the 'original' - we need to update the file (it may even have not been changed at all).
     873                  $original = file_storage::unpack_reference($source->original);
     874                  if ($original['filename'] !== $oldfile->get_filename() || $original['filepath'] !== $oldfile->get_filepath()) {
     875                      // Very odd, original points to another file. Delete and create file.
     876                      $oldfile->delete();
     877                      continue;
     878                  }
     879              } else {
     880                  // The same file name but absence of 'original' means that file was deteled and uploaded again.
     881                  // By deleting and creating new file we properly manage all existing references.
     882                  $oldfile->delete();
     883                  continue;
     884              }
     885  
     886              // status changed, we delete old file, and create a new one
     887              if ($oldfile->get_status() != $newfile->get_status()) {
     888                  // file was changed, use updated with new timemodified data
     889                  $oldfile->delete();
     890                  // This file will be added later
     891                  continue;
     892              }
     893  
     894              // Updated author
     895              if ($oldfile->get_author() != $newfile->get_author()) {
     896                  $oldfile->set_author($newfile->get_author());
     897              }
     898              // Updated license
     899              if ($oldfile->get_license() != $newfile->get_license()) {
     900                  $oldfile->set_license($newfile->get_license());
     901              }
     902  
     903              // Updated file source
     904              // Field files.source for draftarea files contains serialised object with source and original information.
     905              // We only store the source part of it for non-draft file area.
     906              $newsource = $newfile->get_source();
     907              if ($source = @unserialize($newfile->get_source())) {
     908                  $newsource = $source->source;
     909              }
     910              if ($oldfile->get_source() !== $newsource) {
     911                  $oldfile->set_source($newsource);
     912              }
     913  
     914              // Updated sort order
     915              if ($oldfile->get_sortorder() != $newfile->get_sortorder()) {
     916                  $oldfile->set_sortorder($newfile->get_sortorder());
     917              }
     918  
     919              // Update file timemodified
     920              if ($oldfile->get_timemodified() != $newfile->get_timemodified()) {
     921                  $oldfile->set_timemodified($newfile->get_timemodified());
     922              }
     923  
     924              // Replaced file content
     925              if (!$oldfile->is_directory() &&
     926                      ($oldfile->get_contenthash() != $newfile->get_contenthash() ||
     927                      $oldfile->get_filesize() != $newfile->get_filesize() ||
     928                      $oldfile->get_referencefileid() != $newfile->get_referencefileid() ||
     929                      $oldfile->get_userid() != $newfile->get_userid())) {
     930                  $oldfile->replace_file_with($newfile);
     931              }
     932  
     933              // unchanged file or directory - we keep it as is
     934              unset($newhashes[$oldhash]);
     935          }
     936  
     937          // Add fresh file or the file which has changed status
     938          // the size and subdirectory tests are extra safety only, the UI should prevent it
     939          foreach ($newhashes as $file) {
     940              $file_record = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea, 'itemid'=>$itemid, 'timemodified'=>time());
     941              if ($source = @unserialize($file->get_source())) {
     942                  // Field files.source for draftarea files contains serialised object with source and original information.
     943                  // We only store the source part of it for non-draft file area.
     944                  $file_record['source'] = $source->source;
     945              }
     946  
     947              if ($file->is_external_file()) {
     948                  $repoid = $file->get_repository_id();
     949                  if (!empty($repoid)) {
     950                      $file_record['repositoryid'] = $repoid;
     951                      $file_record['reference'] = $file->get_reference();
     952                  }
     953              }
     954  
     955              $fs->create_file_from_storedfile($file_record, $file);
     956          }
     957      }
     958  
     959      // note: do not purge the draft area - we clean up areas later in cron,
     960      //       the reason is that user might press submit twice and they would loose the files,
     961      //       also sometimes we might want to use hacks that save files into two different areas
     962  
     963      if (is_null($text)) {
     964          return null;
     965      } else {
     966          return file_rewrite_urls_to_pluginfile($text, $draftitemid, $forcehttps);
     967      }
     968  }
     969  
     970  /**
     971   * Convert the draft file area URLs in some content to @@PLUGINFILE@@ tokens
     972   * ready to be saved in the database. Normally, this is done automatically by
     973   * {@link file_save_draft_area_files()}.
     974   *
     975   * @category files
     976   * @param string $text the content to process.
     977   * @param int $draftitemid the draft file area the content was using.
     978   * @param bool $forcehttps whether the content contains https URLs. Default false.
     979   * @return string the processed content.
     980   */
     981  function file_rewrite_urls_to_pluginfile($text, $draftitemid, $forcehttps = false) {
     982      global $CFG, $USER;
     983  
     984      $usercontext = context_user::instance($USER->id);
     985  
     986      $wwwroot = $CFG->wwwroot;
     987      if ($forcehttps) {
     988          $wwwroot = str_replace('http://', 'https://', $wwwroot);
     989      }
     990  
     991      // relink embedded files if text submitted - no absolute links allowed in database!
     992      $text = str_ireplace("$wwwroot/draftfile.php/$usercontext->id/user/draft/$draftitemid/", '@@PLUGINFILE@@/', $text);
     993  
     994      if (strpos($text, 'draftfile.php?file=') !== false) {
     995          $matches = array();
     996          preg_match_all("!$wwwroot/draftfile.php\?file=%2F{$usercontext->id}%2Fuser%2Fdraft%2F{$draftitemid}%2F[^'\",&<>|`\s:\\\\]+!iu", $text, $matches);
     997          if ($matches) {
     998              foreach ($matches[0] as $match) {
     999                  $replace = str_ireplace('%2F', '/', $match);
    1000                  $text = str_replace($match, $replace, $text);
    1001              }
    1002          }
    1003          $text = str_ireplace("$wwwroot/draftfile.php?file=/$usercontext->id/user/draft/$draftitemid/", '@@PLUGINFILE@@/', $text);
    1004      }
    1005  
    1006      return $text;
    1007  }
    1008  
    1009  /**
    1010   * Set file sort order
    1011   *
    1012   * @global moodle_database $DB
    1013   * @param int $contextid the context id
    1014   * @param string $component file component
    1015   * @param string $filearea file area.
    1016   * @param int $itemid itemid.
    1017   * @param string $filepath file path.
    1018   * @param string $filename file name.
    1019   * @param int $sortorder the sort order of file.
    1020   * @return bool
    1021   */
    1022  function file_set_sortorder($contextid, $component, $filearea, $itemid, $filepath, $filename, $sortorder) {
    1023      global $DB;
    1024      $conditions = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea, 'itemid'=>$itemid, 'filepath'=>$filepath, 'filename'=>$filename);
    1025      if ($file_record = $DB->get_record('files', $conditions)) {
    1026          $sortorder = (int)$sortorder;
    1027          $file_record->sortorder = $sortorder;
    1028          $DB->update_record('files', $file_record);
    1029          return true;
    1030      }
    1031      return false;
    1032  }
    1033  
    1034  /**
    1035   * reset file sort order number to 0
    1036   * @global moodle_database $DB
    1037   * @param int $contextid the context id
    1038   * @param string $component
    1039   * @param string $filearea file area.
    1040   * @param int|bool $itemid itemid.
    1041   * @return bool
    1042   */
    1043  function file_reset_sortorder($contextid, $component, $filearea, $itemid=false) {
    1044      global $DB;
    1045  
    1046      $conditions = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea);
    1047      if ($itemid !== false) {
    1048          $conditions['itemid'] = $itemid;
    1049      }
    1050  
    1051      $file_records = $DB->get_records('files', $conditions);
    1052      foreach ($file_records as $file_record) {
    1053          $file_record->sortorder = 0;
    1054          $DB->update_record('files', $file_record);
    1055      }
    1056      return true;
    1057  }
    1058  
    1059  /**
    1060   * Returns description of upload error
    1061   *
    1062   * @param int $errorcode found in $_FILES['filename.ext']['error']
    1063   * @return string error description string, '' if ok
    1064   */
    1065  function file_get_upload_error($errorcode) {
    1066  
    1067      switch ($errorcode) {
    1068      case 0: // UPLOAD_ERR_OK - no error
    1069          $errmessage = '';
    1070          break;
    1071  
    1072      case 1: // UPLOAD_ERR_INI_SIZE
    1073          $errmessage = get_string('uploadserverlimit');
    1074          break;
    1075  
    1076      case 2: // UPLOAD_ERR_FORM_SIZE
    1077          $errmessage = get_string('uploadformlimit');
    1078          break;
    1079  
    1080      case 3: // UPLOAD_ERR_PARTIAL
    1081          $errmessage = get_string('uploadpartialfile');
    1082          break;
    1083  
    1084      case 4: // UPLOAD_ERR_NO_FILE
    1085          $errmessage = get_string('uploadnofilefound');
    1086          break;
    1087  
    1088      // Note: there is no error with a value of 5
    1089  
    1090      case 6: // UPLOAD_ERR_NO_TMP_DIR
    1091          $errmessage = get_string('uploadnotempdir');
    1092          break;
    1093  
    1094      case 7: // UPLOAD_ERR_CANT_WRITE
    1095          $errmessage = get_string('uploadcantwrite');
    1096          break;
    1097  
    1098      case 8: // UPLOAD_ERR_EXTENSION
    1099          $errmessage = get_string('uploadextension');
    1100          break;
    1101  
    1102      default:
    1103          $errmessage = get_string('uploadproblem');
    1104      }
    1105  
    1106      return $errmessage;
    1107  }
    1108  
    1109  /**
    1110   * Recursive function formating an array in POST parameter
    1111   * @param array $arraydata - the array that we are going to format and add into &$data array
    1112   * @param string $currentdata - a row of the final postdata array at instant T
    1113   *                when finish, it's assign to $data under this format: name[keyname][][]...[]='value'
    1114   * @param array $data - the final data array containing all POST parameters : 1 row = 1 parameter
    1115   */
    1116  function format_array_postdata_for_curlcall($arraydata, $currentdata, &$data) {
    1117          foreach ($arraydata as $k=>$v) {
    1118              $newcurrentdata = $currentdata;
    1119              if (is_array($v)) { //the value is an array, call the function recursively
    1120                  $newcurrentdata = $newcurrentdata.'['.urlencode($k).']';
    1121                  format_array_postdata_for_curlcall($v, $newcurrentdata, $data);
    1122              }  else { //add the POST parameter to the $data array
    1123                  $data[] = $newcurrentdata.'['.urlencode($k).']='.urlencode($v);
    1124              }
    1125          }
    1126  }
    1127  
    1128  /**
    1129   * Transform a PHP array into POST parameter
    1130   * (see the recursive function format_array_postdata_for_curlcall)
    1131   * @param array $postdata
    1132   * @return array containing all POST parameters  (1 row = 1 POST parameter)
    1133   */
    1134  function format_postdata_for_curlcall($postdata) {
    1135          $data = array();
    1136          foreach ($postdata as $k=>$v) {
    1137              if (is_array($v)) {
    1138                  $currentdata = urlencode($k);
    1139                  format_array_postdata_for_curlcall($v, $currentdata, $data);
    1140              }  else {
    1141                  $data[] = urlencode($k).'='.urlencode($v);
    1142              }
    1143          }
    1144          $convertedpostdata = implode('&', $data);
    1145          return $convertedpostdata;
    1146  }
    1147  
    1148  /**
    1149   * Fetches content of file from Internet (using proxy if defined). Uses cURL extension if present.
    1150   * Due to security concerns only downloads from http(s) sources are supported.
    1151   *
    1152   * @category files
    1153   * @param string $url file url starting with http(s)://
    1154   * @param array $headers http headers, null if none. If set, should be an
    1155   *   associative array of header name => value pairs.
    1156   * @param array $postdata array means use POST request with given parameters
    1157   * @param bool $fullresponse return headers, responses, etc in a similar way snoopy does
    1158   *   (if false, just returns content)
    1159   * @param int $timeout timeout for complete download process including all file transfer
    1160   *   (default 5 minutes)
    1161   * @param int $connecttimeout timeout for connection to server; this is the timeout that
    1162   *   usually happens if the remote server is completely down (default 20 seconds);
    1163   *   may not work when using proxy
    1164   * @param bool $skipcertverify If true, the peer's SSL certificate will not be checked.
    1165   *   Only use this when already in a trusted location.
    1166   * @param string $tofile store the downloaded content to file instead of returning it.
    1167   * @param bool $calctimeout false by default, true enables an extra head request to try and determine
    1168   *   filesize and appropriately larger timeout based on $CFG->curltimeoutkbitrate
    1169   * @return stdClass|string|bool stdClass object if $fullresponse is true, false if request failed, true
    1170   *   if file downloaded into $tofile successfully or the file content as a string.
    1171   */
    1172  function download_file_content($url, $headers=null, $postdata=null, $fullresponse=false, $timeout=300, $connecttimeout=20, $skipcertverify=false, $tofile=NULL, $calctimeout=false) {
    1173      global $CFG;
    1174  
    1175      // Only http and https links supported.
    1176      if (!preg_match('|^https?://|i', $url)) {
    1177          if ($fullresponse) {
    1178              $response = new stdClass();
    1179              $response->status        = 0;
    1180              $response->headers       = array();
    1181              $response->response_code = 'Invalid protocol specified in url';
    1182              $response->results       = '';
    1183              $response->error         = 'Invalid protocol specified in url';
    1184              return $response;
    1185          } else {
    1186              return false;
    1187          }
    1188      }
    1189  
    1190      $options = array();
    1191  
    1192      $headers2 = array();
    1193      if (is_array($headers)) {
    1194          foreach ($headers as $key => $value) {
    1195              if (is_numeric($key)) {
    1196                  $headers2[] = $value;
    1197              } else {
    1198                  $headers2[] = "$key: $value";
    1199              }
    1200          }
    1201      }
    1202  
    1203      if ($skipcertverify) {
    1204          $options['CURLOPT_SSL_VERIFYPEER'] = false;
    1205      } else {
    1206          $options['CURLOPT_SSL_VERIFYPEER'] = true;
    1207      }
    1208  
    1209      $options['CURLOPT_CONNECTTIMEOUT'] = $connecttimeout;
    1210  
    1211      $options['CURLOPT_FOLLOWLOCATION'] = 1;
    1212      $options['CURLOPT_MAXREDIRS'] = 5;
    1213  
    1214      // Use POST if requested.
    1215      if (is_array($postdata)) {
    1216          $postdata = format_postdata_for_curlcall($postdata);
    1217      } else if (empty($postdata)) {
    1218          $postdata = null;
    1219      }
    1220  
    1221      // Optionally attempt to get more correct timeout by fetching the file size.
    1222      if (!isset($CFG->curltimeoutkbitrate)) {
    1223          // Use very slow rate of 56kbps as a timeout speed when not set.
    1224          $bitrate = 56;
    1225      } else {
    1226          $bitrate = $CFG->curltimeoutkbitrate;
    1227      }
    1228      if ($calctimeout and !isset($postdata)) {
    1229          $curl = new curl();
    1230          $curl->setHeader($headers2);
    1231  
    1232          $curl->head($url, $postdata, $options);
    1233  
    1234          $info = $curl->get_info();
    1235          $error_no = $curl->get_errno();
    1236          if (!$error_no && $info['download_content_length'] > 0) {
    1237              // No curl errors - adjust for large files only - take max timeout.
    1238              $timeout = max($timeout, ceil($info['download_content_length'] * 8 / ($bitrate * 1024)));
    1239          }
    1240      }
    1241  
    1242      $curl = new curl();
    1243      $curl->setHeader($headers2);
    1244  
    1245      $options['CURLOPT_RETURNTRANSFER'] = true;
    1246      $options['CURLOPT_NOBODY'] = false;
    1247      $options['CURLOPT_TIMEOUT'] = $timeout;
    1248  
    1249      if ($tofile) {
    1250          $fh = fopen($tofile, 'w');
    1251          if (!$fh) {
    1252              if ($fullresponse) {
    1253                  $response = new stdClass();
    1254                  $response->status        = 0;
    1255                  $response->headers       = array();
    1256                  $response->response_code = 'Can not write to file';
    1257                  $response->results       = false;
    1258                  $response->error         = 'Can not write to file';
    1259                  return $response;
    1260              } else {
    1261                  return false;
    1262              }
    1263          }
    1264          $options['CURLOPT_FILE'] = $fh;
    1265      }
    1266  
    1267      if (isset($postdata)) {
    1268          $content = $curl->post($url, $postdata, $options);
    1269      } else {
    1270          $content = $curl->get($url, null, $options);
    1271      }
    1272  
    1273      if ($tofile) {
    1274          fclose($fh);
    1275          @chmod($tofile, $CFG->filepermissions);
    1276      }
    1277  
    1278  /*
    1279      // Try to detect encoding problems.
    1280      if ((curl_errno($ch) == 23 or curl_errno($ch) == 61) and defined('CURLOPT_ENCODING')) {
    1281          curl_setopt($ch, CURLOPT_ENCODING, 'none');
    1282          $result = curl_exec($ch);
    1283      }
    1284  */
    1285  
    1286      $info       = $curl->get_info();
    1287      $error_no   = $curl->get_errno();
    1288      $rawheaders = $curl->get_raw_response();
    1289  
    1290      if ($error_no) {
    1291          $error = $content;
    1292          if (!$fullresponse) {
    1293              debugging("cURL request for \"$url\" failed with: $error ($error_no)", DEBUG_ALL);
    1294              return false;
    1295          }
    1296  
    1297          $response = new stdClass();
    1298          if ($error_no == 28) {
    1299              $response->status    = '-100'; // Mimic snoopy.
    1300          } else {
    1301              $response->status    = '0';
    1302          }
    1303          $response->headers       = array();
    1304          $response->response_code = $error;
    1305          $response->results       = false;
    1306          $response->error         = $error;
    1307          return $response;
    1308      }
    1309  
    1310      if ($tofile) {
    1311          $content = true;
    1312      }
    1313  
    1314      if (empty($info['http_code'])) {
    1315          // For security reasons we support only true http connections (Location: file:// exploit prevention).
    1316          $response = new stdClass();
    1317          $response->status        = '0';
    1318          $response->headers       = array();
    1319          $response->response_code = 'Unknown cURL error';
    1320          $response->results       = false; // do NOT change this, we really want to ignore the result!
    1321          $response->error         = 'Unknown cURL error';
    1322  
    1323      } else {
    1324          $response = new stdClass();
    1325          $response->status        = (string)$info['http_code'];
    1326          $response->headers       = $rawheaders;
    1327          $response->results       = $content;
    1328          $response->error         = '';
    1329  
    1330          // There might be multiple headers on redirect, find the status of the last one.
    1331          $firstline = true;
    1332          foreach ($rawheaders as $line) {
    1333              if ($firstline) {
    1334                  $response->response_code = $line;
    1335                  $firstline = false;
    1336              }
    1337              if (trim($line, "\r\n") === '') {
    1338                  $firstline = true;
    1339              }
    1340          }
    1341      }
    1342  
    1343      if ($fullresponse) {
    1344          return $response;
    1345      }
    1346  
    1347      if ($info['http_code'] != 200) {
    1348          debugging("cURL request for \"$url\" failed, HTTP response code: ".$response->response_code, DEBUG_ALL);
    1349          return false;
    1350      }
    1351      return $response->results;
    1352  }
    1353  
    1354  /**
    1355   * Returns a list of information about file types based on extensions.
    1356   *
    1357   * The following elements expected in value array for each extension:
    1358   * 'type' - mimetype
    1359   * 'icon' - location of the icon file. If value is FILENAME, then either pix/f/FILENAME.gif
    1360   *     or pix/f/FILENAME.png must be present in moodle and contain 16x16 filetype icon;
    1361   *     also files with bigger sizes under names
    1362   *     FILENAME-24, FILENAME-32, FILENAME-64, FILENAME-128, FILENAME-256 are recommended.
    1363   * 'groups' (optional) - array of filetype groups this filetype extension is part of;
    1364   *     commonly used in moodle the following groups:
    1365   *       - web_image - image that can be included as <img> in HTML
    1366   *       - image - image that we can parse using GD to find it's dimensions, also used for portfolio format
    1367   *       - video - file that can be imported as video in text editor
    1368   *       - audio - file that can be imported as audio in text editor
    1369   *       - archive - we can extract files from this archive
    1370   *       - spreadsheet - used for portfolio format
    1371   *       - document - used for portfolio format
    1372   *       - presentation - used for portfolio format
    1373   * 'string' (optional) - the name of the string from lang/en/mimetypes.php that displays
    1374   *     human-readable description for this filetype;
    1375   *     Function {@link get_mimetype_description()} first looks at the presence of string for
    1376   *     particular mimetype (value of 'type'), if not found looks for string specified in 'string'
    1377   *     attribute, if not found returns the value of 'type';
    1378   * 'defaulticon' (boolean, optional) - used by function {@link file_mimetype_icon()} to find
    1379   *     an icon for mimetype. If an entry with 'defaulticon' is not found for a particular mimetype,
    1380   *     this function will return first found icon; Especially usefull for types such as 'text/plain'
    1381   *
    1382   * @category files
    1383   * @return array List of information about file types based on extensions.
    1384   *   Associative array of extension (lower-case) to associative array
    1385   *   from 'element name' to data. Current element names are 'type' and 'icon'.
    1386   *   Unknown types should use the 'xxx' entry which includes defaults.
    1387   */
    1388  function &get_mimetypes_array() {
    1389      static $mimearray = array (
    1390          'xxx'  => array ('type'=>'document/unknown', 'icon'=>'unknown'),
    1391          '3gp'  => array ('type'=>'video/quicktime', 'icon'=>'quicktime', 'groups'=>array('video'), 'string'=>'video'),
    1392          '7z'  => array ('type'=>'application/x-7z-compressed', 'icon'=>'archive', 'groups'=>array('archive'), 'string'=>'archive'),
    1393          'aac'  => array ('type'=>'audio/aac', 'icon'=>'audio', 'groups'=>array('audio'), 'string'=>'audio'),
    1394          'accdb'  => array ('type'=>'application/msaccess', 'icon'=>'base'),
    1395          'ai'   => array ('type'=>'application/postscript', 'icon'=>'eps', 'groups'=>array('image'), 'string'=>'image'),
    1396          'aif'  => array ('type'=>'audio/x-aiff', 'icon'=>'audio', 'groups'=>array('audio'), 'string'=>'audio'),
    1397          'aiff' => array ('type'=>'audio/x-aiff', 'icon'=>'audio', 'groups'=>array('audio'), 'string'=>'audio'),
    1398          'aifc' => array ('type'=>'audio/x-aiff', 'icon'=>'audio', 'groups'=>array('audio'), 'string'=>'audio'),
    1399          'applescript'  => array ('type'=>'text/plain', 'icon'=>'text'),
    1400          'asc'  => array ('type'=>'text/plain', 'icon'=>'sourcecode'),
    1401          'asm'  => array ('type'=>'text/plain', 'icon'=>'sourcecode'),
    1402          'au'   => array ('type'=>'audio/au', 'icon'=>'audio', 'groups'=>array('audio'), 'string'=>'audio'),
    1403          'avi'  => array ('type'=>'video/x-ms-wm', 'icon'=>'avi', 'groups'=>array('video','web_video'), 'string'=>'video'),
    1404          'bmp'  => array ('type'=>'image/bmp', 'icon'=>'bmp', 'groups'=>array('image'), 'string'=>'image'),
    1405          'c'    => array ('type'=>'text/plain', 'icon'=>'sourcecode'),
    1406          'cct'  => array ('type'=>'shockwave/director', 'icon'=>'flash'),
    1407          'cpp'  => array ('type'=>'text/plain', 'icon'=>'sourcecode'),
    1408          'cs'   => array ('type'=>'application/x-csh', 'icon'=>'sourcecode'),
    1409          'css'  => array ('type'=>'text/css', 'icon'=>'text', 'groups'=>array('web_file')),
    1410          'csv'  => array ('type'=>'text/csv', 'icon'=>'spreadsheet', 'groups'=>array('spreadsheet')),
    1411          'dv'   => array ('type'=>'video/x-dv', 'icon'=>'quicktime', 'groups'=>array('video'), 'string'=>'video'),
    1412          'dmg'  => array ('type'=>'application/octet-stream', 'icon'=>'unknown'),
    1413  
    1414          'doc'  => array ('type'=>'application/msword', 'icon'=>'document', 'groups'=>array('document')),
    1415          'bdoc' => array ('type'=>'application/x-digidoc', 'icon'=>'document', 'groups'=>array('archive')),
    1416          'cdoc' => array ('type'=>'application/x-digidoc', 'icon'=>'document', 'groups'=>array('archive')),
    1417          'ddoc' => array ('type'=>'application/x-digidoc', 'icon'=>'document', 'groups'=>array('archive')),
    1418          'docx' => array ('type'=>'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'icon'=>'document', 'groups'=>array('document')),
    1419          'docm' => array ('type'=>'application/vnd.ms-word.document.macroEnabled.12', 'icon'=>'document'),
    1420          'dotx' => array ('type'=>'application/vnd.openxmlformats-officedocument.wordprocessingml.template', 'icon'=>'document'),
    1421          'dotm' => array ('type'=>'application/vnd.ms-word.template.macroEnabled.12', 'icon'=>'document'),
    1422  
    1423          'dcr'  => array ('type'=>'application/x-director', 'icon'=>'flash'),
    1424          'dif'  => array ('type'=>'video/x-dv', 'icon'=>'quicktime', 'groups'=>array('video'), 'string'=>'video'),
    1425          'dir'  => array ('type'=>'application/x-director', 'icon'=>'flash'),
    1426          'dxr'  => array ('type'=>'application/x-director', 'icon'=>'flash'),
    1427          'eps'  => array ('type'=>'application/postscript', 'icon'=>'eps'),
    1428          'epub' => array ('type'=>'application/epub+zip', 'icon'=>'epub', 'groups'=>array('document')),
    1429          'fdf'  => array ('type'=>'application/pdf', 'icon'=>'pdf'),
    1430          'flv'  => array ('type'=>'video/x-flv', 'icon'=>'flash', 'groups'=>array('video','web_video'), 'string'=>'video'),
    1431          'f4v'  => array ('type'=>'video/mp4', 'icon'=>'flash', 'groups'=>array('video','web_video'), 'string'=>'video'),
    1432  
    1433          'gallery'           => array ('type'=>'application/x-smarttech-notebook', 'icon'=>'archive'),
    1434          'galleryitem'       => array ('type'=>'application/x-smarttech-notebook', 'icon'=>'archive'),
    1435          'gallerycollection' => array ('type'=>'application/x-smarttech-notebook', 'icon'=>'archive'),
    1436          'gif'  => array ('type'=>'image/gif', 'icon'=>'gif', 'groups'=>array('image', 'web_image'), 'string'=>'image'),
    1437          'gtar' => array ('type'=>'application/x-gtar', 'icon'=>'archive', 'groups'=>array('archive'), 'string'=>'archive'),
    1438          'tgz'  => array ('type'=>'application/g-zip', 'icon'=>'archive', 'groups'=>array('archive'), 'string'=>'archive'),
    1439          'gz'   => array ('type'=>'application/g-zip', 'icon'=>'archive', 'groups'=>array('archive'), 'string'=>'archive'),
    1440          'gzip' => array ('type'=>'application/g-zip', 'icon'=>'archive', 'groups'=>array('archive'), 'string'=>'archive'),
    1441          'h'    => array ('type'=>'text/plain', 'icon'=>'sourcecode'),
    1442          'hpp'  => array ('type'=>'text/plain', 'icon'=>'sourcecode'),
    1443          'hqx'  => array ('type'=>'application/mac-binhex40', 'icon'=>'archive', 'groups'=>array('archive'), 'string'=>'archive'),
    1444          'htc'  => array ('type'=>'text/x-component', 'icon'=>'markup'),
    1445          'html' => array ('type'=>'text/html', 'icon'=>'html', 'groups'=>array('web_file')),
    1446          'xhtml'=> array ('type'=>'application/xhtml+xml', 'icon'=>'html', 'groups'=>array('web_file')),
    1447          'htm'  => array ('type'=>'text/html', 'icon'=>'html', 'groups'=>array('web_file')),
    1448          'ico'  => array ('type'=>'image/vnd.microsoft.icon', 'icon'=>'image', 'groups'=>array('image'), 'string'=>'image'),
    1449          'ics'  => array ('type'=>'text/calendar', 'icon'=>'text'),
    1450          'isf'  => array ('type'=>'application/inspiration', 'icon'=>'isf'),
    1451          'ist'  => array ('type'=>'application/inspiration.template', 'icon'=>'isf'),
    1452          'java' => array ('type'=>'text/plain', 'icon'=>'sourcecode'),
    1453          'jar'  => array ('type'=>'application/java-archive', 'icon' => 'archive'),
    1454          'jcb'  => array ('type'=>'text/xml', 'icon'=>'markup'),
    1455          'jcl'  => array ('type'=>'text/xml', 'icon'=>'markup'),
    1456          'jcw'  => array ('type'=>'text/xml', 'icon'=>'markup'),
    1457          'jmt'  => array ('type'=>'text/xml', 'icon'=>'markup'),
    1458          'jmx'  => array ('type'=>'text/xml', 'icon'=>'markup'),
    1459          'jnlp' => array ('type'=>'application/x-java-jnlp-file', 'icon'=>'markup'),
    1460          'jpe'  => array ('type'=>'image/jpeg', 'icon'=>'jpeg', 'groups'=>array('image', 'web_image'), 'string'=>'image'),
    1461          'jpeg' => array ('type'=>'image/jpeg', 'icon'=>'jpeg', 'groups'=>array('image', 'web_image'), 'string'=>'image'),
    1462          'jpg'  => array ('type'=>'image/jpeg', 'icon'=>'jpeg', 'groups'=>array('image', 'web_image'), 'string'=>'image'),
    1463          'jqz'  => array ('type'=>'text/xml', 'icon'=>'markup'),
    1464          'js'   => array ('type'=>'application/x-javascript', 'icon'=>'text', 'groups'=>array('web_file')),
    1465          'latex'=> array ('type'=>'application/x-latex', 'icon'=>'text'),
    1466          'm'    => array ('type'=>'text/plain', 'icon'=>'sourcecode'),
    1467          'mbz'  => array ('type'=>'application/vnd.moodle.backup', 'icon'=>'moodle'),
    1468          'mdb'  => array ('type'=>'application/x-msaccess', 'icon'=>'base'),
    1469          'mht'  => array ('type'=>'message/rfc822', 'icon'=>'archive'),
    1470          'mhtml'=> array ('type'=>'message/rfc822', 'icon'=>'archive'),
    1471          'mov'  => array ('type'=>'video/quicktime', 'icon'=>'quicktime', 'groups'=>array('video','web_video'), 'string'=>'video'),
    1472          'movie'=> array ('type'=>'video/x-sgi-movie', 'icon'=>'quicktime', 'groups'=>array('video'), 'string'=>'video'),
    1473          'mw'   => array ('type'=>'application/maple', 'icon'=>'math'),
    1474          'mws'  => array ('type'=>'application/maple', 'icon'=>'math'),
    1475          'm3u'  => array ('type'=>'audio/x-mpegurl', 'icon'=>'mp3', 'groups'=>array('audio'), 'string'=>'audio'),
    1476          'mp3'  => array ('type'=>'audio/mp3', 'icon'=>'mp3', 'groups'=>array('audio','web_audio'), 'string'=>'audio'),
    1477          'mp4'  => array ('type'=>'video/mp4', 'icon'=>'mpeg', 'groups'=>array('video','web_video'), 'string'=>'video'),
    1478          'm4v'  => array ('type'=>'video/mp4', 'icon'=>'mpeg', 'groups'=>array('video','web_video'), 'string'=>'video'),
    1479          'm4a'  => array ('type'=>'audio/mp4', 'icon'=>'mp3', 'groups'=>array('audio'), 'string'=>'audio'),
    1480          'mpeg' => array ('type'=>'video/mpeg', 'icon'=>'mpeg', 'groups'=>array('video','web_video'), 'string'=>'video'),
    1481          'mpe'  => array ('type'=>'video/mpeg', 'icon'=>'mpeg', 'groups'=>array('video','web_video'), 'string'=>'video'),
    1482          'mpg'  => array ('type'=>'video/mpeg', 'icon'=>'mpeg', 'groups'=>array('video','web_video'), 'string'=>'video'),
    1483          'mpr'  => array ('type'=>'application/vnd.moodle.profiling', 'icon'=>'moodle'),
    1484  
    1485          'nbk'       => array ('type'=>'application/x-smarttech-notebook', 'icon'=>'archive'),
    1486          'notebook'  => array ('type'=>'application/x-smarttech-notebook', 'icon'=>'archive'),
    1487  
    1488          'odt'  => array ('type'=>'application/vnd.oasis.opendocument.text', 'icon'=>'writer', 'groups'=>array('document')),
    1489          'ott'  => array ('type'=>'application/vnd.oasis.opendocument.text-template', 'icon'=>'writer', 'groups'=>array('document')),
    1490          'oth'  => array ('type'=>'application/vnd.oasis.opendocument.text-web', 'icon'=>'oth', 'groups'=>array('document')),
    1491          'odm'  => array ('type'=>'application/vnd.oasis.opendocument.text-master', 'icon'=>'writer'),
    1492          'odg'  => array ('type'=>'application/vnd.oasis.opendocument.graphics', 'icon'=>'draw'),
    1493          'otg'  => array ('type'=>'application/vnd.oasis.opendocument.graphics-template', 'icon'=>'draw'),
    1494          'odp'  => array ('type'=>'application/vnd.oasis.opendocument.presentation', 'icon'=>'impress'),
    1495          'otp'  => array ('type'=>'application/vnd.oasis.opendocument.presentation-template', 'icon'=>'impress'),
    1496          'ods'  => array ('type'=>'application/vnd.oasis.opendocument.spreadsheet', 'icon'=>'calc', 'groups'=>array('spreadsheet')),
    1497          'ots'  => array ('type'=>'application/vnd.oasis.opendocument.spreadsheet-template', 'icon'=>'calc', 'groups'=>array('spreadsheet')),
    1498          'odc'  => array ('type'=>'application/vnd.oasis.opendocument.chart', 'icon'=>'chart'),
    1499          'odf'  => array ('type'=>'application/vnd.oasis.opendocument.formula', 'icon'=>'math'),
    1500          'odb'  => array ('type'=>'application/vnd.oasis.opendocument.database', 'icon'=>'base'),
    1501          'odi'  => array ('type'=>'application/vnd.oasis.opendocument.image', 'icon'=>'draw'),
    1502          'oga'  => array ('type'=>'audio/ogg', 'icon'=>'audio', 'groups'=>array('audio'), 'string'=>'audio'),
    1503          'ogg'  => array ('type'=>'audio/ogg', 'icon'=>'audio', 'groups'=>array('audio'), 'string'=>'audio'),
    1504          'ogv'  => array ('type'=>'video/ogg', 'icon'=>'video', 'groups'=>array('video'), 'string'=>'video'),
    1505  
    1506          'pct'  => array ('type'=>'image/pict', 'icon'=>'image', 'groups'=>array('image'), 'string'=>'image'),
    1507          'pdf'  => array ('type'=>'application/pdf', 'icon'=>'pdf'),
    1508          'php'  => array ('type'=>'text/plain', 'icon'=>'sourcecode'),
    1509          'pic'  => array ('type'=>'image/pict', 'icon'=>'image', 'groups'=>array('image'), 'string'=>'image'),
    1510          'pict' => array ('type'=>'image/pict', 'icon'=>'image', 'groups'=>array('image'), 'string'=>'image'),
    1511          'png'  => array ('type'=>'image/png', 'icon'=>'png', 'groups'=>array('image', 'web_image'), 'string'=>'image'),
    1512          'pps'  => array ('type'=>'application/vnd.ms-powerpoint', 'icon'=>'powerpoint', 'groups'=>array('presentation')),
    1513          'ppt'  => array ('type'=>'application/vnd.ms-powerpoint', 'icon'=>'powerpoint', 'groups'=>array('presentation')),
    1514          'pptx' => array ('type'=>'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'icon'=>'powerpoint'),
    1515          'pptm' => array ('type'=>'application/vnd.ms-powerpoint.presentation.macroEnabled.12', 'icon'=>'powerpoint'),
    1516          'potx' => array ('type'=>'application/vnd.openxmlformats-officedocument.presentationml.template', 'icon'=>'powerpoint'),
    1517          'potm' => array ('type'=>'application/vnd.ms-powerpoint.template.macroEnabled.12', 'icon'=>'powerpoint'),
    1518          'ppam' => array ('type'=>'application/vnd.ms-powerpoint.addin.macroEnabled.12', 'icon'=>'powerpoint'),
    1519          'ppsx' => array ('type'=>'application/vnd.openxmlformats-officedocument.presentationml.slideshow', 'icon'=>'powerpoint'),
    1520          'ppsm' => array ('type'=>'application/vnd.ms-powerpoint.slideshow.macroEnabled.12', 'icon'=>'powerpoint'),
    1521          'ps'   => array ('type'=>'application/postscript', 'icon'=>'pdf'),
    1522          'pub'  => array ('type'=>'application/x-mspublisher', 'icon'=>'publisher', 'groups'=>array('presentation')),
    1523  
    1524          'qt'   => array ('type'=>'video/quicktime', 'icon'=>'quicktime', 'groups'=>array('video','web_video'), 'string'=>'video'),
    1525          'ra'   => array ('type'=>'audio/x-realaudio-plugin', 'icon'=>'audio', 'groups'=>array('audio','web_audio'), 'string'=>'audio'),
    1526          'ram'  => array ('type'=>'audio/x-pn-realaudio-plugin', 'icon'=>'audio', 'groups'=>array('audio'), 'string'=>'audio'),
    1527          'rar'  => array ('type'=>'application/x-rar-compressed', 'icon'=>'archive', 'groups'=>array('archive'), 'string'=>'archive'),
    1528          'rhb'  => array ('type'=>'text/xml', 'icon'=>'markup'),
    1529          'rm'   => array ('type'=>'audio/x-pn-realaudio-plugin', 'icon'=>'audio', 'groups'=>array('audio'), 'string'=>'audio'),
    1530          'rmvb' => array ('type'=>'application/vnd.rn-realmedia-vbr', 'icon'=>'video', 'groups'=>array('video'), 'string'=>'video'),
    1531          'rtf'  => array ('type'=>'text/rtf', 'icon'=>'text', 'groups'=>array('document')),
    1532          'rtx'  => array ('type'=>'text/richtext', 'icon'=>'text'),
    1533          'rv'   => array ('type'=>'audio/x-pn-realaudio-plugin', 'icon'=>'audio', 'groups'=>array('video'), 'string'=>'video'),
    1534          'sh'   => array ('type'=>'application/x-sh', 'icon'=>'sourcecode'),
    1535          'sit'  => array ('type'=>'application/x-stuffit', 'icon'=>'archive', 'groups'=>array('archive'), 'string'=>'archive'),
    1536          'smi'  => array ('type'=>'application/smil', 'icon'=>'text'),
    1537          'smil' => array ('type'=>'application/smil', 'icon'=>'text'),
    1538          'sqt'  => array ('type'=>'text/xml', 'icon'=>'markup'),
    1539          'svg'  => array ('type'=>'image/svg+xml', 'icon'=>'image', 'groups'=>array('image','web_image'), 'string'=>'image'),
    1540          'svgz' => array ('type'=>'image/svg+xml', 'icon'=>'image', 'groups'=>array('image','web_image'), 'string'=>'image'),
    1541          'swa'  => array ('type'=>'application/x-director', 'icon'=>'flash'),
    1542          'swf'  => array ('type'=>'application/x-shockwave-flash', 'icon'=>'flash', 'groups'=>array('video','web_video')),
    1543          'swfl' => array ('type'=>'application/x-shockwave-flash', 'icon'=>'flash', 'groups'=>array('video','web_video')),
    1544  
    1545          'sxw'  => array ('type'=>'application/vnd.sun.xml.writer', 'icon'=>'writer'),
    1546          'stw'  => array ('type'=>'application/vnd.sun.xml.writer.template', 'icon'=>'writer'),
    1547          'sxc'  => array ('type'=>'application/vnd.sun.xml.calc', 'icon'=>'calc'),
    1548          'stc'  => array ('type'=>'application/vnd.sun.xml.calc.template', 'icon'=>'calc'),
    1549          'sxd'  => array ('type'=>'application/vnd.sun.xml.draw', 'icon'=>'draw'),
    1550          'std'  => array ('type'=>'application/vnd.sun.xml.draw.template', 'icon'=>'draw'),
    1551          'sxi'  => array ('type'=>'application/vnd.sun.xml.impress', 'icon'=>'impress'),
    1552          'sti'  => array ('type'=>'application/vnd.sun.xml.impress.template', 'icon'=>'impress'),
    1553          'sxg'  => array ('type'=>'application/vnd.sun.xml.writer.global', 'icon'=>'writer'),
    1554          'sxm'  => array ('type'=>'application/vnd.sun.xml.math', 'icon'=>'math'),
    1555  
    1556          'tar'  => array ('type'=>'application/x-tar', 'icon'=>'archive', 'groups'=>array('archive'), 'string'=>'archive'),
    1557          'tif'  => array ('type'=>'image/tiff', 'icon'=>'tiff', 'groups'=>array('image'), 'string'=>'image'),
    1558          'tiff' => array ('type'=>'image/tiff', 'icon'=>'tiff', 'groups'=>array('image'), 'string'=>'image'),
    1559          'tex'  => array ('type'=>'application/x-tex', 'icon'=>'text'),
    1560          'texi' => array ('type'=>'application/x-texinfo', 'icon'=>'text'),
    1561          'texinfo'  => array ('type'=>'application/x-texinfo', 'icon'=>'text'),
    1562          'tsv'  => array ('type'=>'text/tab-separated-values', 'icon'=>'text'),
    1563          'txt'  => array ('type'=>'text/plain', 'icon'=>'text', 'defaulticon'=>true),
    1564          'wav'  => array ('type'=>'audio/wav', 'icon'=>'wav', 'groups'=>array('audio'), 'string'=>'audio'),
    1565          'webm'  => array ('type'=>'video/webm', 'icon'=>'video', 'groups'=>array('video'), 'string'=>'video'),
    1566          'wmv'  => array ('type'=>'video/x-ms-wmv', 'icon'=>'wmv', 'groups'=>array('video'), 'string'=>'video'),
    1567          'asf'  => array ('type'=>'video/x-ms-asf', 'icon'=>'wmv', 'groups'=>array('video'), 'string'=>'video'),
    1568          'wma'  => array ('type'=>'audio/x-ms-wma', 'icon'=>'audio', 'groups'=>array('audio'), 'string'=>'audio'),
    1569  
    1570          'xbk'  => array ('type'=>'application/x-smarttech-notebook', 'icon'=>'archive'),
    1571          'xdp'  => array ('type'=>'application/pdf', 'icon'=>'pdf'),
    1572          'xfd'  => array ('type'=>'application/pdf', 'icon'=>'pdf'),
    1573          'xfdf' => array ('type'=>'application/pdf', 'icon'=>'pdf'),
    1574  
    1575          'xls'  => array ('type'=>'application/vnd.ms-excel', 'icon'=>'spreadsheet', 'groups'=>array('spreadsheet')),
    1576          'xlsx' => array ('type'=>'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'icon'=>'spreadsheet'),
    1577          'xlsm' => array ('type'=>'application/vnd.ms-excel.sheet.macroEnabled.12', 'icon'=>'spreadsheet', 'groups'=>array('spreadsheet')),
    1578          'xltx' => array ('type'=>'application/vnd.openxmlformats-officedocument.spreadsheetml.template', 'icon'=>'spreadsheet'),
    1579          'xltm' => array ('type'=>'application/vnd.ms-excel.template.macroEnabled.12', 'icon'=>'spreadsheet'),
    1580          'xlsb' => array ('type'=>'application/vnd.ms-excel.sheet.binary.macroEnabled.12', 'icon'=>'spreadsheet'),
    1581          'xlam' => array ('type'=>'application/vnd.ms-excel.addin.macroEnabled.12', 'icon'=>'spreadsheet'),
    1582  
    1583          'xml'  => array ('type'=>'application/xml', 'icon'=>'markup'),
    1584          'xsl'  => array ('type'=>'text/xml', 'icon'=>'markup'),
    1585  
    1586          'zip'  => array ('type'=>'application/zip', 'icon'=>'archive', 'groups'=>array('archive'), 'string'=>'archive')
    1587      );
    1588      return $mimearray;
    1589  }
    1590  
    1591  /**
    1592   * Determine a file's MIME type based on the given filename using the function mimeinfo.
    1593   *
    1594   * This function retrieves a file's MIME type for a file that will be sent to the user.
    1595   * This should only be used for file-sending purposes just like in send_stored_file, send_file, and send_temp_file.
    1596   * Should the file's MIME type cannot be determined by mimeinfo, it will return 'application/octet-stream' as a default
    1597   * MIME type which should tell the browser "I don't know what type of file this is, so just download it.".
    1598   *
    1599   * @param string $filename The file's filename.
    1600   * @return string The file's MIME type or 'application/octet-stream' if it cannot be determined.
    1601   */
    1602  function get_mimetype_for_sending($filename = '') {
    1603      // Guess the file's MIME type using mimeinfo.
    1604      $mimetype = mimeinfo('type', $filename);
    1605  
    1606      // Use octet-stream as fallback if MIME type cannot be determined by mimeinfo.
    1607      if (!$mimetype || $mimetype === 'document/unknown') {
    1608          $mimetype = 'application/octet-stream';
    1609      }
    1610  
    1611      return $mimetype;
    1612  }
    1613  
    1614  /**
    1615   * Obtains information about a filetype based on its extension. Will
    1616   * use a default if no information is present about that particular
    1617   * extension.
    1618   *
    1619   * @category files
    1620   * @param string $element Desired information (usually 'icon'
    1621   *   for icon filename or 'type' for MIME type. Can also be
    1622   *   'icon24', ...32, 48, 64, 72, 80, 96, 128, 256)
    1623   * @param string $filename Filename we're looking up
    1624   * @return string Requested piece of information from array
    1625   */
    1626  function mimeinfo($element, $filename) {
    1627      global $CFG;
    1628      $mimeinfo = & get_mimetypes_array();
    1629      static $iconpostfixes = array(256=>'-256', 128=>'-128', 96=>'-96', 80=>'-80', 72=>'-72', 64=>'-64', 48=>'-48', 32=>'-32', 24=>'-24', 16=>'');
    1630  
    1631      $filetype = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
    1632      if (empty($filetype)) {
    1633          $filetype = 'xxx'; // file without extension
    1634      }
    1635      if (preg_match('/^icon(\d*)$/', $element, $iconsizematch)) {
    1636          $iconsize = max(array(16, (int)$iconsizematch[1]));
    1637          $filenames = array($mimeinfo['xxx']['icon']);
    1638          if ($filetype != 'xxx' && isset($mimeinfo[$filetype]['icon'])) {
    1639              array_unshift($filenames, $mimeinfo[$filetype]['icon']);
    1640          }
    1641          // find the file with the closest size, first search for specific icon then for default icon
    1642          foreach ($filenames as $filename) {
    1643              foreach ($iconpostfixes as $size => $postfix) {
    1644                  $fullname = $CFG->dirroot.'/pix/f/'.$filename.$postfix;
    1645                  if ($iconsize >= $size && (file_exists($fullname.'.png') || file_exists($fullname.'.gif'))) {
    1646                      return $filename.$postfix;
    1647                  }
    1648              }
    1649          }
    1650      } else if (isset($mimeinfo[$filetype][$element])) {
    1651          return $mimeinfo[$filetype][$element];
    1652      } else if (isset($mimeinfo['xxx'][$element])) {
    1653          return $mimeinfo['xxx'][$element];   // By default
    1654      } else {
    1655          return null;
    1656      }
    1657  }
    1658  
    1659  /**
    1660   * Obtains information about a filetype based on the MIME type rather than
    1661   * the other way around.
    1662   *
    1663   * @category files
    1664   * @param string $element Desired information ('extension', 'icon', 'icon-24', etc.)
    1665   * @param string $mimetype MIME type we're looking up
    1666   * @return string Requested piece of information from array
    1667   */
    1668  function mimeinfo_from_type($element, $mimetype) {
    1669      /* array of cached mimetype->extension associations */
    1670      static $cached = array();
    1671      $mimeinfo = & get_mimetypes_array();
    1672  
    1673      if (!array_key_exists($mimetype, $cached)) {
    1674          $cached[$mimetype] = null;
    1675          foreach($mimeinfo as $filetype => $values) {
    1676              if ($values['type'] == $mimetype) {
    1677                  if ($cached[$mimetype] === null) {
    1678                      $cached[$mimetype] = '.'.$filetype;
    1679                  }
    1680                  if (!empty($values['defaulticon'])) {
    1681                      $cached[$mimetype] = '.'.$filetype;
    1682                      break;
    1683                  }
    1684              }
    1685          }
    1686          if (empty($cached[$mimetype])) {
    1687              $cached[$mimetype] = '.xxx';
    1688          }
    1689      }
    1690      if ($element === 'extension') {
    1691          return $cached[$mimetype];
    1692      } else {
    1693          return mimeinfo($element, $cached[$mimetype]);
    1694      }
    1695  }
    1696  
    1697  /**
    1698   * Return the relative icon path for a given file
    1699   *
    1700   * Usage:
    1701   * <code>
    1702   * // $file - instance of stored_file or file_info
    1703   * $icon = $OUTPUT->pix_url(file_file_icon($file))->out();
    1704   * echo html_writer::empty_tag('img', array('src' => $icon, 'alt' => get_mimetype_description($file)));
    1705   * </code>
    1706   * or
    1707   * <code>
    1708   * echo $OUTPUT->pix_icon(file_file_icon($file), get_mimetype_description($file));
    1709   * </code>
    1710   *
    1711   * @param stored_file|file_info|stdClass|array $file (in case of object attributes $file->filename
    1712   *     and $file->mimetype are expected)
    1713   * @param int $size The size of the icon. Defaults to 16 can also be 24, 32, 64, 128, 256
    1714   * @return string
    1715   */
    1716  function file_file_icon($file, $size = null) {
    1717      if (!is_object($file)) {
    1718          $file = (object)$file;
    1719      }
    1720      if (isset($file->filename)) {
    1721          $filename = $file->filename;
    1722      } else if (method_exists($file, 'get_filename')) {
    1723          $filename = $file->get_filename();
    1724      } else if (method_exists($file, 'get_visible_name')) {
    1725          $filename = $file->get_visible_name();
    1726      } else {
    1727          $filename = '';
    1728      }
    1729      if (isset($file->mimetype)) {
    1730          $mimetype = $file->mimetype;
    1731      } else if (method_exists($file, 'get_mimetype')) {
    1732          $mimetype = $file->get_mimetype();
    1733      } else {
    1734          $mimetype = '';
    1735      }
    1736      $mimetypes = &get_mimetypes_array();
    1737      if ($filename) {
    1738          $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
    1739          if ($extension && !empty($mimetypes[$extension])) {
    1740              // if file name has known extension, return icon for this extension
    1741              return file_extension_icon($filename, $size);
    1742          }
    1743      }
    1744      return file_mimetype_icon($mimetype, $size);
    1745  }
    1746  
    1747  /**
    1748   * Return the relative icon path for a folder image
    1749   *
    1750   * Usage:
    1751   * <code>
    1752   * $icon = $OUTPUT->pix_url(file_folder_icon())->out();
    1753   * echo html_writer::empty_tag('img', array('src' => $icon));
    1754   * </code>
    1755   * or
    1756   * <code>
    1757   * echo $OUTPUT->pix_icon(file_folder_icon(32));
    1758   * </code>
    1759   *
    1760   * @param int $iconsize The size of the icon. Defaults to 16 can also be 24, 32, 48, 64, 72, 80, 96, 128, 256
    1761   * @return string
    1762   */
    1763  function file_folder_icon($iconsize = null) {
    1764      global $CFG;
    1765      static $iconpostfixes = array(256=>'-256', 128=>'-128', 96=>'-96', 80=>'-80', 72=>'-72', 64=>'-64', 48=>'-48', 32=>'-32', 24=>'-24', 16=>'');
    1766      static $cached = array();
    1767      $iconsize = max(array(16, (int)$iconsize));
    1768      if (!array_key_exists($iconsize, $cached)) {
    1769          foreach ($iconpostfixes as $size => $postfix) {
    1770              $fullname = $CFG->dirroot.'/pix/f/folder'.$postfix;
    1771              if ($iconsize >= $size && (file_exists($fullname.'.png') || file_exists($fullname.'.gif'))) {
    1772                  $cached[$iconsize] = 'f/folder'.$postfix;
    1773                  break;
    1774              }
    1775          }
    1776      }
    1777      return $cached[$iconsize];
    1778  }
    1779  
    1780  /**
    1781   * Returns the relative icon path for a given mime type
    1782   *
    1783   * This function should be used in conjunction with $OUTPUT->pix_url to produce
    1784   * a return the full path to an icon.
    1785   *
    1786   * <code>
    1787   * $mimetype = 'image/jpg';
    1788   * $icon = $OUTPUT->pix_url(file_mimetype_icon($mimetype))->out();
    1789   * echo html_writer::empty_tag('img', array('src' => $icon, 'alt' => get_mimetype_description($mimetype)));
    1790   * </code>
    1791   *
    1792   * @category files
    1793   * @todo MDL-31074 When an $OUTPUT->icon method is available this function should be altered
    1794   * to conform with that.
    1795   * @param string $mimetype The mimetype to fetch an icon for
    1796   * @param int $size The size of the icon. Defaults to 16 can also be 24, 32, 64, 128, 256
    1797   * @return string The relative path to the icon
    1798   */
    1799  function file_mimetype_icon($mimetype, $size = NULL) {
    1800      return 'f/'.mimeinfo_from_type('icon'.$size, $mimetype);
    1801  }
    1802  
    1803  /**
    1804   * Returns the relative icon path for a given file name
    1805   *
    1806   * This function should be used in conjunction with $OUTPUT->pix_url to produce
    1807   * a return the full path to an icon.
    1808   *
    1809   * <code>
    1810   * $filename = '.jpg';
    1811   * $icon = $OUTPUT->pix_url(file_extension_icon($filename))->out();
    1812   * echo html_writer::empty_tag('img', array('src' => $icon, 'alt' => '...'));
    1813   * </code>
    1814   *
    1815   * @todo MDL-31074 When an $OUTPUT->icon method is available this function should be altered
    1816   * to conform with that.
    1817   * @todo MDL-31074 Implement $size
    1818   * @category files
    1819   * @param string $filename The filename to get the icon for
    1820   * @param int $size The size of the icon. Defaults to 16 can also be 24, 32, 64, 128, 256
    1821   * @return string
    1822   */
    1823  function file_extension_icon($filename, $size = NULL) {
    1824      return 'f/'.mimeinfo('icon'.$size, $filename);
    1825  }
    1826  
    1827  /**
    1828   * Obtains descriptions for file types (e.g. 'Microsoft Word document') from the
    1829   * mimetypes.php language file.
    1830   *
    1831   * @param mixed $obj - instance of stored_file or file_info or array/stdClass with field
    1832   *   'filename' and 'mimetype', or just a string with mimetype (though it is recommended to
    1833   *   have filename); In case of array/stdClass the field 'mimetype' is optional.
    1834   * @param bool $capitalise If true, capitalises first character of result
    1835   * @return string Text description
    1836   */
    1837  function get_mimetype_description($obj, $capitalise=false) {
    1838      $filename = $mimetype = '';
    1839      if (is_object($obj) && method_exists($obj, 'get_filename') && method_exists($obj, 'get_mimetype')) {
    1840          // this is an instance of stored_file
    1841          $mimetype = $obj->get_mimetype();
    1842          $filename = $obj->get_filename();
    1843      } else if (is_object($obj) && method_exists($obj, 'get_visible_name') && method_exists($obj, 'get_mimetype')) {
    1844          // this is an instance of file_info
    1845          $mimetype = $obj->get_mimetype();
    1846          $filename = $obj->get_visible_name();
    1847      } else if (is_array($obj) || is_object ($obj)) {
    1848          $obj = (array)$obj;
    1849          if (!empty($obj['filename'])) {
    1850              $filename = $obj['filename'];
    1851          }
    1852          if (!empty($obj['mimetype'])) {
    1853              $mimetype = $obj['mimetype'];
    1854          }
    1855      } else {
    1856          $mimetype = $obj;
    1857      }
    1858      $mimetypefromext = mimeinfo('type', $filename);
    1859      if (empty($mimetype) || $mimetypefromext !== 'document/unknown') {
    1860          // if file has a known extension, overwrite the specified mimetype
    1861          $mimetype = $mimetypefromext;
    1862      }
    1863      $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
    1864      if (empty($extension)) {
    1865          $mimetypestr = mimeinfo_from_type('string', $mimetype);
    1866          $extension = str_replace('.', '', mimeinfo_from_type('extension', $mimetype));
    1867      } else {
    1868          $mimetypestr = mimeinfo('string', $filename);
    1869      }
    1870      $chunks = explode('/', $mimetype, 2);
    1871      $chunks[] = '';
    1872      $attr = array(
    1873          'mimetype' => $mimetype,
    1874          'ext' => $extension,
    1875          'mimetype1' => $chunks[0],
    1876          'mimetype2' => $chunks[1],
    1877      );
    1878      $a = array();
    1879      foreach ($attr as $key => $value) {
    1880          $a[$key] = $value;
    1881          $a[strtoupper($key)] = strtoupper($value);
    1882          $a[ucfirst($key)] = ucfirst($value);
    1883      }
    1884  
    1885      // MIME types may include + symbol but this is not permitted in string ids.
    1886      $safemimetype = str_replace('+', '_', $mimetype);
    1887      $safemimetypestr = str_replace('+', '_', $mimetypestr);
    1888      if (get_string_manager()->string_exists($safemimetype, 'mimetypes')) {
    1889          $result = get_string($safemimetype, 'mimetypes', (object)$a);
    1890      } else if (get_string_manager()->string_exists($safemimetypestr, 'mimetypes')) {
    1891          $result = get_string($safemimetypestr, 'mimetypes', (object)$a);
    1892      } else if (get_string_manager()->string_exists('default', 'mimetypes')) {
    1893          $result = get_string('default', 'mimetypes', (object)$a);
    1894      } else {
    1895          $result = $mimetype;
    1896      }
    1897      if ($capitalise) {
    1898          $result=ucfirst($result);
    1899      }
    1900      return $result;
    1901  }
    1902  
    1903  /**
    1904   * Returns array of elements of type $element in type group(s)
    1905   *
    1906   * @param string $element name of the element we are interested in, usually 'type' or 'extension'
    1907   * @param string|array $groups one group or array of groups/extensions/mimetypes
    1908   * @return array
    1909   */
    1910  function file_get_typegroup($element, $groups) {
    1911      static $cached = array();
    1912      if (!is_array($groups)) {
    1913          $groups = array($groups);
    1914      }
    1915      if (!array_key_exists($element, $cached)) {
    1916          $cached[$element] = array();
    1917      }
    1918      $result = array();
    1919      foreach ($groups as $group) {
    1920          if (!array_key_exists($group, $cached[$element])) {
    1921              // retrieive and cache all elements of type $element for group $group
    1922              $mimeinfo = & get_mimetypes_array();
    1923              $cached[$element][$group] = array();
    1924              foreach ($mimeinfo as $extension => $value) {
    1925                  $value['extension'] = '.'.$extension;
    1926                  if (empty($value[$element])) {
    1927                      continue;
    1928                  }
    1929                  if (($group === '.'.$extension || $group === $value['type'] ||
    1930                          (!empty($value['groups']) && in_array($group, $value['groups']))) &&
    1931                          !in_array($value[$element], $cached[$element][$group])) {
    1932                      $cached[$element][$group][] = $value[$element];
    1933                  }
    1934              }
    1935          }
    1936          $result = array_merge($result, $cached[$element][$group]);
    1937      }
    1938      return array_values(array_unique($result));
    1939  }
    1940  
    1941  /**
    1942   * Checks if file with name $filename has one of the extensions in groups $groups
    1943   *
    1944   * @see get_mimetypes_array()
    1945   * @param string $filename name of the file to check
    1946   * @param string|array $groups one group or array of groups to check
    1947   * @param bool $checktype if true and extension check fails, find the mimetype and check if
    1948   * file mimetype is in mimetypes in groups $groups
    1949   * @return bool
    1950   */
    1951  function file_extension_in_typegroup($filename, $groups, $checktype = false) {
    1952      $extension = pathinfo($filename, PATHINFO_EXTENSION);
    1953      if (!empty($extension) && in_array('.'.strtolower($extension), file_get_typegroup('extension', $groups))) {
    1954          return true;
    1955      }
    1956      return $checktype && file_mimetype_in_typegroup(mimeinfo('type', $filename), $groups);
    1957  }
    1958  
    1959  /**
    1960   * Checks if mimetype $mimetype belongs to one of the groups $groups
    1961   *
    1962   * @see get_mimetypes_array()
    1963   * @param string $mimetype
    1964   * @param string|array $groups one group or array of groups to check
    1965   * @return bool
    1966   */
    1967  function file_mimetype_in_typegroup($mimetype, $groups) {
    1968      return !empty($mimetype) && in_array($mimetype, file_get_typegroup('type', $groups));
    1969  }
    1970  
    1971  /**
    1972   * Requested file is not found or not accessible, does not return, terminates script
    1973   *
    1974   * @global stdClass $CFG
    1975   * @global stdClass $COURSE
    1976   */
    1977  function send_file_not_found() {
    1978      global $CFG, $COURSE;
    1979  
    1980      // Allow cross-origin requests only for Web Services.
    1981      // This allow to receive requests done by Web Workers or webapps in different domains.
    1982      if (WS_SERVER) {
    1983          header('Access-Control-Allow-Origin: *');
    1984      }
    1985  
    1986      send_header_404();
    1987      print_error('filenotfound', 'error', $CFG->wwwroot.'/course/view.php?id='.$COURSE->id); //this is not displayed on IIS??
    1988  }
    1989  /**
    1990   * Helper function to send correct 404 for server.
    1991   */
    1992  function send_header_404() {
    1993      if (substr(php_sapi_name(), 0, 3) == 'cgi') {
    1994          header("Status: 404 Not Found");
    1995      } else {
    1996          header('HTTP/1.0 404 not found');
    1997      }
    1998  }
    1999  
    2000  /**
    2001   * The readfile function can fail when files are larger than 2GB (even on 64-bit
    2002   * platforms). This wrapper uses readfile for small files and custom code for
    2003   * large ones.
    2004   *
    2005   * @param string $path Path to file
    2006   * @param int $filesize Size of file (if left out, will get it automatically)
    2007   * @return int|bool Size read (will always be $filesize) or false if failed
    2008   */
    2009  function readfile_allow_large($path, $filesize = -1) {
    2010      // Automatically get size if not specified.
    2011      if ($filesize === -1) {
    2012          $filesize = filesize($path);
    2013      }
    2014      if ($filesize <= 2147483647) {
    2015          // If the file is up to 2^31 - 1, send it normally using readfile.
    2016          return readfile($path);
    2017      } else {
    2018          // For large files, read and output in 64KB chunks.
    2019          $handle = fopen($path, 'r');
    2020          if ($handle === false) {
    2021              return false;
    2022          }
    2023          $left = $filesize;
    2024          while ($left > 0) {
    2025              $size = min($left, 65536);
    2026              $buffer = fread($handle, $size);
    2027              if ($buffer === false) {
    2028                  return false;
    2029              }
    2030              echo $buffer;
    2031              $left -= $size;
    2032          }
    2033          return $filesize;
    2034      }
    2035  }
    2036  
    2037  /**
    2038   * Enhanced readfile() with optional acceleration.
    2039   * @param string|stored_file $file
    2040   * @param string $mimetype
    2041   * @param bool $accelerate
    2042   * @return void
    2043   */
    2044  function readfile_accel($file, $mimetype, $accelerate) {
    2045      global $CFG;
    2046  
    2047      if ($mimetype === 'text/plain') {
    2048          // there is no encoding specified in text files, we need something consistent
    2049          header('Content-Type: text/plain; charset=utf-8');
    2050      } else {
    2051          header('Content-Type: '.$mimetype);
    2052      }
    2053  
    2054      $lastmodified = is_object($file) ? $file->get_timemodified() : filemtime($file);
    2055      header('Last-Modified: '. gmdate('D, d M Y H:i:s', $lastmodified) .' GMT');
    2056  
    2057      if (is_object($file)) {
    2058          header('Etag: "' . $file->get_contenthash() . '"');
    2059          if (isset($_SERVER['HTTP_IF_NONE_MATCH']) and trim($_SERVER['HTTP_IF_NONE_MATCH'], '"') === $file->get_contenthash()) {
    2060              header('HTTP/1.1 304 Not Modified');
    2061              return;
    2062          }
    2063      }
    2064  
    2065      // if etag present for stored file rely on it exclusively
    2066      if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) and (empty($_SERVER['HTTP_IF_NONE_MATCH']) or !is_object($file))) {
    2067          // get unixtime of request header; clip extra junk off first
    2068          $since = strtotime(preg_replace('/;.*$/', '', $_SERVER["HTTP_IF_MODIFIED_SINCE"]));
    2069          if ($since && $since >= $lastmodified) {
    2070              header('HTTP/1.1 304 Not Modified');
    2071              return;
    2072          }
    2073      }
    2074  
    2075      if ($accelerate and !empty($CFG->xsendfile)) {
    2076          if (empty($CFG->disablebyteserving) and $mimetype !== 'text/plain') {
    2077              header('Accept-Ranges: bytes');
    2078          } else {
    2079              header('Accept-Ranges: none');
    2080          }
    2081  
    2082          if (is_object($file)) {
    2083              $fs = get_file_storage();
    2084              if ($fs->xsendfile($file->get_contenthash())) {
    2085                  return;
    2086              }
    2087  
    2088          } else {
    2089              require_once("$CFG->libdir/xsendfilelib.php");
    2090              if (xsendfile($file)) {
    2091                  return;
    2092              }
    2093          }
    2094      }
    2095  
    2096      $filesize = is_object($file) ? $file->get_filesize() : filesize($file);
    2097  
    2098      header('Last-Modified: '. gmdate('D, d M Y H:i:s', $lastmodified) .' GMT');
    2099  
    2100      if ($accelerate and empty($CFG->disablebyteserving) and $mimetype !== 'text/plain') {
    2101          header('Accept-Ranges: bytes');
    2102  
    2103          if (!empty($_SERVER['HTTP_RANGE']) and strpos($_SERVER['HTTP_RANGE'],'bytes=') !== FALSE) {
    2104              // byteserving stuff - for acrobat reader and download accelerators
    2105              // see: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35
    2106              // inspired by: http://www.coneural.org/florian/papers/04_byteserving.php
    2107              $ranges = false;
    2108              if (preg_match_all('/(\d*)-(\d*)/', $_SERVER['HTTP_RANGE'], $ranges, PREG_SET_ORDER)) {
    2109                  foreach ($ranges as $key=>$value) {
    2110                      if ($ranges[$key][1] == '') {
    2111                          //suffix case
    2112                          $ranges[$key][1] = $filesize - $ranges[$key][2];
    2113                          $ranges[$key][2] = $filesize - 1;
    2114                      } else if ($ranges[$key][2] == '' || $ranges[$key][2] > $filesize - 1) {
    2115                          //fix range length
    2116                          $ranges[$key][2] = $filesize - 1;
    2117                      }
    2118                      if ($ranges[$key][2] != '' && $ranges[$key][2] < $ranges[$key][1]) {
    2119                          //invalid byte-range ==> ignore header
    2120                          $ranges = false;
    2121                          break;
    2122                      }
    2123                      //prepare multipart header
    2124                      $ranges[$key][0] =  "\r\n--".BYTESERVING_BOUNDARY."\r\nContent-Type: $mimetype\r\n";
    2125                      $ranges[$key][0] .= "Content-Range: bytes {$ranges[$key][1]}-{$ranges[$key][2]}/$filesize\r\n\r\n";
    2126                  }
    2127              } else {
    2128                  $ranges = false;
    2129              }
    2130              if ($ranges) {
    2131                  if (is_object($file)) {
    2132                      $handle = $file->get_content_file_handle();
    2133                  } else {
    2134                      $handle = fopen($file, 'rb');
    2135                  }
    2136                  byteserving_send_file($handle, $mimetype, $ranges, $filesize);
    2137              }
    2138          }
    2139      } else {
    2140          // Do not byteserve
    2141          header('Accept-Ranges: none');
    2142      }
    2143  
    2144      header('Content-Length: '.$filesize);
    2145  
    2146      if ($filesize > 10000000) {
    2147          // for large files try to flush and close all buffers to conserve memory
    2148          while(@ob_get_level()) {
    2149              if (!@ob_end_flush()) {
    2150                  break;
    2151              }
    2152          }
    2153      }
    2154  
    2155      // send the whole file content
    2156      if (is_object($file)) {
    2157          $file->readfile();
    2158      } else {
    2159          readfile_allow_large($file, $filesize);
    2160      }
    2161  }
    2162  
    2163  /**
    2164   * Similar to readfile_accel() but designed for strings.
    2165   * @param string $string
    2166   * @param string $mimetype
    2167   * @param bool $accelerate
    2168   * @return void
    2169   */
    2170  function readstring_accel($string, $mimetype, $accelerate) {
    2171      global $CFG;
    2172  
    2173      if ($mimetype === 'text/plain') {
    2174          // there is no encoding specified in text files, we need something consistent
    2175          header('Content-Type: text/plain; charset=utf-8');
    2176      } else {
    2177          header('Content-Type: '.$mimetype);
    2178      }
    2179      header('Last-Modified: '. gmdate('D, d M Y H:i:s', time()) .' GMT');
    2180      header('Accept-Ranges: none');
    2181  
    2182      if ($accelerate and !empty($CFG->xsendfile)) {
    2183          $fs = get_file_storage();
    2184          if ($fs->xsendfile(sha1($string))) {
    2185              return;
    2186          }
    2187      }
    2188  
    2189      header('Content-Length: '.strlen($string));
    2190      echo $string;
    2191  }
    2192  
    2193  /**
    2194   * Handles the sending of temporary file to user, download is forced.
    2195   * File is deleted after abort or successful sending, does not return, script terminated
    2196   *
    2197   * @param string $path path to file, preferably from moodledata/temp/something; or content of file itself
    2198   * @param string $filename proposed file name when saving file
    2199   * @param bool $pathisstring If the path is string
    2200   */
    2201  function send_temp_file($path, $filename, $pathisstring=false) {
    2202      global $CFG;
    2203  
    2204      // Guess the file's MIME type.
    2205      $mimetype = get_mimetype_for_sending($filename);
    2206  
    2207      // close session - not needed anymore
    2208      \core\session\manager::write_close();
    2209  
    2210      if (!$pathisstring) {
    2211          if (!file_exists($path)) {
    2212              send_header_404();
    2213              print_error('filenotfound', 'error', $CFG->wwwroot.'/');
    2214          }
    2215          // executed after normal finish or abort
    2216          core_shutdown_manager::register_function('send_temp_file_finished', array($path));
    2217      }
    2218  
    2219      // if user is using IE, urlencode the filename so that multibyte file name will show up correctly on popup
    2220      if (core_useragent::is_ie()) {
    2221          $filename = urlencode($filename);
    2222      }
    2223  
    2224      header('Content-Disposition: attachment; filename="'.$filename.'"');
    2225      if (is_https()) { // HTTPS sites - watch out for IE! KB812935 and KB316431.
    2226          header('Cache-Control: private, max-age=10, no-transform');
    2227          header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
    2228          header('Pragma: ');
    2229      } else { //normal http - prevent caching at all cost
    2230          header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0, no-transform');
    2231          header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
    2232          header('Pragma: no-cache');
    2233      }
    2234  
    2235      // send the contents - we can not accelerate this because the file will be deleted asap
    2236      if ($pathisstring) {
    2237          readstring_accel($path, $mimetype, false);
    2238      } else {
    2239          readfile_accel($path, $mimetype, false);
    2240          @unlink($path);
    2241      }
    2242  
    2243      die; //no more chars to output
    2244  }
    2245  
    2246  /**
    2247   * Internal callback function used by send_temp_file()
    2248   *
    2249   * @param string $path
    2250   */
    2251  function send_temp_file_finished($path) {
    2252      if (file_exists($path)) {
    2253          @unlink($path);
    2254      }
    2255  }
    2256  
    2257  /**
    2258   * Handles the sending of file data to the user's browser, including support for
    2259   * byteranges etc.
    2260   *
    2261   * @category files
    2262   * @param string $path Path of file on disk (including real filename), or actual content of file as string
    2263   * @param string $filename Filename to send
    2264   * @param int $lifetime Number of seconds before the file should expire from caches (null means $CFG->filelifetime)
    2265   * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only
    2266   * @param bool $pathisstring If true (default false), $path is the content to send and not the pathname
    2267   * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin
    2268   * @param string $mimetype Include to specify the MIME type; leave blank to have it guess the type from $filename
    2269   * @param bool $dontdie - return control to caller afterwards. this is not recommended and only used for cleanup tasks.
    2270   *                        if this is passed as true, ignore_user_abort is called.  if you don't want your processing to continue on cancel,
    2271   *                        you must detect this case when control is returned using connection_aborted. Please not that session is closed
    2272   *                        and should not be reopened.
    2273   * @return null script execution stopped unless $dontdie is true
    2274   */
    2275  function send_file($path, $filename, $lifetime = null , $filter=0, $pathisstring=false, $forcedownload=false, $mimetype='', $dontdie=false) {
    2276      global $CFG, $COURSE;
    2277  
    2278      if ($dontdie) {
    2279          ignore_user_abort(true);
    2280      }
    2281  
    2282      if ($lifetime === 'default' or is_null($lifetime)) {
    2283          $lifetime = $CFG->filelifetime;
    2284      }
    2285  
    2286      \core\session\manager::write_close(); // Unlock session during file serving.
    2287  
    2288      // Use given MIME type if specified, otherwise guess it.
    2289      if (!$mimetype || $mimetype === 'document/unknown') {
    2290          $mimetype = get_mimetype_for_sending($filename);
    2291      }
    2292  
    2293      // if user is using IE, urlencode the filename so that multibyte file name will show up correctly on popup
    2294      if (core_useragent::is_ie()) {
    2295          $filename = rawurlencode($filename);
    2296      }
    2297  
    2298      if ($forcedownload) {
    2299          header('Content-Disposition: attachment; filename="'.$filename.'"');
    2300      } else if ($mimetype !== 'application/x-shockwave-flash') {
    2301          // If this is an swf don't pass content-disposition with filename as this makes the flash player treat the file
    2302          // as an upload and enforces security that may prevent the file from being loaded.
    2303  
    2304          header('Content-Disposition: inline; filename="'.$filename.'"');
    2305      }
    2306  
    2307      if ($lifetime > 0) {
    2308          $cacheability = ' public,';
    2309          if (isloggedin() and !isguestuser()) {
    2310              // By default, under the conditions above, this file must be cache-able only by browsers.
    2311              $cacheability = ' private,';
    2312          }
    2313          $nobyteserving = false;
    2314          header('Cache-Control:'.$cacheability.' max-age='.$lifetime.', no-transform');
    2315          header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
    2316          header('Pragma: ');
    2317  
    2318      } else { // Do not cache files in proxies and browsers
    2319          $nobyteserving = true;
    2320          if (is_https()) { // HTTPS sites - watch out for IE! KB812935 and KB316431.
    2321              header('Cache-Control: private, max-age=10, no-transform');
    2322              header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
    2323              header('Pragma: ');
    2324          } else { //normal http - prevent caching at all cost
    2325              header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0, no-transform');
    2326              header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
    2327              header('Pragma: no-cache');
    2328          }
    2329      }
    2330  
    2331      if (empty($filter)) {
    2332          // send the contents
    2333          if ($pathisstring) {
    2334              readstring_accel($path, $mimetype, !$dontdie);
    2335          } else {
    2336              readfile_accel($path, $mimetype, !$dontdie);
    2337          }
    2338  
    2339      } else {
    2340          // Try to put the file through filters
    2341          if ($mimetype == 'text/html') {
    2342              $options = new stdClass();
    2343              $options->noclean = true;
    2344              $options->nocache = true; // temporary workaround for MDL-5136
    2345              $text = $pathisstring ? $path : implode('', file($path));
    2346  
    2347              $text = file_modify_html_header($text);
    2348              $output = format_text($text, FORMAT_HTML, $options, $COURSE->id);
    2349  
    2350              readstring_accel($output, $mimetype, false);
    2351  
    2352          } else if (($mimetype == 'text/plain') and ($filter == 1)) {
    2353              // only filter text if filter all files is selected
    2354              $options = new stdClass();
    2355              $options->newlines = false;
    2356              $options->noclean = true;
    2357              $text = htmlentities($pathisstring ? $path : implode('', file($path)), ENT_QUOTES, 'UTF-8');
    2358              $output = '<pre>'. format_text($text, FORMAT_MOODLE, $options, $COURSE->id) .'</pre>';
    2359  
    2360              readstring_accel($output, $mimetype, false);
    2361  
    2362          } else {
    2363              // send the contents
    2364              if ($pathisstring) {
    2365                  readstring_accel($path, $mimetype, !$dontdie);
    2366              } else {
    2367                  readfile_accel($path, $mimetype, !$dontdie);
    2368              }
    2369          }
    2370      }
    2371      if ($dontdie) {
    2372          return;
    2373      }
    2374      die; //no more chars to output!!!
    2375  }
    2376  
    2377  /**
    2378   * Handles the sending of file data to the user's browser, including support for
    2379   * byteranges etc.
    2380   *
    2381   * The $options parameter supports the following keys:
    2382   *  (string|null) preview - send the preview of the file (e.g. "thumb" for a thumbnail)
    2383   *  (string|null) filename - overrides the implicit filename
    2384   *  (bool) dontdie - return control to caller afterwards. this is not recommended and only used for cleanup tasks.
    2385   *      if this is passed as true, ignore_user_abort is called.  if you don't want your processing to continue on cancel,
    2386   *      you must detect this case when control is returned using connection_aborted. Please not that session is closed
    2387   *      and should not be reopened
    2388   *  (string|null) cacheability - force the cacheability setting of the HTTP response, "private" or "public",
    2389   *      when $lifetime is greater than 0. Cacheability defaults to "private" when logged in as other than guest; otherwise,
    2390   *      defaults to "public".
    2391   *
    2392   * @category files
    2393   * @param stored_file $stored_file local file object
    2394   * @param int $lifetime Number of seconds before the file should expire from caches (null means $CFG->filelifetime)
    2395   * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only
    2396   * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin
    2397   * @param array $options additional options affecting the file serving
    2398   * @return null script execution stopped unless $options['dontdie'] is true
    2399   */
    2400  function send_stored_file($stored_file, $lifetime=null, $filter=0, $forcedownload=false, array $options=array()) {
    2401      global $CFG, $COURSE;
    2402  
    2403      if (empty($options['filename'])) {
    2404          $filename = null;
    2405      } else {
    2406          $filename = $options['filename'];
    2407      }
    2408  
    2409      if (empty($options['dontdie'])) {
    2410          $dontdie = false;
    2411      } else {
    2412          $dontdie = true;
    2413      }
    2414  
    2415      if ($lifetime === 'default' or is_null($lifetime)) {
    2416          $lifetime = $CFG->filelifetime;
    2417      }
    2418  
    2419      if (!empty($options['preview'])) {
    2420          // replace the file with its preview
    2421          $fs = get_file_storage();
    2422          $preview_file = $fs->get_file_preview($stored_file, $options['preview']);
    2423          if (!$preview_file) {
    2424              // unable to create a preview of the file, send its default mime icon instead
    2425              if ($options['preview'] === 'tinyicon') {
    2426                  $size = 24;
    2427              } else if ($options['preview'] === 'thumb') {
    2428                  $size = 90;
    2429              } else {
    2430                  $size = 256;
    2431              }
    2432              $fileicon = file_file_icon($stored_file, $size);
    2433              send_file($CFG->dirroot.'/pix/'.$fileicon.'.png', basename($fileicon).'.png');
    2434          } else {
    2435              // preview images have fixed cache lifetime and they ignore forced download
    2436              // (they are generated by GD and therefore they are considered reasonably safe).
    2437              $stored_file = $preview_file;
    2438              $lifetime = DAYSECS;
    2439              $filter = 0;
    2440              $forcedownload = false;
    2441          }
    2442      }
    2443  
    2444      // handle external resource
    2445      if ($stored_file && $stored_file->is_external_file() && !isset($options['sendcachedexternalfile'])) {
    2446          $stored_file->send_file($lifetime, $filter, $forcedownload, $options);
    2447          die;
    2448      }
    2449  
    2450      if (!$stored_file or $stored_file->is_directory()) {
    2451          // nothing to serve
    2452          if ($dontdie) {
    2453              return;
    2454          }
    2455          die;
    2456      }
    2457  
    2458      if ($dontdie) {
    2459          ignore_user_abort(true);
    2460      }
    2461  
    2462      \core\session\manager::write_close(); // Unlock session during file serving.
    2463  
    2464      $filename     = is_null($filename) ? $stored_file->get_filename() : $filename;
    2465  
    2466      // Use given MIME type if specified.
    2467      $mimetype = $stored_file->get_mimetype();
    2468  
    2469      // Otherwise guess it.
    2470      if (!$mimetype || $mimetype === 'document/unknown') {
    2471          $mimetype = get_mimetype_for_sending($filename);
    2472      }
    2473  
    2474      // if user is using IE, urlencode the filename so that multibyte file name will show up correctly on popup
    2475      if (core_useragent::is_ie()) {
    2476          $filename = rawurlencode($filename);
    2477      }
    2478  
    2479      if ($forcedownload) {
    2480          header('Content-Disposition: attachment; filename="'.$filename.'"');
    2481      } else if ($mimetype !== 'application/x-shockwave-flash') {
    2482          // If this is an swf don't pass content-disposition with filename as this makes the flash player treat the file
    2483          // as an upload and enforces security that may prevent the file from being loaded.
    2484  
    2485          header('Content-Disposition: inline; filename="'.$filename.'"');
    2486      }
    2487  
    2488      if ($lifetime > 0) {
    2489          $cacheability = ' public,';
    2490          if (!empty($options['cacheability']) && ($options['cacheability'] === 'public')) {
    2491              // This file must be cache-able by both browsers and proxies.
    2492              $cacheability = ' public,';
    2493          } else if (!empty($options['cacheability']) && ($options['cacheability'] === 'private')) {
    2494              // This file must be cache-able only by browsers.
    2495              $cacheability = ' private,';
    2496          } else if (isloggedin() and !isguestuser()) {
    2497              $cacheability = ' private,';
    2498          }
    2499          header('Cache-Control:'.$cacheability.' max-age='.$lifetime.', no-transform');
    2500          header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
    2501          header('Pragma: ');
    2502  
    2503      } else { // Do not cache files in proxies and browsers
    2504          if (is_https()) { // HTTPS sites - watch out for IE! KB812935 and KB316431.
    2505              header('Cache-Control: private, max-age=10, no-transform');
    2506              header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
    2507              header('Pragma: ');
    2508          } else { //normal http - prevent caching at all cost
    2509              header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0, no-transform');
    2510              header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
    2511              header('Pragma: no-cache');
    2512          }
    2513      }
    2514  
    2515      // Allow cross-origin requests only for Web Services.
    2516      // This allow to receive requests done by Web Workers or webapps in different domains.
    2517      if (WS_SERVER) {
    2518          header('Access-Control-Allow-Origin: *');
    2519      }
    2520  
    2521      if (empty($filter)) {
    2522          // send the contents
    2523          readfile_accel($stored_file, $mimetype, !$dontdie);
    2524  
    2525      } else {     // Try to put the file through filters
    2526          if ($mimetype == 'text/html') {
    2527              $options = new stdClass();
    2528              $options->noclean = true;
    2529              $options->nocache = true; // temporary workaround for MDL-5136
    2530              $text = $stored_file->get_content();
    2531              $text = file_modify_html_header($text);
    2532              $output = format_text($text, FORMAT_HTML, $options, $COURSE->id);
    2533  
    2534              readstring_accel($output, $mimetype, false);
    2535  
    2536          } else if (($mimetype == 'text/plain') and ($filter == 1)) {
    2537              // only filter text if filter all files is selected
    2538              $options = new stdClass();
    2539              $options->newlines = false;
    2540              $options->noclean = true;
    2541              $text = $stored_file->get_content();
    2542              $output = '<pre>'. format_text($text, FORMAT_MOODLE, $options, $COURSE->id) .'</pre>';
    2543  
    2544              readstring_accel($output, $mimetype, false);
    2545  
    2546          } else {    // Just send it out raw
    2547              readfile_accel($stored_file, $mimetype, !$dontdie);
    2548          }
    2549      }
    2550      if ($dontdie) {
    2551          return;
    2552      }
    2553      die; //no more chars to output!!!
    2554  }
    2555  
    2556  /**
    2557   * Retrieves an array of records from a CSV file and places
    2558   * them into a given table structure
    2559   *
    2560   * @global stdClass $CFG
    2561   * @global moodle_database $DB
    2562   * @param string $file The path to a CSV file
    2563   * @param string $table The table to retrieve columns from
    2564   * @return bool|array Returns an array of CSV records or false
    2565   */
    2566  function get_records_csv($file, $table) {
    2567      global $CFG, $DB;
    2568  
    2569      if (!$metacolumns = $DB->get_columns($table)) {
    2570          return false;
    2571      }
    2572  
    2573      if(!($handle = @fopen($file, 'r'))) {
    2574          print_error('get_records_csv failed to open '.$file);
    2575      }
    2576  
    2577      $fieldnames = fgetcsv($handle, 4096);
    2578      if(empty($fieldnames)) {
    2579          fclose($handle);
    2580          return false;
    2581      }
    2582  
    2583      $columns = array();
    2584  
    2585      foreach($metacolumns as $metacolumn) {
    2586          $ord = array_search($metacolumn->name, $fieldnames);
    2587          if(is_int($ord)) {
    2588              $columns[$metacolumn->name] = $ord;
    2589          }
    2590      }
    2591  
    2592      $rows = array();
    2593  
    2594      while (($data = fgetcsv($handle, 4096)) !== false) {
    2595          $item = new stdClass;
    2596          foreach($columns as $name => $ord) {
    2597              $item->$name = $data[$ord];
    2598          }
    2599          $rows[] = $item;
    2600      }
    2601  
    2602      fclose($handle);
    2603      return $rows;
    2604  }
    2605  
    2606  /**
    2607   * Create a file with CSV contents
    2608   *
    2609   * @global stdClass $CFG
    2610   * @global moodle_database $DB
    2611   * @param string $file The file to put the CSV content into
    2612   * @param array $records An array of records to write to a CSV file
    2613   * @param string $table The table to get columns from
    2614   * @return bool success
    2615   */
    2616  function put_records_csv($file, $records, $table = NULL) {
    2617      global $CFG, $DB;
    2618  
    2619      if (empty($records)) {
    2620          return true;
    2621      }
    2622  
    2623      $metacolumns = NULL;
    2624      if ($table !== NULL && !$metacolumns = $DB->get_columns($table)) {
    2625          return false;
    2626      }
    2627  
    2628      echo "x";
    2629  
    2630      if(!($fp = @fopen($CFG->tempdir.'/'.$file, 'w'))) {
    2631          print_error('put_records_csv failed to open '.$file);
    2632      }
    2633  
    2634      $proto = reset($records);
    2635      if(is_object($proto)) {
    2636          $fields_records = array_keys(get_object_vars($proto));
    2637      }
    2638      else if(is_array($proto)) {
    2639          $fields_records = array_keys($proto);
    2640      }
    2641      else {
    2642          return false;
    2643      }
    2644      echo "x";
    2645  
    2646      if(!empty($metacolumns)) {
    2647          $fields_table = array_map(create_function('$a', 'return $a->name;'), $metacolumns);
    2648          $fields = array_intersect($fields_records, $fields_table);
    2649      }
    2650      else {
    2651          $fields = $fields_records;
    2652      }
    2653  
    2654      fwrite($fp, implode(',', $fields));
    2655      fwrite($fp, "\r\n");
    2656  
    2657      foreach($records as $record) {
    2658          $array  = (array)$record;
    2659          $values = array();
    2660          foreach($fields as $field) {
    2661              if(strpos($array[$field], ',')) {
    2662                  $values[] = '"'.str_replace('"', '\"', $array[$field]).'"';
    2663              }
    2664              else {
    2665                  $values[] = $array[$field];
    2666              }
    2667          }
    2668          fwrite($fp, implode(',', $values)."\r\n");
    2669      }
    2670  
    2671      fclose($fp);
    2672      @chmod($CFG->tempdir.'/'.$file, $CFG->filepermissions);
    2673      return true;
    2674  }
    2675  
    2676  
    2677  /**
    2678   * Recursively delete the file or folder with path $location. That is,
    2679   * if it is a file delete it. If it is a folder, delete all its content
    2680   * then delete it. If $location does not exist to start, that is not
    2681   * considered an error.
    2682   *
    2683   * @param string $location the path to remove.
    2684   * @return bool
    2685   */
    2686  function fulldelete($location) {
    2687      if (empty($location)) {
    2688          // extra safety against wrong param
    2689          return false;
    2690      }
    2691      if (is_dir($location)) {
    2692          if (!$currdir = opendir($location)) {
    2693              return false;
    2694          }
    2695          while (false !== ($file = readdir($currdir))) {
    2696              if ($file <> ".." && $file <> ".") {
    2697                  $fullfile = $location."/".$file;
    2698                  if (is_dir($fullfile)) {
    2699                      if (!fulldelete($fullfile)) {
    2700                          return false;
    2701                      }
    2702                  } else {
    2703                      if (!unlink($fullfile)) {
    2704                          return false;
    2705                      }
    2706                  }
    2707              }
    2708          }
    2709          closedir($currdir);
    2710          if (! rmdir($location)) {
    2711              return false;
    2712          }
    2713  
    2714      } else if (file_exists($location)) {
    2715          if (!unlink($location)) {
    2716              return false;
    2717          }
    2718      }
    2719      return true;
    2720  }
    2721  
    2722  /**
    2723   * Send requested byterange of file.
    2724   *
    2725   * @param resource $handle A file handle
    2726   * @param string $mimetype The mimetype for the output
    2727   * @param array $ranges An array of ranges to send
    2728   * @param string $filesize The size of the content if only one range is used
    2729   */
    2730  function byteserving_send_file($handle, $mimetype, $ranges, $filesize) {
    2731      // better turn off any kind of compression and buffering
    2732      ini_set('zlib.output_compression', 'Off');
    2733  
    2734      $chunksize = 1*(1024*1024); // 1MB chunks - must be less than 2MB!
    2735      if ($handle === false) {
    2736          die;
    2737      }
    2738      if (count($ranges) == 1) { //only one range requested
    2739          $length = $ranges[0][2] - $ranges[0][1] + 1;
    2740          header('HTTP/1.1 206 Partial content');
    2741          header('Content-Length: '.$length);
    2742          header('Content-Range: bytes '.$ranges[0][1].'-'.$ranges[0][2].'/'.$filesize);
    2743          header('Content-Type: '.$mimetype);
    2744  
    2745          while(@ob_get_level()) {
    2746              if (!@ob_end_flush()) {
    2747                  break;
    2748              }
    2749          }
    2750  
    2751          fseek($handle, $ranges[0][1]);
    2752          while (!feof($handle) && $length > 0) {
    2753              core_php_time_limit::raise(60*60); //reset time limit to 60 min - should be enough for 1 MB chunk
    2754              $buffer = fread($handle, ($chunksize < $length ? $chunksize : $length));
    2755              echo $buffer;
    2756              flush();
    2757              $length -= strlen($buffer);
    2758          }
    2759          fclose($handle);
    2760          die;
    2761      } else { // multiple ranges requested - not tested much
    2762          $totallength = 0;
    2763          foreach($ranges as $range) {
    2764              $totallength += strlen($range[0]) + $range[2] - $range[1] + 1;
    2765          }
    2766          $totallength += strlen("\r\n--".BYTESERVING_BOUNDARY."--\r\n");
    2767          header('HTTP/1.1 206 Partial content');
    2768          header('Content-Length: '.$totallength);
    2769          header('Content-Type: multipart/byteranges; boundary='.BYTESERVING_BOUNDARY);
    2770  
    2771          while(@ob_get_level()) {
    2772              if (!@ob_end_flush()) {
    2773                  break;
    2774              }
    2775          }
    2776  
    2777          foreach($ranges as $range) {
    2778              $length = $range[2] - $range[1] + 1;
    2779              echo $range[0];
    2780              fseek($handle, $range[1]);
    2781              while (!feof($handle) && $length > 0) {
    2782                  core_php_time_limit::raise(60*60); //reset time limit to 60 min - should be enough for 1 MB chunk
    2783                  $buffer = fread($handle, ($chunksize < $length ? $chunksize : $length));
    2784                  echo $buffer;
    2785                  flush();
    2786                  $length -= strlen($buffer);
    2787              }
    2788          }
    2789          echo "\r\n--".BYTESERVING_BOUNDARY."--\r\n";
    2790          fclose($handle);
    2791          die;
    2792      }
    2793  }
    2794  
    2795  /**
    2796   * add includes (js and css) into uploaded files
    2797   * before returning them, useful for themes and utf.js includes
    2798   *
    2799   * @global stdClass $CFG
    2800   * @param string $text text to search and replace
    2801   * @return string text with added head includes
    2802   * @todo MDL-21120
    2803   */
    2804  function file_modify_html_header($text) {
    2805      // first look for <head> tag
    2806      global $CFG;
    2807  
    2808      $stylesheetshtml = '';
    2809  /*
    2810      foreach ($CFG->stylesheets as $stylesheet) {
    2811          //TODO: MDL-21120
    2812          $stylesheetshtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
    2813      }
    2814  */
    2815      // TODO The code below is actually a waste of CPU. When MDL-29738 will be implemented it should be re-evaluated too.
    2816  
    2817      preg_match('/\<head\>|\<HEAD\>/', $text, $matches);
    2818      if ($matches) {
    2819          $replacement = '<head>'.$stylesheetshtml;
    2820          $text = preg_replace('/\<head\>|\<HEAD\>/', $replacement, $text, 1);
    2821          return $text;
    2822      }
    2823  
    2824      // if not, look for <html> tag, and stick <head> right after
    2825      preg_match('/\<html\>|\<HTML\>/', $text, $matches);
    2826      if ($matches) {
    2827          // replace <html> tag with <html><head>includes</head>
    2828          $replacement = '<html>'."\n".'<head>'.$stylesheetshtml.'</head>';
    2829          $text = preg_replace('/\<html\>|\<HTML\>/', $replacement, $text, 1);
    2830          return $text;
    2831      }
    2832  
    2833      // if not, look for <body> tag, and stick <head> before body
    2834      preg_match('/\<body\>|\<BODY\>/', $text, $matches);
    2835      if ($matches) {
    2836          $replacement = '<head>'.$stylesheetshtml.'</head>'."\n".'<body>';
    2837          $text = preg_replace('/\<body\>|\<BODY\>/', $replacement, $text, 1);
    2838          return $text;
    2839      }
    2840  
    2841      // if not, just stick a <head> tag at the beginning
    2842      $text = '<head>'.$stylesheetshtml.'</head>'."\n".$text;
    2843      return $text;
    2844  }
    2845  
    2846  /**
    2847   * RESTful cURL class
    2848   *
    2849   * This is a wrapper class for curl, it is quite easy to use:
    2850   * <code>
    2851   * $c = new curl;
    2852   * // enable cache
    2853   * $c = new curl(array('cache'=>true));
    2854   * // enable cookie
    2855   * $c = new curl(array('cookie'=>true));
    2856   * // enable proxy
    2857   * $c = new curl(array('proxy'=>true));
    2858   *
    2859   * // HTTP GET Method
    2860   * $html = $c->get('http://example.com');
    2861   * // HTTP POST Method
    2862   * $html = $c->post('http://example.com/', array('q'=>'words', 'name'=>'moodle'));
    2863   * // HTTP PUT Method
    2864   * $html = $c->put('http://example.com/', array('file'=>'/var/www/test.txt');
    2865   * </code>
    2866   *
    2867   * @package   core_files
    2868   * @category files
    2869   * @copyright Dongsheng Cai <dongsheng@moodle.com>
    2870   * @license   http://www.gnu.org/copyleft/gpl.html GNU Public License
    2871   */
    2872  class curl {
    2873      /** @var bool Caches http request contents */
    2874      public  $cache    = false;
    2875      /** @var bool Uses proxy, null means automatic based on URL */
    2876      public  $proxy    = null;
    2877      /** @var string library version */
    2878      public  $version  = '0.4 dev';
    2879      /** @var array http's response */
    2880      public  $response = array();
    2881      /** @var array Raw response headers, needed for BC in download_file_content(). */
    2882      public $rawresponse = array();
    2883      /** @var array http header */
    2884      public  $header   = array();
    2885      /** @var string cURL information */
    2886      public  $info;
    2887      /** @var string error */
    2888      public  $error;
    2889      /** @var int error code */
    2890      public  $errno;
    2891      /** @var bool use workaround for open_basedir restrictions, to be changed from unit tests only! */
    2892      public $emulateredirects = null;
    2893  
    2894      /** @var array cURL options */
    2895      private $options;
    2896  
    2897      /** @var string Proxy host */
    2898      private $proxy_host = '';
    2899      /** @var string Proxy auth */
    2900      private $proxy_auth = '';
    2901      /** @var string Proxy type */
    2902      private $proxy_type = '';
    2903      /** @var bool Debug mode on */
    2904      private $debug    = false;
    2905      /** @var bool|string Path to cookie file */
    2906      private $cookie   = false;
    2907      /** @var bool tracks multiple headers in response - redirect detection */
    2908      private $responsefinished = false;
    2909  
    2910      /**
    2911       * Curl constructor.
    2912       *
    2913       * Allowed settings are:
    2914       *  proxy: (bool) use proxy server, null means autodetect non-local from url
    2915       *  debug: (bool) use debug output
    2916       *  cookie: (string) path to cookie file, false if none
    2917       *  cache: (bool) use cache
    2918       *  module_cache: (string) type of cache
    2919       *
    2920       * @param array $settings
    2921       */
    2922      public function __construct($settings = array()) {
    2923          global $CFG;
    2924          if (!function_exists('curl_init')) {
    2925              $this->error = 'cURL module must be enabled!';
    2926              trigger_error($this->error, E_USER_ERROR);
    2927              return false;
    2928          }
    2929  
    2930          // All settings of this class should be init here.
    2931          $this->resetopt();
    2932          if (!empty($settings['debug'])) {
    2933              $this->debug = true;
    2934          }
    2935          if (!empty($settings['cookie'])) {
    2936              if($settings['cookie'] === true) {
    2937                  $this->cookie = $CFG->dataroot.'/curl_cookie.txt';
    2938              } else {
    2939                  $this->cookie = $settings['cookie'];
    2940              }
    2941          }
    2942          if (!empty($settings['cache'])) {
    2943              if (class_exists('curl_cache')) {
    2944                  if (!empty($settings['module_cache'])) {
    2945                      $this->cache = new curl_cache($settings['module_cache']);
    2946                  } else {
    2947                      $this->cache = new curl_cache('misc');
    2948                  }
    2949              }
    2950          }
    2951          if (!empty($CFG->proxyhost)) {
    2952              if (empty($CFG->proxyport)) {
    2953                  $this->proxy_host = $CFG->proxyhost;
    2954              } else {
    2955                  $this->proxy_host = $CFG->proxyhost.':'.$CFG->proxyport;
    2956              }
    2957              if (!empty($CFG->proxyuser) and !empty($CFG->proxypassword)) {
    2958                  $this->proxy_auth = $CFG->proxyuser.':'.$CFG->proxypassword;
    2959                  $this->setopt(array(
    2960                              'proxyauth'=> CURLAUTH_BASIC | CURLAUTH_NTLM,
    2961                              'proxyuserpwd'=>$this->proxy_auth));
    2962              }
    2963              if (!empty($CFG->proxytype)) {
    2964                  if ($CFG->proxytype == 'SOCKS5') {
    2965                      $this->proxy_type = CURLPROXY_SOCKS5;
    2966                  } else {
    2967                      $this->proxy_type = CURLPROXY_HTTP;
    2968                      $this->setopt(array('httpproxytunnel'=>false));
    2969                  }
    2970                  $this->setopt(array('proxytype'=>$this->proxy_type));
    2971              }
    2972  
    2973              if (isset($settings['proxy'])) {
    2974                  $this->proxy = $settings['proxy'];
    2975              }
    2976          } else {
    2977              $this->proxy = false;
    2978          }
    2979  
    2980          if (!isset($this->emulateredirects)) {
    2981              $this->emulateredirects = ini_get('open_basedir');
    2982          }
    2983      }
    2984  
    2985      /**
    2986       * Resets the CURL options that have already been set
    2987       */
    2988      public function resetopt() {
    2989          $this->options = array();
    2990          $this->options['CURLOPT_USERAGENT']         = 'MoodleBot/1.0';
    2991          // True to include the header in the output
    2992          $this->options['CURLOPT_HEADER']            = 0;
    2993          // True to Exclude the body from the output
    2994          $this->options['CURLOPT_NOBODY']            = 0;
    2995          // Redirect ny default.
    2996          $this->options['CURLOPT_FOLLOWLOCATION']    = 1;
    2997          $this->options['CURLOPT_MAXREDIRS']         = 10;
    2998          $this->options['CURLOPT_ENCODING']          = '';
    2999          // TRUE to return the transfer as a string of the return
    3000          // value of curl_exec() instead of outputting it out directly.
    3001          $this->options['CURLOPT_RETURNTRANSFER']    = 1;
    3002          $this->options['CURLOPT_SSL_VERIFYPEER']    = 0;
    3003          $this->options['CURLOPT_SSL_VERIFYHOST']    = 2;
    3004          $this->options['CURLOPT_CONNECTTIMEOUT']    = 30;
    3005  
    3006          if ($cacert = self::get_cacert()) {
    3007              $this->options['CURLOPT_CAINFO'] = $cacert;
    3008          }
    3009      }
    3010  
    3011      /**
    3012       * Get the location of ca certificates.
    3013       * @return string absolute file path or empty if default used
    3014       */
    3015      public static function get_cacert() {
    3016          global $CFG;
    3017  
    3018          // Bundle in dataroot always wins.
    3019          if (is_readable("$CFG->dataroot/moodleorgca.crt")) {
    3020              return realpath("$CFG->dataroot/moodleorgca.crt");
    3021          }
    3022  
    3023          // Next comes the default from php.ini
    3024          $cacert = ini_get('curl.cainfo');
    3025          if (!empty($cacert) and is_readable($cacert)) {
    3026              return realpath($cacert);
    3027          }
    3028  
    3029          // Windows PHP does not have any certs, we need to use something.
    3030          if ($CFG->ostype === 'WINDOWS') {
    3031              if (is_readable("$CFG->libdir/cacert.pem")) {
    3032                  return realpath("$CFG->libdir/cacert.pem");
    3033              }
    3034          }
    3035  
    3036          // Use default, this should work fine on all properly configured *nix systems.
    3037          return null;
    3038      }
    3039  
    3040      /**
    3041       * Reset Cookie
    3042       */
    3043      public function resetcookie() {
    3044          if (!empty($this->cookie)) {
    3045              if (is_file($this->cookie)) {
    3046                  $fp = fopen($this->cookie, 'w');
    3047                  if (!empty($fp)) {
    3048                      fwrite($fp, '');
    3049                      fclose($fp);
    3050                  }
    3051              }
    3052          }
    3053      }
    3054  
    3055      /**
    3056       * Set curl options.
    3057       *
    3058       * Do not use the curl constants to define the options, pass a string
    3059       * corresponding to that constant. Ie. to set CURLOPT_MAXREDIRS, pass
    3060       * array('CURLOPT_MAXREDIRS' => 10) or array('maxredirs' => 10) to this method.
    3061       *
    3062       * @param array $options If array is null, this function will reset the options to default value.
    3063       * @return void
    3064       * @throws coding_exception If an option uses constant value instead of option name.
    3065       */
    3066      public function setopt($options = array()) {
    3067          if (is_array($options)) {
    3068              foreach ($options as $name => $val) {
    3069                  if (!is_string($name)) {
    3070                      throw new coding_exception('Curl options should be defined using strings, not constant values.');
    3071                  }
    3072                  if (stripos($name, 'CURLOPT_') === false) {
    3073                      $name = strtoupper('CURLOPT_'.$name);
    3074                  } else {
    3075                      $name = strtoupper($name);
    3076                  }
    3077                  $this->options[$name] = $val;
    3078              }
    3079          }
    3080      }
    3081  
    3082      /**
    3083       * Reset http method
    3084       */
    3085      public function cleanopt() {
    3086          unset($this->options['CURLOPT_HTTPGET']);
    3087          unset($this->options['CURLOPT_POST']);
    3088          unset($this->options['CURLOPT_POSTFIELDS']);
    3089          unset($this->options['CURLOPT_PUT']);
    3090          unset($this->options['CURLOPT_INFILE']);
    3091          unset($this->options['CURLOPT_INFILESIZE']);
    3092          unset($this->options['CURLOPT_CUSTOMREQUEST']);
    3093          unset($this->options['CURLOPT_FILE']);
    3094      }
    3095  
    3096      /**
    3097       * Resets the HTTP Request headers (to prepare for the new request)
    3098       */
    3099      public function resetHeader() {
    3100          $this->header = array();
    3101      }
    3102  
    3103      /**
    3104       * Set HTTP Request Header
    3105       *
    3106       * @param array $header
    3107       */
    3108      public function setHeader($header) {
    3109          if (is_array($header)) {
    3110              foreach ($header as $v) {
    3111                  $this->setHeader($v);
    3112              }
    3113          } else {
    3114              // Remove newlines, they are not allowed in headers.
    3115              $this->header[] = preg_replace('/[\r\n]/', '', $header);
    3116          }
    3117      }
    3118  
    3119      /**
    3120       * Get HTTP Response Headers
    3121       * @return array of arrays
    3122       */
    3123      public function getResponse() {
    3124          return $this->response;
    3125      }
    3126  
    3127      /**
    3128       * Get raw HTTP Response Headers
    3129       * @return array of strings
    3130       */
    3131      public function get_raw_response() {
    3132          return $this->rawresponse;
    3133      }
    3134  
    3135      /**
    3136       * private callback function
    3137       * Formatting HTTP Response Header
    3138       *
    3139       * We only keep the last headers returned. For example during a redirect the
    3140       * redirect headers will not appear in {@link self::getResponse()}, if you need
    3141       * to use those headers, refer to {@link self::get_raw_response()}.
    3142       *
    3143       * @param resource $ch Apparently not used
    3144       * @param string $header
    3145       * @return int The strlen of the header
    3146       */
    3147      private function formatHeader($ch, $header) {
    3148          $this->rawresponse[] = $header;
    3149  
    3150          if (trim($header, "\r\n") === '') {
    3151              // This must be the last header.
    3152              $this->responsefinished = true;
    3153          }
    3154  
    3155          if (strlen($header) > 2) {
    3156              if ($this->responsefinished) {
    3157                  // We still have headers after the supposedly last header, we must be
    3158                  // in a redirect so let's empty the response to keep the last headers.
    3159                  $this->responsefinished = false;
    3160                  $this->response = array();
    3161              }
    3162              list($key, $value) = explode(" ", rtrim($header, "\r\n"), 2);
    3163              $key = rtrim($key, ':');
    3164              if (!empty($this->response[$key])) {
    3165                  if (is_array($this->response[$key])) {
    3166                      $this->response[$key][] = $value;
    3167                  } else {
    3168                      $tmp = $this->response[$key];
    3169                      $this->response[$key] = array();
    3170                      $this->response[$key][] = $tmp;
    3171                      $this->response[$key][] = $value;
    3172  
    3173                  }
    3174              } else {
    3175                  $this->response[$key] = $value;
    3176              }
    3177          }
    3178          return strlen($header);
    3179      }
    3180  
    3181      /**
    3182       * Set options for individual curl instance
    3183       *
    3184       * @param resource $curl A curl handle
    3185       * @param array $options
    3186       * @return resource The curl handle
    3187       */
    3188      private function apply_opt($curl, $options) {
    3189          // Clean up
    3190          $this->cleanopt();
    3191          // set cookie
    3192          if (!empty($this->cookie) || !empty($options['cookie'])) {
    3193              $this->setopt(array('cookiejar'=>$this->cookie,
    3194                              'cookiefile'=>$this->cookie
    3195                               ));
    3196          }
    3197  
    3198          // Bypass proxy if required.
    3199          if ($this->proxy === null) {
    3200              if (!empty($this->options['CURLOPT_URL']) and is_proxybypass($this->options['CURLOPT_URL'])) {
    3201                  $proxy = false;
    3202              } else {
    3203                  $proxy = true;
    3204              }
    3205          } else {
    3206              $proxy = (bool)$this->proxy;
    3207          }
    3208  
    3209          // Set proxy.
    3210          if ($proxy) {
    3211              $options['CURLOPT_PROXY'] = $this->proxy_host;
    3212          } else {
    3213              unset($this->options['CURLOPT_PROXY']);
    3214          }
    3215  
    3216          $this->setopt($options);
    3217  
    3218          // Reset before set options.
    3219          curl_setopt($curl, CURLOPT_HEADERFUNCTION, array(&$this,'formatHeader'));
    3220  
    3221          // Setting the User-Agent based on options provided.
    3222          $useragent = '';
    3223  
    3224          if (!empty($options['CURLOPT_USERAGENT'])) {
    3225              $useragent = $options['CURLOPT_USERAGENT'];
    3226          } else if (!empty($this->options['CURLOPT_USERAGENT'])) {
    3227              $useragent = $this->options['CURLOPT_USERAGENT'];
    3228          } else {
    3229              $useragent = 'MoodleBot/1.0';
    3230          }
    3231  
    3232          // Set headers.
    3233          if (empty($this->header)) {
    3234              $this->setHeader(array(
    3235                  'User-Agent: ' . $useragent,
    3236                  'Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7',
    3237                  'Connection: keep-alive'
    3238                  ));
    3239          } else if (!in_array('User-Agent: ' . $useragent, $this->header)) {
    3240              // Remove old User-Agent if one existed.
    3241              // We have to partial search since we don't know what the original User-Agent is.
    3242              if ($match = preg_grep('/User-Agent.*/', $this->header)) {
    3243                  $key = array_keys($match)[0];
    3244                  unset($this->header[$key]);
    3245              }
    3246              $this->setHeader(array('User-Agent: ' . $useragent));
    3247          }
    3248          curl_setopt($curl, CURLOPT_HTTPHEADER, $this->header);
    3249  
    3250          if ($this->debug) {
    3251              echo '<h1>Options</h1>';
    3252              var_dump($this->options);
    3253              echo '<h1>Header</h1>';
    3254              var_dump($this->header);
    3255          }
    3256  
    3257          // Do not allow infinite redirects.
    3258          if (!isset($this->options['CURLOPT_MAXREDIRS'])) {
    3259              $this->options['CURLOPT_MAXREDIRS'] = 0;
    3260          } else if ($this->options['CURLOPT_MAXREDIRS'] > 100) {
    3261              $this->options['CURLOPT_MAXREDIRS'] = 100;
    3262          } else {
    3263              $this->options['CURLOPT_MAXREDIRS'] = (int)$this->options['CURLOPT_MAXREDIRS'];
    3264          }
    3265  
    3266          // Make sure we always know if redirects expected.
    3267          if (!isset($this->options['CURLOPT_FOLLOWLOCATION'])) {
    3268              $this->options['CURLOPT_FOLLOWLOCATION'] = 0;
    3269          }
    3270  
    3271          // Limit the protocols to HTTP and HTTPS.
    3272          if (defined('CURLOPT_PROTOCOLS')) {
    3273              $this->options['CURLOPT_PROTOCOLS'] = (CURLPROTO_HTTP | CURLPROTO_HTTPS);
    3274              $this->options['CURLOPT_REDIR_PROTOCOLS'] = (CURLPROTO_HTTP | CURLPROTO_HTTPS);
    3275          }
    3276  
    3277          // Set options.
    3278          foreach($this->options as $name => $val) {
    3279              if ($name === 'CURLOPT_FOLLOWLOCATION' and $this->emulateredirects) {
    3280                  // The redirects are emulated elsewhere.
    3281                  curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 0);
    3282                  continue;
    3283              }
    3284              $name = constant($name);
    3285              curl_setopt($curl, $name, $val);
    3286          }
    3287  
    3288          return $curl;
    3289      }
    3290  
    3291      /**
    3292       * Download multiple files in parallel
    3293       *
    3294       * Calls {@link multi()} with specific download headers
    3295       *
    3296       * <code>
    3297       * $c = new curl();
    3298       * $file1 = fopen('a', 'wb');
    3299       * $file2 = fopen('b', 'wb');
    3300       * $c->download(array(
    3301       *     array('url'=>'http://localhost/', 'file'=>$file1),
    3302       *     array('url'=>'http://localhost/20/', 'file'=>$file2)
    3303       * ));
    3304       * fclose($file1);
    3305       * fclose($file2);
    3306       * </code>
    3307       *
    3308       * or
    3309       *
    3310       * <code>
    3311       * $c = new curl();
    3312       * $c->download(array(
    3313       *              array('url'=>'http://localhost/', 'filepath'=>'/tmp/file1.tmp'),
    3314       *              array('url'=>'http://localhost/20/', 'filepath'=>'/tmp/file2.tmp')
    3315       *              ));
    3316       * </code>
    3317       *
    3318       * @param array $requests An array of files to request {
    3319       *                  url => url to download the file [required]
    3320       *                  file => file handler, or
    3321       *                  filepath => file path
    3322       * }
    3323       * If 'file' and 'filepath' parameters are both specified in one request, the
    3324       * open file handle in the 'file' parameter will take precedence and 'filepath'
    3325       * will be ignored.
    3326       *
    3327       * @param array $options An array of options to set
    3328       * @return array An array of results
    3329       */
    3330      public function download($requests, $options = array()) {
    3331          $options['RETURNTRANSFER'] = false;
    3332          return $this->multi($requests, $options);
    3333      }
    3334  
    3335      /**
    3336       * Multi HTTP Requests
    3337       * This function could run multi-requests in parallel.
    3338       *
    3339       * @param array $requests An array of files to request
    3340       * @param array $options An array of options to set
    3341       * @return array An array of results
    3342       */
    3343      protected function multi($requests, $options = array()) {
    3344          $count   = count($requests);
    3345          $handles = array();
    3346          $results = array();
    3347          $main    = curl_multi_init();
    3348          for ($i = 0; $i < $count; $i++) {
    3349              if (!empty($requests[$i]['filepath']) and empty($requests[$i]['file'])) {
    3350                  // open file
    3351                  $requests[$i]['file'] = fopen($requests[$i]['filepath'], 'w');
    3352                  $requests[$i]['auto-handle'] = true;
    3353              }
    3354              foreach($requests[$i] as $n=>$v) {
    3355                  $options[$n] = $v;
    3356              }
    3357              $handles[$i] = curl_init($requests[$i]['url']);
    3358              $this->apply_opt($handles[$i], $options);
    3359              curl_multi_add_handle($main, $handles[$i]);
    3360          }
    3361          $running = 0;
    3362          do {
    3363              curl_multi_exec($main, $running);
    3364          } while($running > 0);
    3365          for ($i = 0; $i < $count; $i++) {
    3366              if (!empty($options['CURLOPT_RETURNTRANSFER'])) {
    3367                  $results[] = true;
    3368              } else {
    3369                  $results[] = curl_multi_getcontent($handles[$i]);
    3370              }
    3371              curl_multi_remove_handle($main, $handles[$i]);
    3372          }
    3373          curl_multi_close($main);
    3374  
    3375          for ($i = 0; $i < $count; $i++) {
    3376              if (!empty($requests[$i]['filepath']) and !empty($requests[$i]['auto-handle'])) {
    3377                  // close file handler if file is opened in this function
    3378                  fclose($requests[$i]['file']);
    3379              }
    3380          }
    3381          return $results;
    3382      }
    3383  
    3384      /**
    3385       * Single HTTP Request
    3386       *
    3387       * @param string $url The URL to request
    3388       * @param array $options
    3389       * @return bool
    3390       */
    3391      protected function request($url, $options = array()) {
    3392          // Set the URL as a curl option.
    3393          $this->setopt(array('CURLOPT_URL' => $url));
    3394  
    3395          // Create curl instance.
    3396          $curl = curl_init();
    3397  
    3398          // Reset here so that the data is valid when result returned from cache.
    3399          $this->info             = array();
    3400          $this->error            = '';
    3401          $this->errno            = 0;
    3402          $this->response         = array();
    3403          $this->rawresponse      = array();
    3404          $this->responsefinished = false;
    3405  
    3406          $this->apply_opt($curl, $options);
    3407          if ($this->cache && $ret = $this->cache->get($this->options)) {
    3408              return $ret;
    3409          }
    3410  
    3411          $ret = curl_exec($curl);
    3412          $this->info  = curl_getinfo($curl);
    3413          $this->error = curl_error($curl);
    3414          $this->errno = curl_errno($curl);
    3415          // Note: $this->response and $this->rawresponse are filled by $hits->formatHeader callback.
    3416  
    3417          if ($this->emulateredirects and $this->options['CURLOPT_FOLLOWLOCATION'] and $this->info['http_code'] != 200) {
    3418              $redirects = 0;
    3419  
    3420              while($redirects <= $this->options['CURLOPT_MAXREDIRS']) {
    3421  
    3422                  if ($this->info['http_code'] == 301) {
    3423                      // Moved Permanently - repeat the same request on new URL.
    3424  
    3425                  } else if ($this->info['http_code'] == 302) {
    3426                      // Found - the standard redirect - repeat the same request on new URL.
    3427  
    3428                  } else if ($this->info['http_code'] == 303) {
    3429                      // 303 See Other - repeat only if GET, do not bother with POSTs.
    3430                      if (empty($this->options['CURLOPT_HTTPGET'])) {
    3431                          break;
    3432                      }
    3433  
    3434                  } else if ($this->info['http_code'] == 307) {
    3435                      // Temporary Redirect - must repeat using the same request type.
    3436  
    3437                  } else if ($this->info['http_code'] == 308) {
    3438                      // Permanent Redirect - must repeat using the same request type.
    3439  
    3440                  } else {
    3441                      // Some other http code means do not retry!
    3442                      break;
    3443                  }
    3444  
    3445                  $redirects++;
    3446  
    3447                  $redirecturl = null;
    3448                  if (isset($this->info['redirect_url'])) {
    3449                      if (preg_match('|^https?://|i', $this->info['redirect_url'])) {
    3450                          $redirecturl = $this->info['redirect_url'];
    3451                      }
    3452                  }
    3453                  if (!$redirecturl) {
    3454                      foreach ($this->response as $k => $v) {
    3455                          if (strtolower($k) === 'location') {
    3456                              $redirecturl = $v;
    3457                              break;
    3458                          }
    3459                      }
    3460                      if (preg_match('|^https?://|i', $redirecturl)) {
    3461                          // Great, this is the correct location format!
    3462  
    3463                      } else if ($redirecturl) {
    3464                          $current = curl_getinfo($curl, CURLINFO_EFFECTIVE_URL);
    3465                          if (strpos($redirecturl, '/') === 0) {
    3466                              // Relative to server root - just guess.
    3467                              $pos = strpos('/', $current, 8);
    3468                              if ($pos === false) {
    3469                                  $redirecturl = $current.$redirecturl;
    3470                              } else {
    3471                                  $redirecturl = substr($current, 0, $pos).$redirecturl;
    3472                              }
    3473                          } else {
    3474                              // Relative to current script.
    3475                              $redirecturl = dirname($current).'/'.$redirecturl;
    3476                          }
    3477                      }
    3478                  }
    3479  
    3480                  curl_setopt($curl, CURLOPT_URL, $redirecturl);
    3481                  $ret = curl_exec($curl);
    3482  
    3483                  $this->info  = curl_getinfo($curl);
    3484                  $this->error = curl_error($curl);
    3485                  $this->errno = curl_errno($curl);
    3486  
    3487                  $this->info['redirect_count'] = $redirects;
    3488  
    3489                  if ($this->info['http_code'] === 200) {
    3490                      // Finally this is what we wanted.
    3491                      break;
    3492                  }
    3493                  if ($this->errno != CURLE_OK) {
    3494                      // Something wrong is going on.
    3495                      break;
    3496                  }
    3497              }
    3498              if ($redirects > $this->options['CURLOPT_MAXREDIRS']) {
    3499                  $this->errno = CURLE_TOO_MANY_REDIRECTS;
    3500                  $this->error = 'Maximum ('.$this->options['CURLOPT_MAXREDIRS'].') redirects followed';
    3501              }
    3502          }
    3503  
    3504          if ($this->cache) {
    3505              $this->cache->set($this->options, $ret);
    3506          }
    3507  
    3508          if ($this->debug) {
    3509              echo '<h1>Return Data</h1>';
    3510              var_dump($ret);
    3511              echo '<h1>Info</h1>';
    3512              var_dump($this->info);
    3513              echo '<h1>Error</h1>';
    3514              var_dump($this->error);
    3515          }
    3516  
    3517          curl_close($curl);
    3518  
    3519          if (empty($this->error)) {
    3520              return $ret;
    3521          } else {
    3522              return $this->error;
    3523              // exception is not ajax friendly
    3524              //throw new moodle_exception($this->error, 'curl');
    3525          }
    3526      }
    3527  
    3528      /**
    3529       * HTTP HEAD method
    3530       *
    3531       * @see request()
    3532       *
    3533       * @param string $url
    3534       * @param array $options
    3535       * @return bool
    3536       */
    3537      public function head($url, $options = array()) {
    3538          $options['CURLOPT_HTTPGET'] = 0;
    3539          $options['CURLOPT_HEADER']  = 1;
    3540          $options['CURLOPT_NOBODY']  = 1;
    3541          return $this->request($url, $options);
    3542      }
    3543  
    3544      /**
    3545       * HTTP POST method
    3546       *
    3547       * @param string $url
    3548       * @param array|string $params
    3549       * @param array $options
    3550       * @return bool
    3551       */
    3552      public function post($url, $params = '', $options = array()) {
    3553          $options['CURLOPT_POST']       = 1;
    3554          if (is_array($params)) {
    3555              $this->_tmp_file_post_params = array();
    3556              foreach ($params as $key => $value) {
    3557                  if ($value instanceof stored_file) {
    3558                      $value->add_to_curl_request($this, $key);
    3559                  } else {
    3560                      $this->_tmp_file_post_params[$key] = $value;
    3561                  }
    3562              }
    3563              $options['CURLOPT_POSTFIELDS'] = $this->_tmp_file_post_params;
    3564              unset($this->_tmp_file_post_params);
    3565          } else {
    3566              // $params is the raw post data
    3567              $options['CURLOPT_POSTFIELDS'] = $params;
    3568          }
    3569          return $this->request($url, $options);
    3570      }
    3571  
    3572      /**
    3573       * HTTP GET method
    3574       *
    3575       * @param string $url
    3576       * @param array $params
    3577       * @param array $options
    3578       * @return bool
    3579       */
    3580      public function get($url, $params = array(), $options = array()) {
    3581          $options['CURLOPT_HTTPGET'] = 1;
    3582  
    3583          if (!empty($params)) {
    3584              $url .= (stripos($url, '?') !== false) ? '&' : '?';
    3585              $url .= http_build_query($params, '', '&');
    3586          }
    3587          return $this->request($url, $options);
    3588      }
    3589  
    3590      /**
    3591       * Downloads one file and writes it to the specified file handler
    3592       *
    3593       * <code>
    3594       * $c = new curl();
    3595       * $file = fopen('savepath', 'w');
    3596       * $result = $c->download_one('http://localhost/', null,
    3597       *   array('file' => $file, 'timeout' => 5, 'followlocation' => true, 'maxredirs' => 3));
    3598       * fclose($file);
    3599       * $download_info = $c->get_info();
    3600       * if ($result === true) {
    3601       *   // file downloaded successfully
    3602       * } else {
    3603       *   $error_text = $result;
    3604       *   $error_code = $c->get_errno();
    3605       * }
    3606       * </code>
    3607       *
    3608       * <code>
    3609       * $c = new curl();
    3610       * $result = $c->download_one('http://localhost/', null,
    3611       *   array('filepath' => 'savepath', 'timeout' => 5, 'followlocation' => true, 'maxredirs' => 3));
    3612       * // ... see above, no need to close handle and remove file if unsuccessful
    3613       * </code>
    3614       *
    3615       * @param string $url
    3616       * @param array|null $params key-value pairs to be added to $url as query string
    3617       * @param array $options request options. Must include either 'file' or 'filepath'
    3618       * @return bool|string true on success or error string on failure
    3619       */
    3620      public function download_one($url, $params, $options = array()) {
    3621          $options['CURLOPT_HTTPGET'] = 1;
    3622          if (!empty($params)) {
    3623              $url .= (stripos($url, '?') !== false) ? '&' : '?';
    3624              $url .= http_build_query($params, '', '&');
    3625          }
    3626          if (!empty($options['filepath']) && empty($options['file'])) {
    3627              // open file
    3628              if (!($options['file'] = fopen($options['filepath'], 'w'))) {
    3629                  $this->errno = 100;
    3630                  return get_string('cannotwritefile', 'error', $options['filepath']);
    3631              }
    3632              $filepath = $options['filepath'];
    3633          }
    3634          unset($options['filepath']);
    3635          $result = $this->request($url, $options);
    3636          if (isset($filepath)) {
    3637              fclose($options['file']);
    3638              if ($result !== true) {
    3639                  unlink($filepath);
    3640              }
    3641          }
    3642          return $result;
    3643      }
    3644  
    3645      /**
    3646       * HTTP PUT method
    3647       *
    3648       * @param string $url
    3649       * @param array $params
    3650       * @param array $options
    3651       * @return bool
    3652       */
    3653      public function put($url, $params = array(), $options = array()) {
    3654          $file = $params['file'];
    3655          if (!is_file($file)) {
    3656              return null;
    3657          }
    3658          $fp   = fopen($file, 'r');
    3659          $size = filesize($file);
    3660          $options['CURLOPT_PUT']        = 1;
    3661          $options['CURLOPT_INFILESIZE'] = $size;
    3662          $options['CURLOPT_INFILE']     = $fp;
    3663          if (!isset($this->options['CURLOPT_USERPWD'])) {
    3664              $this->setopt(array('CURLOPT_USERPWD'=>'anonymous: noreply@moodle.org'));
    3665          }
    3666          $ret = $this->request($url, $options);
    3667          fclose($fp);
    3668          return $ret;
    3669      }
    3670  
    3671      /**
    3672       * HTTP DELETE method
    3673       *
    3674       * @param string $url
    3675       * @param array $param
    3676       * @param array $options
    3677       * @return bool
    3678       */
    3679      public function delete($url, $param = array(), $options = array()) {
    3680          $options['CURLOPT_CUSTOMREQUEST'] = 'DELETE';
    3681          if (!isset($options['CURLOPT_USERPWD'])) {
    3682              $options['CURLOPT_USERPWD'] = 'anonymous: noreply@moodle.org';
    3683          }
    3684          $ret = $this->request($url, $options);
    3685          return $ret;
    3686      }
    3687  
    3688      /**
    3689       * HTTP TRACE method
    3690       *
    3691       * @param string $url
    3692       * @param array $options
    3693       * @return bool
    3694       */
    3695      public function trace($url, $options = array()) {
    3696          $options['CURLOPT_CUSTOMREQUEST'] = 'TRACE';
    3697          $ret = $this->request($url, $options);
    3698          return $ret;
    3699      }
    3700  
    3701      /**
    3702       * HTTP OPTIONS method
    3703       *
    3704       * @param string $url
    3705       * @param array $options
    3706       * @return bool
    3707       */
    3708      public function options($url, $options = array()) {
    3709          $options['CURLOPT_CUSTOMREQUEST'] = 'OPTIONS';
    3710          $ret = $this->request($url, $options);
    3711          return $ret;
    3712      }
    3713  
    3714      /**
    3715       * Get curl information
    3716       *
    3717       * @return string
    3718       */
    3719      public function get_info() {
    3720          return $this->info;
    3721      }
    3722  
    3723      /**
    3724       * Get curl error code
    3725       *
    3726       * @return int
    3727       */
    3728      public function get_errno() {
    3729          return $this->errno;
    3730      }
    3731  
    3732      /**
    3733       * When using a proxy, an additional HTTP response code may appear at
    3734       * the start of the header. For example, when using https over a proxy
    3735       * there may be 'HTTP/1.0 200 Connection Established'. Other codes are
    3736       * also possible and some may come with their own headers.
    3737       *
    3738       * If using the return value containing all headers, this function can be
    3739       * called to remove unwanted doubles.
    3740       *
    3741       * Note that it is not possible to distinguish this situation from valid
    3742       * data unless you know the actual response part (below the headers)
    3743       * will not be included in this string, or else will not 'look like' HTTP
    3744       * headers. As a result it is not safe to call this function for general
    3745       * data.
    3746       *
    3747       * @param string $input Input HTTP response
    3748       * @return string HTTP response with additional headers stripped if any
    3749       */
    3750      public static function strip_double_headers($input) {
    3751          // I have tried to make this regular expression as specific as possible
    3752          // to avoid any case where it does weird stuff if you happen to put
    3753          // HTTP/1.1 200 at the start of any line in your RSS file. This should
    3754          // also make it faster because it can abandon regex processing as soon
    3755          // as it hits something that doesn't look like an http header. The
    3756          // header definition is taken from RFC 822, except I didn't support
    3757          // folding which is never used in practice.
    3758          $crlf = "\r\n";
    3759          return preg_replace(
    3760                  // HTTP version and status code (ignore value of code).
    3761                  '~^HTTP/1\..*' . $crlf .
    3762                  // Header name: character between 33 and 126 decimal, except colon.
    3763                  // Colon. Header value: any character except \r and \n. CRLF.
    3764                  '(?:[\x21-\x39\x3b-\x7e]+:[^' . $crlf . ']+' . $crlf . ')*' .
    3765                  // Headers are terminated by another CRLF (blank line).
    3766                  $crlf .
    3767                  // Second HTTP status code, this time must be 200.
    3768                  '(HTTP/1.[01] 200 )~', '$1', $input);
    3769      }
    3770  }
    3771  
    3772  /**
    3773   * This class is used by cURL class, use case:
    3774   *
    3775   * <code>
    3776   * $CFG->repositorycacheexpire = 120;
    3777   * $CFG->curlcache = 120;
    3778   *
    3779   * $c = new curl(array('cache'=>true), 'module_cache'=>'repository');
    3780   * $ret = $c->get('http://www.google.com');
    3781   * </code>
    3782   *
    3783   * @package   core_files
    3784   * @copyright Dongsheng Cai <dongsheng@moodle.com>
    3785   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    3786   */
    3787  class curl_cache {
    3788      /** @var string Path to cache directory */
    3789      public $dir = '';
    3790  
    3791      /**
    3792       * Constructor
    3793       *
    3794       * @global stdClass $CFG
    3795       * @param string $module which module is using curl_cache
    3796       */
    3797      public function __construct($module = 'repository') {
    3798          global $CFG;
    3799          if (!empty($module)) {
    3800              $this->dir = $CFG->cachedir.'/'.$module.'/';
    3801          } else {
    3802              $this->dir = $CFG->cachedir.'/misc/';
    3803          }
    3804          if (!file_exists($this->dir)) {
    3805              mkdir($this->dir, $CFG->directorypermissions, true);
    3806          }
    3807          if ($module == 'repository') {
    3808              if (empty($CFG->repositorycacheexpire)) {
    3809                  $CFG->repositorycacheexpire = 120;
    3810              }
    3811              $this->ttl = $CFG->repositorycacheexpire;
    3812          } else {
    3813              if (empty($CFG->curlcache)) {
    3814                  $CFG->curlcache = 120;
    3815              }
    3816              $this->ttl = $CFG->curlcache;
    3817          }
    3818      }
    3819  
    3820      /**
    3821       * Get cached value
    3822       *
    3823       * @global stdClass $CFG
    3824       * @global stdClass $USER
    3825       * @param mixed $param
    3826       * @return bool|string
    3827       */
    3828      public function get($param) {
    3829          global $CFG, $USER;
    3830          $this->cleanup($this->ttl);
    3831          $filename = 'u'.$USER->id.'_'.md5(serialize($param));
    3832          if(file_exists($this->dir.$filename)) {
    3833              $lasttime = filemtime($this->dir.$filename);
    3834              if (time()-$lasttime > $this->ttl) {
    3835                  return false;
    3836              } else {
    3837                  $fp = fopen($this->dir.$filename, 'r');
    3838                  $size = filesize($this->dir.$filename);
    3839                  $content = fread($fp, $size);
    3840                  return unserialize($content);
    3841              }
    3842          }
    3843          return false;
    3844      }
    3845  
    3846      /**
    3847       * Set cache value
    3848       *
    3849       * @global object $CFG
    3850       * @global object $USER
    3851       * @param mixed $param
    3852       * @param mixed $val
    3853       */
    3854      public function set($param, $val) {
    3855          global $CFG, $USER;
    3856          $filename = 'u'.$USER->id.'_'.md5(serialize($param));
    3857          $fp = fopen($this->dir.$filename, 'w');
    3858          fwrite($fp, serialize($val));
    3859          fclose($fp);
    3860          @chmod($this->dir.$filename, $CFG->filepermissions);
    3861      }
    3862  
    3863      /**
    3864       * Remove cache files
    3865       *
    3866       * @param int $expire The number of seconds before expiry
    3867       */
    3868      public function cleanup($expire) {
    3869          if ($dir = opendir($this->dir)) {
    3870              while (false !== ($file = readdir($dir))) {
    3871                  if(!is_dir($file) && $file != '.' && $file != '..') {
    3872                      $lasttime = @filemtime($this->dir.$file);
    3873                      if (time() - $lasttime > $expire) {
    3874                          @unlink($this->dir.$file);
    3875                      }
    3876                  }
    3877              }
    3878              closedir($dir);
    3879          }
    3880      }
    3881      /**
    3882       * delete current user's cache file
    3883       *
    3884       * @global object $CFG
    3885       * @global object $USER
    3886       */
    3887      public function refresh() {
    3888          global $CFG, $USER;
    3889          if ($dir = opendir($this->dir)) {
    3890              while (false !== ($file = readdir($dir))) {
    3891                  if (!is_dir($file) && $file != '.' && $file != '..') {
    3892                      if (strpos($file, 'u'.$USER->id.'_') !== false) {
    3893                          @unlink($this->dir.$file);
    3894                      }
    3895                  }
    3896              }
    3897          }
    3898      }
    3899  }
    3900  
    3901  /**
    3902   * This function delegates file serving to individual plugins
    3903   *
    3904   * @param string $relativepath
    3905   * @param bool $forcedownload
    3906   * @param null|string $preview the preview mode, defaults to serving the original file
    3907   * @todo MDL-31088 file serving improments
    3908   */
    3909  function file_pluginfile($relativepath, $forcedownload, $preview = null) {
    3910      global $DB, $CFG, $USER;
    3911      // relative path must start with '/'
    3912      if (!$relativepath) {
    3913          print_error('invalidargorconf');
    3914      } else if ($relativepath[0] != '/') {
    3915          print_error('pathdoesnotstartslash');
    3916      }
    3917  
    3918      // extract relative path components
    3919      $args = explode('/', ltrim($relativepath, '/'));
    3920  
    3921      if (count($args) < 3) { // always at least context, component and filearea
    3922          print_error('invalidarguments');
    3923      }
    3924  
    3925      $contextid = (int)array_shift($args);
    3926      $component = clean_param(array_shift($args), PARAM_COMPONENT);
    3927      $filearea  = clean_param(array_shift($args), PARAM_AREA);
    3928  
    3929      list($context, $course, $cm) = get_context_info_array($contextid);
    3930  
    3931      $fs = get_file_storage();
    3932  
    3933      // ========================================================================================================================
    3934      if ($component === 'blog') {
    3935          // Blog file serving
    3936          if ($context->contextlevel != CONTEXT_SYSTEM) {
    3937              send_file_not_found();
    3938          }
    3939          if ($filearea !== 'attachment' and $filearea !== 'post') {
    3940              send_file_not_found();
    3941          }
    3942  
    3943          if (empty($CFG->enableblogs)) {
    3944              print_error('siteblogdisable', 'blog');
    3945          }
    3946  
    3947          $entryid = (int)array_shift($args);
    3948          if (!$entry = $DB->get_record('post', array('module'=>'blog', 'id'=>$entryid))) {
    3949              send_file_not_found();
    3950          }
    3951          if ($CFG->bloglevel < BLOG_GLOBAL_LEVEL) {
    3952              require_login();
    3953              if (isguestuser()) {
    3954                  print_error('noguest');
    3955              }
    3956              if ($CFG->bloglevel == BLOG_USER_LEVEL) {
    3957                  if ($USER->id != $entry->userid) {
    3958                      send_file_not_found();
    3959                  }
    3960              }
    3961          }
    3962  
    3963          if ($entry->publishstate === 'public') {
    3964              if ($CFG->forcelogin) {
    3965                  require_login();
    3966              }
    3967  
    3968          } else if ($entry->publishstate === 'site') {
    3969              require_login();
    3970              //ok
    3971          } else if ($entry->publishstate === 'draft') {
    3972              require_login();
    3973              if ($USER->id != $entry->userid) {
    3974                  send_file_not_found();
    3975              }
    3976          }
    3977  
    3978          $filename = array_pop($args);
    3979          $filepath = $args ? '/'.implode('/', $args).'/' : '/';
    3980  
    3981          if (!$file = $fs->get_file($context->id, $component, $filearea, $entryid, $filepath, $filename) or $file->is_directory()) {
    3982              send_file_not_found();
    3983          }
    3984  
    3985          send_stored_file($file, 10*60, 0, true, array('preview' => $preview)); // download MUST be forced - security!
    3986  
    3987      // ========================================================================================================================
    3988      } else if ($component === 'grade') {
    3989          if (($filearea === 'outcome' or $filearea === 'scale') and $context->contextlevel == CONTEXT_SYSTEM) {
    3990              // Global gradebook files
    3991              if ($CFG->forcelogin) {
    3992                  require_login();
    3993              }
    3994  
    3995              $fullpath = "/$context->id/$component/$filearea/".implode('/', $args);
    3996  
    3997              if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
    3998                  send_file_not_found();
    3999              }
    4000  
    4001              \core\session\manager::write_close(); // Unlock session during file serving.
    4002              send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
    4003  
    4004          } else if ($filearea === 'feedback' and $context->contextlevel == CONTEXT_COURSE) {
    4005              //TODO: nobody implemented this yet in grade edit form!!
    4006              send_file_not_found();
    4007  
    4008              if ($CFG->forcelogin || $course->id != SITEID) {
    4009                  require_login($course);
    4010              }
    4011  
    4012              $fullpath = "/$context->id/$component/$filearea/".implode('/', $args);
    4013  
    4014              if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
    4015                  send_file_not_found();
    4016              }
    4017  
    4018              \core\session\manager::write_close(); // Unlock session during file serving.
    4019              send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
    4020          } else {
    4021              send_file_not_found();
    4022          }
    4023  
    4024      // ========================================================================================================================
    4025      } else if ($component === 'tag') {
    4026          if ($filearea === 'description' and $context->contextlevel == CONTEXT_SYSTEM) {
    4027  
    4028              // All tag descriptions are going to be public but we still need to respect forcelogin
    4029              if ($CFG->forcelogin) {
    4030                  require_login();
    4031              }
    4032  
    4033              $fullpath = "/$context->id/tag/description/".implode('/', $args);
    4034  
    4035              if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
    4036                  send_file_not_found();
    4037              }
    4038  
    4039              \core\session\manager::write_close(); // Unlock session during file serving.
    4040              send_stored_file($file, 60*60, 0, true, array('preview' => $preview));
    4041  
    4042          } else {
    4043              send_file_not_found();
    4044          }
    4045      // ========================================================================================================================
    4046      } else if ($component === 'badges') {
    4047          require_once($CFG->libdir . '/badgeslib.php');
    4048  
    4049          $badgeid = (int)array_shift($args);
    4050          $badge = new badge($badgeid);
    4051          $filename = array_pop($args);
    4052  
    4053          if ($filearea === 'badgeimage') {
    4054              if ($filename !== 'f1' && $filename !== 'f2') {
    4055                  send_file_not_found();
    4056              }
    4057              if (!$file = $fs->get_file($context->id, 'badges', 'badgeimage', $badge->id, '/', $filename.'.png')) {
    4058                  send_file_not_found();
    4059              }
    4060  
    4061              \core\session\manager::write_close();
    4062              send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
    4063          } else if ($filearea === 'userbadge'  and $context->contextlevel == CONTEXT_USER) {
    4064              if (!$file = $fs->get_file($context->id, 'badges', 'userbadge', $badge->id, '/', $filename.'.png')) {
    4065                  send_file_not_found();
    4066              }
    4067  
    4068              \core\session\manager::write_close();
    4069              send_stored_file($file, 60*60, 0, true, array('preview' => $preview));
    4070          }
    4071      // ========================================================================================================================
    4072      } else if ($component === 'calendar') {
    4073          if ($filearea === 'event_description'  and $context->contextlevel == CONTEXT_SYSTEM) {
    4074  
    4075              // All events here are public the one requirement is that we respect forcelogin
    4076              if ($CFG->forcelogin) {
    4077                  require_login();
    4078              }
    4079  
    4080              // Get the event if from the args array
    4081              $eventid = array_shift($args);
    4082  
    4083              // Load the event from the database
    4084              if (!$event = $DB->get_record('event', array('id'=>(int)$eventid, 'eventtype'=>'site'))) {
    4085                  send_file_not_found();
    4086              }
    4087  
    4088              // Get the file and serve if successful
    4089              $filename = array_pop($args);
    4090              $filepath = $args ? '/'.implode('/', $args).'/' : '/';
    4091              if (!$file = $fs->get_file($context->id, $component, $filearea, $eventid, $filepath, $filename) or $file->is_directory()) {
    4092                  send_file_not_found();
    4093              }
    4094  
    4095              \core\session\manager::write_close(); // Unlock session during file serving.
    4096              send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
    4097  
    4098          } else if ($filearea === 'event_description' and $context->contextlevel == CONTEXT_USER) {
    4099  
    4100              // Must be logged in, if they are not then they obviously can't be this user
    4101              require_login();
    4102  
    4103              // Don't want guests here, potentially saves a DB call
    4104              if (isguestuser()) {
    4105                  send_file_not_found();
    4106              }
    4107  
    4108              // Get the event if from the args array
    4109              $eventid = array_shift($args);
    4110  
    4111              // Load the event from the database - user id must match
    4112              if (!$event = $DB->get_record('event', array('id'=>(int)$eventid, 'userid'=>$USER->id, 'eventtype'=>'user'))) {
    4113                  send_file_not_found();
    4114              }
    4115  
    4116              // Get the file and serve if successful
    4117              $filename = array_pop($args);
    4118              $filepath = $args ? '/'.implode('/', $args).'/' : '/';
    4119              if (!$file = $fs->get_file($context->id, $component, $filearea, $eventid, $filepath, $filename) or $file->is_directory()) {
    4120                  send_file_not_found();
    4121              }
    4122  
    4123              \core\session\manager::write_close(); // Unlock session during file serving.
    4124              send_stored_file($file, 0, 0, true, array('preview' => $preview));
    4125  
    4126          } else if ($filearea === 'event_description' and $context->contextlevel == CONTEXT_COURSE) {
    4127  
    4128              // Respect forcelogin and require login unless this is the site.... it probably
    4129              // should NEVER be the site
    4130              if ($CFG->forcelogin || $course->id != SITEID) {
    4131                  require_login($course);
    4132              }
    4133  
    4134              // Must be able to at least view the course. This does not apply to the front page.
    4135              if ($course->id != SITEID && (!is_enrolled($context)) && (!is_viewing($context))) {
    4136                  //TODO: hmm, do we really want to block guests here?
    4137                  send_file_not_found();
    4138              }
    4139  
    4140              // Get the event id
    4141              $eventid = array_shift($args);
    4142  
    4143              // Load the event from the database we need to check whether it is
    4144              // a) valid course event
    4145              // b) a group event
    4146              // Group events use the course context (there is no group context)
    4147              if (!$event = $DB->get_record('event', array('id'=>(int)$eventid, 'courseid'=>$course->id))) {
    4148                  send_file_not_found();
    4149              }
    4150  
    4151              // If its a group event require either membership of view all groups capability
    4152              if ($event->eventtype === 'group') {
    4153                  if (!has_capability('moodle/site:accessallgroups', $context) && !groups_is_member($event->groupid, $USER->id)) {
    4154                      send_file_not_found();
    4155                  }
    4156              } else if ($event->eventtype === 'course' || $event->eventtype === 'site') {
    4157                  // Ok. Please note that the event type 'site' still uses a course context.
    4158              } else {
    4159                  // Some other type.
    4160                  send_file_not_found();
    4161              }
    4162  
    4163              // If we get this far we can serve the file
    4164              $filename = array_pop($args);
    4165              $filepath = $args ? '/'.implode('/', $args).'/' : '/';
    4166              if (!$file = $fs->get_file($context->id, $component, $filearea, $eventid, $filepath, $filename) or $file->is_directory()) {
    4167                  send_file_not_found();
    4168              }
    4169  
    4170              \core\session\manager::write_close(); // Unlock session during file serving.
    4171              send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
    4172  
    4173          } else {
    4174              send_file_not_found();
    4175          }
    4176  
    4177      // ========================================================================================================================
    4178      } else if ($component === 'user') {
    4179          if ($filearea === 'icon' and $context->contextlevel == CONTEXT_USER) {
    4180              if (count($args) == 1) {
    4181                  $themename = theme_config::DEFAULT_THEME;
    4182                  $filename = array_shift($args);
    4183              } else {
    4184                  $themename = array_shift($args);
    4185                  $filename = array_shift($args);
    4186              }
    4187  
    4188              // fix file name automatically
    4189              if ($filename !== 'f1' and $filename !== 'f2' and $filename !== 'f3') {
    4190                  $filename = 'f1';
    4191              }
    4192  
    4193              if ((!empty($CFG->forcelogin) and !isloggedin()) ||
    4194                      (!empty($CFG->forceloginforprofileimage) && (!isloggedin() || isguestuser()))) {
    4195                  // protect images if login required and not logged in;
    4196                  // also if login is required for profile images and is not logged in or guest
    4197                  // do not use require_login() because it is expensive and not suitable here anyway
    4198                  $theme = theme_config::load($themename);
    4199                  redirect($theme->pix_url('u/'.$filename, 'moodle')); // intentionally not cached
    4200              }
    4201  
    4202              if (!$file = $fs->get_file($context->id, 'user', 'icon', 0, '/', $filename.'.png')) {
    4203                  if (!$file = $fs->get_file($context->id, 'user', 'icon', 0, '/', $filename.'.jpg')) {
    4204                      if ($filename === 'f3') {
    4205                          // f3 512x512px was introduced in 2.3, there might be only the smaller version.
    4206                          if (!$file = $fs->get_file($context->id, 'user', 'icon', 0, '/', 'f1.png')) {
    4207                              $file = $fs->get_file($context->id, 'u