mirror of
https://github.com/moodle/moodle.git
synced 2025-03-17 14:10:08 +01:00
This commit affects mostly qtypes. Other standard components either do not have pluginfile handler or have been already updated to the new API.
4007 lines
150 KiB
PHP
4007 lines
150 KiB
PHP
<?php
|
|
// This file is part of Moodle - http://moodle.org/
|
|
//
|
|
// Moodle is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Moodle is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
/**
|
|
* Functions for file handling.
|
|
*
|
|
* @package core_files
|
|
* @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com)
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
*/
|
|
|
|
defined('MOODLE_INTERNAL') || die();
|
|
|
|
/**
|
|
* BYTESERVING_BOUNDARY - string unique string constant.
|
|
*/
|
|
define('BYTESERVING_BOUNDARY', 's1k2o3d4a5k6s7');
|
|
|
|
require_once("$CFG->libdir/filestorage/file_exceptions.php");
|
|
require_once("$CFG->libdir/filestorage/file_storage.php");
|
|
require_once("$CFG->libdir/filestorage/zip_packer.php");
|
|
require_once("$CFG->libdir/filebrowser/file_browser.php");
|
|
|
|
/**
|
|
* Encodes file serving url
|
|
*
|
|
* @deprecated use moodle_url factory methods instead
|
|
*
|
|
* @todo MDL-31071 deprecate this function
|
|
* @global stdClass $CFG
|
|
* @param string $urlbase
|
|
* @param string $path /filearea/itemid/dir/dir/file.exe
|
|
* @param bool $forcedownload
|
|
* @param bool $https https url required
|
|
* @return string encoded file url
|
|
*/
|
|
function file_encode_url($urlbase, $path, $forcedownload=false, $https=false) {
|
|
global $CFG;
|
|
|
|
//TODO: deprecate this
|
|
|
|
if ($CFG->slasharguments) {
|
|
$parts = explode('/', $path);
|
|
$parts = array_map('rawurlencode', $parts);
|
|
$path = implode('/', $parts);
|
|
$return = $urlbase.$path;
|
|
if ($forcedownload) {
|
|
$return .= '?forcedownload=1';
|
|
}
|
|
} else {
|
|
$path = rawurlencode($path);
|
|
$return = $urlbase.'?file='.$path;
|
|
if ($forcedownload) {
|
|
$return .= '&forcedownload=1';
|
|
}
|
|
}
|
|
|
|
if ($https) {
|
|
$return = str_replace('http://', 'https://', $return);
|
|
}
|
|
|
|
return $return;
|
|
}
|
|
|
|
/**
|
|
* Prepares 'editor' formslib element from data in database
|
|
*
|
|
* The passed $data record must contain field foobar, foobarformat and optionally foobartrust. This
|
|
* function then copies the embedded files into draft area (assigning itemids automatically),
|
|
* creates the form element foobar_editor and rewrites the URLs so the embedded images can be
|
|
* displayed.
|
|
* In your mform definition, you must have an 'editor' element called foobar_editor. Then you call
|
|
* your mform's set_data() supplying the object returned by this function.
|
|
*
|
|
* @category files
|
|
* @param stdClass $data database field that holds the html text with embedded media
|
|
* @param string $field the name of the database field that holds the html text with embedded media
|
|
* @param array $options editor options (like maxifiles, maxbytes etc.)
|
|
* @param stdClass $context context of the editor
|
|
* @param string $component
|
|
* @param string $filearea file area name
|
|
* @param int $itemid item id, required if item exists
|
|
* @return stdClass modified data object
|
|
*/
|
|
function file_prepare_standard_editor($data, $field, array $options, $context=null, $component=null, $filearea=null, $itemid=null) {
|
|
$options = (array)$options;
|
|
if (!isset($options['trusttext'])) {
|
|
$options['trusttext'] = false;
|
|
}
|
|
if (!isset($options['forcehttps'])) {
|
|
$options['forcehttps'] = false;
|
|
}
|
|
if (!isset($options['subdirs'])) {
|
|
$options['subdirs'] = false;
|
|
}
|
|
if (!isset($options['maxfiles'])) {
|
|
$options['maxfiles'] = 0; // no files by default
|
|
}
|
|
if (!isset($options['noclean'])) {
|
|
$options['noclean'] = false;
|
|
}
|
|
|
|
//sanity check for passed context. This function doesn't expect $option['context'] to be set
|
|
//But this function is called before creating editor hence, this is one of the best places to check
|
|
//if context is used properly. This check notify developer that they missed passing context to editor.
|
|
if (isset($context) && !isset($options['context'])) {
|
|
//if $context is not null then make sure $option['context'] is also set.
|
|
debugging('Context for editor is not set in editoroptions. Hence editor will not respect editor filters', DEBUG_DEVELOPER);
|
|
} else if (isset($options['context']) && isset($context)) {
|
|
//If both are passed then they should be equal.
|
|
if ($options['context']->id != $context->id) {
|
|
$exceptionmsg = 'Editor context ['.$options['context']->id.'] is not equal to passed context ['.$context->id.']';
|
|
throw new coding_exception($exceptionmsg);
|
|
}
|
|
}
|
|
|
|
if (is_null($itemid) or is_null($context)) {
|
|
$contextid = null;
|
|
$itemid = null;
|
|
if (!isset($data)) {
|
|
$data = new stdClass();
|
|
}
|
|
if (!isset($data->{$field})) {
|
|
$data->{$field} = '';
|
|
}
|
|
if (!isset($data->{$field.'format'})) {
|
|
$data->{$field.'format'} = editors_get_preferred_format();
|
|
}
|
|
if (!$options['noclean']) {
|
|
$data->{$field} = clean_text($data->{$field}, $data->{$field.'format'});
|
|
}
|
|
|
|
} else {
|
|
if ($options['trusttext']) {
|
|
// noclean ignored if trusttext enabled
|
|
if (!isset($data->{$field.'trust'})) {
|
|
$data->{$field.'trust'} = 0;
|
|
}
|
|
$data = trusttext_pre_edit($data, $field, $context);
|
|
} else {
|
|
if (!$options['noclean']) {
|
|
$data->{$field} = clean_text($data->{$field}, $data->{$field.'format'});
|
|
}
|
|
}
|
|
$contextid = $context->id;
|
|
}
|
|
|
|
if ($options['maxfiles'] != 0) {
|
|
$draftid_editor = file_get_submitted_draft_itemid($field);
|
|
$currenttext = file_prepare_draft_area($draftid_editor, $contextid, $component, $filearea, $itemid, $options, $data->{$field});
|
|
$data->{$field.'_editor'} = array('text'=>$currenttext, 'format'=>$data->{$field.'format'}, 'itemid'=>$draftid_editor);
|
|
} else {
|
|
$data->{$field.'_editor'} = array('text'=>$data->{$field}, 'format'=>$data->{$field.'format'}, 'itemid'=>0);
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Prepares the content of the 'editor' form element with embedded media files to be saved in database
|
|
*
|
|
* This function moves files from draft area to the destination area and
|
|
* encodes URLs to the draft files so they can be safely saved into DB. The
|
|
* form has to contain the 'editor' element named foobar_editor, where 'foobar'
|
|
* is the name of the database field to hold the wysiwyg editor content. The
|
|
* editor data comes as an array with text, format and itemid properties. This
|
|
* function automatically adds $data properties foobar, foobarformat and
|
|
* foobartrust, where foobar has URL to embedded files encoded.
|
|
*
|
|
* @category files
|
|
* @param stdClass $data raw data submitted by the form
|
|
* @param string $field name of the database field containing the html with embedded media files
|
|
* @param array $options editor options (trusttext, subdirs, maxfiles, maxbytes etc.)
|
|
* @param stdClass $context context, required for existing data
|
|
* @param string $component file component
|
|
* @param string $filearea file area name
|
|
* @param int $itemid item id, required if item exists
|
|
* @return stdClass modified data object
|
|
*/
|
|
function file_postupdate_standard_editor($data, $field, array $options, $context, $component=null, $filearea=null, $itemid=null) {
|
|
$options = (array)$options;
|
|
if (!isset($options['trusttext'])) {
|
|
$options['trusttext'] = false;
|
|
}
|
|
if (!isset($options['forcehttps'])) {
|
|
$options['forcehttps'] = false;
|
|
}
|
|
if (!isset($options['subdirs'])) {
|
|
$options['subdirs'] = false;
|
|
}
|
|
if (!isset($options['maxfiles'])) {
|
|
$options['maxfiles'] = 0; // no files by default
|
|
}
|
|
if (!isset($options['maxbytes'])) {
|
|
$options['maxbytes'] = 0; // unlimited
|
|
}
|
|
|
|
if ($options['trusttext']) {
|
|
$data->{$field.'trust'} = trusttext_trusted($context);
|
|
} else {
|
|
$data->{$field.'trust'} = 0;
|
|
}
|
|
|
|
$editor = $data->{$field.'_editor'};
|
|
|
|
if ($options['maxfiles'] == 0 or is_null($filearea) or is_null($itemid) or empty($editor['itemid'])) {
|
|
$data->{$field} = $editor['text'];
|
|
} else {
|
|
$data->{$field} = file_save_draft_area_files($editor['itemid'], $context->id, $component, $filearea, $itemid, $options, $editor['text'], $options['forcehttps']);
|
|
}
|
|
$data->{$field.'format'} = $editor['format'];
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Saves text and files modified by Editor formslib element
|
|
*
|
|
* @category files
|
|
* @param stdClass $data $database entry field
|
|
* @param string $field name of data field
|
|
* @param array $options various options
|
|
* @param stdClass $context context - must already exist
|
|
* @param string $component
|
|
* @param string $filearea file area name
|
|
* @param int $itemid must already exist, usually means data is in db
|
|
* @return stdClass modified data obejct
|
|
*/
|
|
function file_prepare_standard_filemanager($data, $field, array $options, $context=null, $component=null, $filearea=null, $itemid=null) {
|
|
$options = (array)$options;
|
|
if (!isset($options['subdirs'])) {
|
|
$options['subdirs'] = false;
|
|
}
|
|
if (is_null($itemid) or is_null($context)) {
|
|
$itemid = null;
|
|
$contextid = null;
|
|
} else {
|
|
$contextid = $context->id;
|
|
}
|
|
|
|
$draftid_editor = file_get_submitted_draft_itemid($field.'_filemanager');
|
|
file_prepare_draft_area($draftid_editor, $contextid, $component, $filearea, $itemid, $options);
|
|
$data->{$field.'_filemanager'} = $draftid_editor;
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Saves files modified by File manager formslib element
|
|
*
|
|
* @todo MDL-31073 review this function
|
|
* @category files
|
|
* @param stdClass $data $database entry field
|
|
* @param string $field name of data field
|
|
* @param array $options various options
|
|
* @param stdClass $context context - must already exist
|
|
* @param string $component
|
|
* @param string $filearea file area name
|
|
* @param int $itemid must already exist, usually means data is in db
|
|
* @return stdClass modified data obejct
|
|
*/
|
|
function file_postupdate_standard_filemanager($data, $field, array $options, $context, $component, $filearea, $itemid) {
|
|
$options = (array)$options;
|
|
if (!isset($options['subdirs'])) {
|
|
$options['subdirs'] = false;
|
|
}
|
|
if (!isset($options['maxfiles'])) {
|
|
$options['maxfiles'] = -1; // unlimited
|
|
}
|
|
if (!isset($options['maxbytes'])) {
|
|
$options['maxbytes'] = 0; // unlimited
|
|
}
|
|
|
|
if (empty($data->{$field.'_filemanager'})) {
|
|
$data->$field = '';
|
|
|
|
} else {
|
|
file_save_draft_area_files($data->{$field.'_filemanager'}, $context->id, $component, $filearea, $itemid, $options);
|
|
$fs = get_file_storage();
|
|
|
|
if ($fs->get_area_files($context->id, $component, $filearea, $itemid)) {
|
|
$data->$field = '1'; // TODO: this is an ugly hack (skodak)
|
|
} else {
|
|
$data->$field = '';
|
|
}
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Generate a draft itemid
|
|
*
|
|
* @category files
|
|
* @global moodle_database $DB
|
|
* @global stdClass $USER
|
|
* @return int a random but available draft itemid that can be used to create a new draft
|
|
* file area.
|
|
*/
|
|
function file_get_unused_draft_itemid() {
|
|
global $DB, $USER;
|
|
|
|
if (isguestuser() or !isloggedin()) {
|
|
// guests and not-logged-in users can not be allowed to upload anything!!!!!!
|
|
print_error('noguest');
|
|
}
|
|
|
|
$contextid = get_context_instance(CONTEXT_USER, $USER->id)->id;
|
|
|
|
$fs = get_file_storage();
|
|
$draftitemid = rand(1, 999999999);
|
|
while ($files = $fs->get_area_files($contextid, 'user', 'draft', $draftitemid)) {
|
|
$draftitemid = rand(1, 999999999);
|
|
}
|
|
|
|
return $draftitemid;
|
|
}
|
|
|
|
/**
|
|
* Initialise a draft file area from a real one by copying the files. A draft
|
|
* area will be created if one does not already exist. Normally you should
|
|
* get $draftitemid by calling file_get_submitted_draft_itemid('elementname');
|
|
*
|
|
* @category files
|
|
* @global stdClass $CFG
|
|
* @global stdClass $USER
|
|
* @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.
|
|
* @param int $contextid This parameter and the next two identify the file area to copy files from.
|
|
* @param string $component
|
|
* @param string $filearea helps indentify the file area.
|
|
* @param int $itemid helps identify the file area. Can be null if there are no files yet.
|
|
* @param array $options text and file options ('subdirs'=>false, 'forcehttps'=>false)
|
|
* @param string $text some html content that needs to have embedded links rewritten to point to the draft area.
|
|
* @return string|null returns string if $text was passed in, the rewritten $text is returned. Otherwise NULL.
|
|
*/
|
|
function file_prepare_draft_area(&$draftitemid, $contextid, $component, $filearea, $itemid, array $options=null, $text=null) {
|
|
global $CFG, $USER, $CFG;
|
|
|
|
$options = (array)$options;
|
|
if (!isset($options['subdirs'])) {
|
|
$options['subdirs'] = false;
|
|
}
|
|
if (!isset($options['forcehttps'])) {
|
|
$options['forcehttps'] = false;
|
|
}
|
|
|
|
$usercontext = get_context_instance(CONTEXT_USER, $USER->id);
|
|
$fs = get_file_storage();
|
|
|
|
if (empty($draftitemid)) {
|
|
// create a new area and copy existing files into
|
|
$draftitemid = file_get_unused_draft_itemid();
|
|
$file_record = array('contextid'=>$usercontext->id, 'component'=>'user', 'filearea'=>'draft', 'itemid'=>$draftitemid);
|
|
if (!is_null($itemid) and $files = $fs->get_area_files($contextid, $component, $filearea, $itemid)) {
|
|
foreach ($files as $file) {
|
|
if ($file->is_directory() and $file->get_filepath() === '/') {
|
|
// we need a way to mark the age of each draft area,
|
|
// by not copying the root dir we force it to be created automatically with current timestamp
|
|
continue;
|
|
}
|
|
if (!$options['subdirs'] and ($file->is_directory() or $file->get_filepath() !== '/')) {
|
|
continue;
|
|
}
|
|
$fs->create_file_from_storedfile($file_record, $file);
|
|
}
|
|
}
|
|
if (!is_null($text)) {
|
|
// at this point there should not be any draftfile links yet,
|
|
// because this is a new text from database that should still contain the @@pluginfile@@ links
|
|
// this happens when developers forget to post process the text
|
|
$text = str_replace("\"$CFG->httpswwwroot/draftfile.php", "\"$CFG->httpswwwroot/brokenfile.php#", $text);
|
|
}
|
|
} else {
|
|
// nothing to do
|
|
}
|
|
|
|
if (is_null($text)) {
|
|
return null;
|
|
}
|
|
|
|
// relink embedded files - editor can not handle @@PLUGINFILE@@ !
|
|
return file_rewrite_pluginfile_urls($text, 'draftfile.php', $usercontext->id, 'user', 'draft', $draftitemid, $options);
|
|
}
|
|
|
|
/**
|
|
* Convert encoded URLs in $text from the @@PLUGINFILE@@/... form to an actual URL.
|
|
*
|
|
* @category files
|
|
* @global stdClass $CFG
|
|
* @param string $text The content that may contain ULRs in need of rewriting.
|
|
* @param string $file The script that should be used to serve these files. pluginfile.php, draftfile.php, etc.
|
|
* @param int $contextid This parameter and the next two identify the file area to use.
|
|
* @param string $component
|
|
* @param string $filearea helps identify the file area.
|
|
* @param int $itemid helps identify the file area.
|
|
* @param array $options text and file options ('forcehttps'=>false)
|
|
* @return string the processed text.
|
|
*/
|
|
function file_rewrite_pluginfile_urls($text, $file, $contextid, $component, $filearea, $itemid, array $options=null) {
|
|
global $CFG;
|
|
|
|
$options = (array)$options;
|
|
if (!isset($options['forcehttps'])) {
|
|
$options['forcehttps'] = false;
|
|
}
|
|
|
|
if (!$CFG->slasharguments) {
|
|
$file = $file . '?file=';
|
|
}
|
|
|
|
$baseurl = "$CFG->wwwroot/$file/$contextid/$component/$filearea/";
|
|
|
|
if ($itemid !== null) {
|
|
$baseurl .= "$itemid/";
|
|
}
|
|
|
|
if ($options['forcehttps']) {
|
|
$baseurl = str_replace('http://', 'https://', $baseurl);
|
|
}
|
|
|
|
return str_replace('@@PLUGINFILE@@/', $baseurl, $text);
|
|
}
|
|
|
|
/**
|
|
* Returns information about files in a draft area.
|
|
*
|
|
* @global stdClass $CFG
|
|
* @global stdClass $USER
|
|
* @param int $draftitemid the draft area item id.
|
|
* @return array with the following entries:
|
|
* 'filecount' => number of files in the draft area.
|
|
* (more information will be added as needed).
|
|
*/
|
|
function file_get_draft_area_info($draftitemid) {
|
|
global $CFG, $USER;
|
|
|
|
$usercontext = get_context_instance(CONTEXT_USER, $USER->id);
|
|
$fs = get_file_storage();
|
|
|
|
$results = array();
|
|
|
|
// The number of files
|
|
$draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid, 'id', false);
|
|
$results['filecount'] = count($draftfiles);
|
|
$results['filesize'] = 0;
|
|
foreach ($draftfiles as $file) {
|
|
$results['filesize'] += $file->get_filesize();
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Get used space of files
|
|
* @global moodle_database $DB
|
|
* @global stdClass $USER
|
|
* @return int total bytes
|
|
*/
|
|
function file_get_user_used_space() {
|
|
global $DB, $USER;
|
|
|
|
$usercontext = get_context_instance(CONTEXT_USER, $USER->id);
|
|
$sql = "SELECT SUM(files1.filesize) AS totalbytes FROM {files} files1
|
|
JOIN (SELECT contenthash, filename, MAX(id) AS id
|
|
FROM {files}
|
|
WHERE contextid = ? AND component = ? AND filearea != ?
|
|
GROUP BY contenthash, filename) files2 ON files1.id = files2.id";
|
|
$params = array('contextid'=>$usercontext->id, 'component'=>'user', 'filearea'=>'draft');
|
|
$record = $DB->get_record_sql($sql, $params);
|
|
return (int)$record->totalbytes;
|
|
}
|
|
|
|
/**
|
|
* Convert any string to a valid filepath
|
|
* @todo review this function
|
|
* @param string $str
|
|
* @return string path
|
|
*/
|
|
function file_correct_filepath($str) { //TODO: what is this? (skodak)
|
|
if ($str == '/' or empty($str)) {
|
|
return '/';
|
|
} else {
|
|
return '/'.trim($str, './@#$ ').'/';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate a folder tree of draft area of current USER recursively
|
|
*
|
|
* @todo MDL-31073 use normal return value instead, this does not fit the rest of api here (skodak)
|
|
* @param int $draftitemid
|
|
* @param string $filepath
|
|
* @param mixed $data
|
|
*/
|
|
function file_get_drafarea_folders($draftitemid, $filepath, &$data) {
|
|
global $USER, $OUTPUT, $CFG;
|
|
$data->children = array();
|
|
$context = get_context_instance(CONTEXT_USER, $USER->id);
|
|
$fs = get_file_storage();
|
|
if ($files = $fs->get_directory_files($context->id, 'user', 'draft', $draftitemid, $filepath, false)) {
|
|
foreach ($files as $file) {
|
|
if ($file->is_directory()) {
|
|
$item = new stdClass();
|
|
$item->sortorder = $file->get_sortorder();
|
|
$item->filepath = $file->get_filepath();
|
|
|
|
$foldername = explode('/', trim($item->filepath, '/'));
|
|
$item->fullname = trim(array_pop($foldername), '/');
|
|
|
|
$item->id = uniqid();
|
|
file_get_drafarea_folders($draftitemid, $item->filepath, $item);
|
|
$data->children[] = $item;
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Listing all files (including folders) in current path (draft area)
|
|
* used by file manager
|
|
* @param int $draftitemid
|
|
* @param string $filepath
|
|
* @return stdClass
|
|
*/
|
|
function file_get_drafarea_files($draftitemid, $filepath = '/') {
|
|
global $USER, $OUTPUT, $CFG;
|
|
|
|
$context = get_context_instance(CONTEXT_USER, $USER->id);
|
|
$fs = get_file_storage();
|
|
|
|
$data = new stdClass();
|
|
$data->path = array();
|
|
$data->path[] = array('name'=>get_string('files'), 'path'=>'/');
|
|
|
|
// will be used to build breadcrumb
|
|
$trail = '';
|
|
if ($filepath !== '/') {
|
|
$filepath = file_correct_filepath($filepath);
|
|
$parts = explode('/', $filepath);
|
|
foreach ($parts as $part) {
|
|
if ($part != '' && $part != null) {
|
|
$trail .= ('/'.$part.'/');
|
|
$data->path[] = array('name'=>$part, 'path'=>$trail);
|
|
}
|
|
}
|
|
}
|
|
|
|
$list = array();
|
|
$maxlength = 12;
|
|
if ($files = $fs->get_directory_files($context->id, 'user', 'draft', $draftitemid, $filepath, false)) {
|
|
foreach ($files as $file) {
|
|
$item = new stdClass();
|
|
$item->filename = $file->get_filename();
|
|
$item->filepath = $file->get_filepath();
|
|
$item->fullname = trim($item->filename, '/');
|
|
$filesize = $file->get_filesize();
|
|
$item->filesize = $filesize ? display_size($filesize) : '';
|
|
|
|
$icon = mimeinfo_from_type('icon', $file->get_mimetype());
|
|
$item->icon = $OUTPUT->pix_url('f/' . $icon)->out();
|
|
$item->sortorder = $file->get_sortorder();
|
|
|
|
if ($icon == 'zip') {
|
|
$item->type = 'zip';
|
|
} else {
|
|
$item->type = 'file';
|
|
}
|
|
|
|
if ($file->is_directory()) {
|
|
$item->filesize = 0;
|
|
$item->icon = $OUTPUT->pix_url('f/folder')->out();
|
|
$item->type = 'folder';
|
|
$foldername = explode('/', trim($item->filepath, '/'));
|
|
$item->fullname = trim(array_pop($foldername), '/');
|
|
} else {
|
|
// do NOT use file browser here!
|
|
$item->url = moodle_url::make_draftfile_url($draftitemid, $item->filepath, $item->filename)->out();
|
|
}
|
|
$list[] = $item;
|
|
}
|
|
}
|
|
$data->itemid = $draftitemid;
|
|
$data->list = $list;
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Returns draft area itemid for a given element.
|
|
*
|
|
* @category files
|
|
* @param string $elname name of formlib editor element, or a hidden form field that stores the draft area item id, etc.
|
|
* @return int the itemid, or 0 if there is not one yet.
|
|
*/
|
|
function file_get_submitted_draft_itemid($elname) {
|
|
// this is a nasty hack, ideally all new elements should use arrays here or there should be a new parameter
|
|
if (!isset($_REQUEST[$elname])) {
|
|
return 0;
|
|
}
|
|
if (is_array($_REQUEST[$elname])) {
|
|
$param = optional_param_array($elname, 0, PARAM_INT);
|
|
if (!empty($param['itemid'])) {
|
|
$param = $param['itemid'];
|
|
} else {
|
|
debugging('Missing itemid, maybe caused by unset maxfiles option', DEBUG_DEVELOPER);
|
|
return false;
|
|
}
|
|
|
|
} else {
|
|
$param = optional_param($elname, 0, PARAM_INT);
|
|
}
|
|
|
|
if ($param) {
|
|
require_sesskey();
|
|
}
|
|
|
|
return $param;
|
|
}
|
|
|
|
/**
|
|
* Saves files from a draft file area to a real one (merging the list of files).
|
|
* Can rewrite URLs in some content at the same time if desired.
|
|
*
|
|
* @category files
|
|
* @global stdClass $USER
|
|
* @param int $draftitemid the id of the draft area to use. Normally obtained
|
|
* from file_get_submitted_draft_itemid('elementname') or similar.
|
|
* @param int $contextid This parameter and the next two identify the file area to save to.
|
|
* @param string $component
|
|
* @param string $filearea indentifies the file area.
|
|
* @param int $itemid helps identifies the file area.
|
|
* @param array $options area options (subdirs=>false, maxfiles=-1, maxbytes=0)
|
|
* @param string $text some html content that needs to have embedded links rewritten
|
|
* to the @@PLUGINFILE@@ form for saving in the database.
|
|
* @param bool $forcehttps force https urls.
|
|
* @return string|null if $text was passed in, the rewritten $text is returned. Otherwise NULL.
|
|
*/
|
|
function file_save_draft_area_files($draftitemid, $contextid, $component, $filearea, $itemid, array $options=null, $text=null, $forcehttps=false) {
|
|
global $USER;
|
|
|
|
$usercontext = get_context_instance(CONTEXT_USER, $USER->id);
|
|
$fs = get_file_storage();
|
|
|
|
$options = (array)$options;
|
|
if (!isset($options['subdirs'])) {
|
|
$options['subdirs'] = false;
|
|
}
|
|
if (!isset($options['maxfiles'])) {
|
|
$options['maxfiles'] = -1; // unlimited
|
|
}
|
|
if (!isset($options['maxbytes'])) {
|
|
$options['maxbytes'] = 0; // unlimited
|
|
}
|
|
|
|
$draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid, 'id');
|
|
$oldfiles = $fs->get_area_files($contextid, $component, $filearea, $itemid, 'id');
|
|
|
|
if (count($draftfiles) < 2) {
|
|
// means there are no files - one file means root dir only ;-)
|
|
$fs->delete_area_files($contextid, $component, $filearea, $itemid);
|
|
|
|
} else if (count($oldfiles) < 2) {
|
|
$filecount = 0;
|
|
// there were no files before - one file means root dir only ;-)
|
|
$file_record = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea, 'itemid'=>$itemid);
|
|
foreach ($draftfiles as $file) {
|
|
if (!$options['subdirs']) {
|
|
if ($file->get_filepath() !== '/' or $file->is_directory()) {
|
|
continue;
|
|
}
|
|
}
|
|
if ($options['maxbytes'] and $options['maxbytes'] < $file->get_filesize()) {
|
|
// oversized file - should not get here at all
|
|
continue;
|
|
}
|
|
if ($options['maxfiles'] != -1 and $options['maxfiles'] <= $filecount) {
|
|
// more files - should not get here at all
|
|
break;
|
|
}
|
|
if (!$file->is_directory()) {
|
|
$filecount++;
|
|
}
|
|
$fs->create_file_from_storedfile($file_record, $file);
|
|
}
|
|
|
|
} else {
|
|
// we have to merge old and new files - we want to keep file ids for files that were not changed
|
|
// we change time modified for all new and changed files, we keep time created as is
|
|
$file_record = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea, 'itemid'=>$itemid, 'timemodified'=>time());
|
|
|
|
$newhashes = array();
|
|
foreach ($draftfiles as $file) {
|
|
$newhash = $fs->get_pathname_hash($contextid, $component, $filearea, $itemid, $file->get_filepath(), $file->get_filename());
|
|
$newhashes[$newhash] = $file;
|
|
}
|
|
$filecount = 0;
|
|
foreach ($oldfiles as $oldfile) {
|
|
$oldhash = $oldfile->get_pathnamehash();
|
|
if (!isset($newhashes[$oldhash])) {
|
|
// delete files not needed any more - deleted by user
|
|
$oldfile->delete();
|
|
continue;
|
|
}
|
|
$newfile = $newhashes[$oldhash];
|
|
if ($oldfile->get_contenthash() != $newfile->get_contenthash() or $oldfile->get_sortorder() != $newfile->get_sortorder()
|
|
or $oldfile->get_status() != $newfile->get_status() or $oldfile->get_license() != $newfile->get_license()
|
|
or $oldfile->get_author() != $newfile->get_author() or $oldfile->get_source() != $newfile->get_source()) {
|
|
// file was changed, use updated with new timemodified data
|
|
$oldfile->delete();
|
|
continue;
|
|
}
|
|
// unchanged file or directory - we keep it as is
|
|
unset($newhashes[$oldhash]);
|
|
if (!$oldfile->is_directory()) {
|
|
$filecount++;
|
|
}
|
|
}
|
|
|
|
// now add new/changed files
|
|
// the size and subdirectory tests are extra safety only, the UI should prevent it
|
|
foreach ($newhashes as $file) {
|
|
if (!$options['subdirs']) {
|
|
if ($file->get_filepath() !== '/' or $file->is_directory()) {
|
|
continue;
|
|
}
|
|
}
|
|
if ($options['maxbytes'] and $options['maxbytes'] < $file->get_filesize()) {
|
|
// oversized file - should not get here at all
|
|
continue;
|
|
}
|
|
if ($options['maxfiles'] != -1 and $options['maxfiles'] <= $filecount) {
|
|
// more files - should not get here at all
|
|
break;
|
|
}
|
|
if (!$file->is_directory()) {
|
|
$filecount++;
|
|
}
|
|
$fs->create_file_from_storedfile($file_record, $file);
|
|
}
|
|
}
|
|
|
|
// note: do not purge the draft area - we clean up areas later in cron,
|
|
// the reason is that user might press submit twice and they would loose the files,
|
|
// also sometimes we might want to use hacks that save files into two different areas
|
|
|
|
if (is_null($text)) {
|
|
return null;
|
|
} else {
|
|
return file_rewrite_urls_to_pluginfile($text, $draftitemid, $forcehttps);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convert the draft file area URLs in some content to @@PLUGINFILE@@ tokens
|
|
* ready to be saved in the database. Normally, this is done automatically by
|
|
* {@link file_save_draft_area_files()}.
|
|
*
|
|
* @category files
|
|
* @param string $text the content to process.
|
|
* @param int $draftitemid the draft file area the content was using.
|
|
* @param bool $forcehttps whether the content contains https URLs. Default false.
|
|
* @return string the processed content.
|
|
*/
|
|
function file_rewrite_urls_to_pluginfile($text, $draftitemid, $forcehttps = false) {
|
|
global $CFG, $USER;
|
|
|
|
$usercontext = get_context_instance(CONTEXT_USER, $USER->id);
|
|
|
|
$wwwroot = $CFG->wwwroot;
|
|
if ($forcehttps) {
|
|
$wwwroot = str_replace('http://', 'https://', $wwwroot);
|
|
}
|
|
|
|
// relink embedded files if text submitted - no absolute links allowed in database!
|
|
$text = str_ireplace("$wwwroot/draftfile.php/$usercontext->id/user/draft/$draftitemid/", '@@PLUGINFILE@@/', $text);
|
|
|
|
if (strpos($text, 'draftfile.php?file=') !== false) {
|
|
$matches = array();
|
|
preg_match_all("!$wwwroot/draftfile.php\?file=%2F{$usercontext->id}%2Fuser%2Fdraft%2F{$draftitemid}%2F[^'\",&<>|`\s:\\\\]+!iu", $text, $matches);
|
|
if ($matches) {
|
|
foreach ($matches[0] as $match) {
|
|
$replace = str_ireplace('%2F', '/', $match);
|
|
$text = str_replace($match, $replace, $text);
|
|
}
|
|
}
|
|
$text = str_ireplace("$wwwroot/draftfile.php?file=/$usercontext->id/user/draft/$draftitemid/", '@@PLUGINFILE@@/', $text);
|
|
}
|
|
|
|
return $text;
|
|
}
|
|
|
|
/**
|
|
* Set file sort order
|
|
*
|
|
* @global moodle_database $DB
|
|
* @param int $contextid the context id
|
|
* @param string $component file component
|
|
* @param string $filearea file area.
|
|
* @param int $itemid itemid.
|
|
* @param string $filepath file path.
|
|
* @param string $filename file name.
|
|
* @param int $sortorder the sort order of file.
|
|
* @return bool
|
|
*/
|
|
function file_set_sortorder($contextid, $component, $filearea, $itemid, $filepath, $filename, $sortorder) {
|
|
global $DB;
|
|
$conditions = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea, 'itemid'=>$itemid, 'filepath'=>$filepath, 'filename'=>$filename);
|
|
if ($file_record = $DB->get_record('files', $conditions)) {
|
|
$sortorder = (int)$sortorder;
|
|
$file_record->sortorder = $sortorder;
|
|
$DB->update_record('files', $file_record);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* reset file sort order number to 0
|
|
* @global moodle_database $DB
|
|
* @param int $contextid the context id
|
|
* @param string $component
|
|
* @param string $filearea file area.
|
|
* @param int|bool $itemid itemid.
|
|
* @return bool
|
|
*/
|
|
function file_reset_sortorder($contextid, $component, $filearea, $itemid=false) {
|
|
global $DB;
|
|
|
|
$conditions = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea);
|
|
if ($itemid !== false) {
|
|
$conditions['itemid'] = $itemid;
|
|
}
|
|
|
|
$file_records = $DB->get_records('files', $conditions);
|
|
foreach ($file_records as $file_record) {
|
|
$file_record->sortorder = 0;
|
|
$DB->update_record('files', $file_record);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Returns description of upload error
|
|
*
|
|
* @param int $errorcode found in $_FILES['filename.ext']['error']
|
|
* @return string error description string, '' if ok
|
|
*/
|
|
function file_get_upload_error($errorcode) {
|
|
|
|
switch ($errorcode) {
|
|
case 0: // UPLOAD_ERR_OK - no error
|
|
$errmessage = '';
|
|
break;
|
|
|
|
case 1: // UPLOAD_ERR_INI_SIZE
|
|
$errmessage = get_string('uploadserverlimit');
|
|
break;
|
|
|
|
case 2: // UPLOAD_ERR_FORM_SIZE
|
|
$errmessage = get_string('uploadformlimit');
|
|
break;
|
|
|
|
case 3: // UPLOAD_ERR_PARTIAL
|
|
$errmessage = get_string('uploadpartialfile');
|
|
break;
|
|
|
|
case 4: // UPLOAD_ERR_NO_FILE
|
|
$errmessage = get_string('uploadnofilefound');
|
|
break;
|
|
|
|
// Note: there is no error with a value of 5
|
|
|
|
case 6: // UPLOAD_ERR_NO_TMP_DIR
|
|
$errmessage = get_string('uploadnotempdir');
|
|
break;
|
|
|
|
case 7: // UPLOAD_ERR_CANT_WRITE
|
|
$errmessage = get_string('uploadcantwrite');
|
|
break;
|
|
|
|
case 8: // UPLOAD_ERR_EXTENSION
|
|
$errmessage = get_string('uploadextension');
|
|
break;
|
|
|
|
default:
|
|
$errmessage = get_string('uploadproblem');
|
|
}
|
|
|
|
return $errmessage;
|
|
}
|
|
|
|
/**
|
|
* Recursive function formating an array in POST parameter
|
|
* @param array $arraydata - the array that we are going to format and add into &$data array
|
|
* @param string $currentdata - a row of the final postdata array at instant T
|
|
* when finish, it's assign to $data under this format: name[keyname][][]...[]='value'
|
|
* @param array $data - the final data array containing all POST parameters : 1 row = 1 parameter
|
|
*/
|
|
function format_array_postdata_for_curlcall($arraydata, $currentdata, &$data) {
|
|
foreach ($arraydata as $k=>$v) {
|
|
$newcurrentdata = $currentdata;
|
|
if (is_array($v)) { //the value is an array, call the function recursively
|
|
$newcurrentdata = $newcurrentdata.'['.urlencode($k).']';
|
|
format_array_postdata_for_curlcall($v, $newcurrentdata, $data);
|
|
} else { //add the POST parameter to the $data array
|
|
$data[] = $newcurrentdata.'['.urlencode($k).']='.urlencode($v);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Transform a PHP array into POST parameter
|
|
* (see the recursive function format_array_postdata_for_curlcall)
|
|
* @param array $postdata
|
|
* @return array containing all POST parameters (1 row = 1 POST parameter)
|
|
*/
|
|
function format_postdata_for_curlcall($postdata) {
|
|
$data = array();
|
|
foreach ($postdata as $k=>$v) {
|
|
if (is_array($v)) {
|
|
$currentdata = urlencode($k);
|
|
format_array_postdata_for_curlcall($v, $currentdata, $data);
|
|
} else {
|
|
$data[] = urlencode($k).'='.urlencode($v);
|
|
}
|
|
}
|
|
$convertedpostdata = implode('&', $data);
|
|
return $convertedpostdata;
|
|
}
|
|
|
|
/**
|
|
* Fetches content of file from Internet (using proxy if defined). Uses cURL extension if present.
|
|
* Due to security concerns only downloads from http(s) sources are supported.
|
|
*
|
|
* @todo MDL-31073 add version test for '7.10.5'
|
|
* @category files
|
|
* @param string $url file url starting with http(s)://
|
|
* @param array $headers http headers, null if none. If set, should be an
|
|
* associative array of header name => value pairs.
|
|
* @param array $postdata array means use POST request with given parameters
|
|
* @param bool $fullresponse return headers, responses, etc in a similar way snoopy does
|
|
* (if false, just returns content)
|
|
* @param int $timeout timeout for complete download process including all file transfer
|
|
* (default 5 minutes)
|
|
* @param int $connecttimeout timeout for connection to server; this is the timeout that
|
|
* usually happens if the remote server is completely down (default 20 seconds);
|
|
* may not work when using proxy
|
|
* @param bool $skipcertverify If true, the peer's SSL certificate will not be checked.
|
|
* Only use this when already in a trusted location.
|
|
* @param string $tofile store the downloaded content to file instead of returning it.
|
|
* @param bool $calctimeout false by default, true enables an extra head request to try and determine
|
|
* filesize and appropriately larger timeout based on $CFG->curltimeoutkbitrate
|
|
* @return mixed false if request failed or content of the file as string if ok. True if file downloaded into $tofile successfully.
|
|
*/
|
|
function download_file_content($url, $headers=null, $postdata=null, $fullresponse=false, $timeout=300, $connecttimeout=20, $skipcertverify=false, $tofile=NULL, $calctimeout=false) {
|
|
global $CFG;
|
|
|
|
// some extra security
|
|
$newlines = array("\r", "\n");
|
|
if (is_array($headers) ) {
|
|
foreach ($headers as $key => $value) {
|
|
$headers[$key] = str_replace($newlines, '', $value);
|
|
}
|
|
}
|
|
$url = str_replace($newlines, '', $url);
|
|
if (!preg_match('|^https?://|i', $url)) {
|
|
if ($fullresponse) {
|
|
$response = new stdClass();
|
|
$response->status = 0;
|
|
$response->headers = array();
|
|
$response->response_code = 'Invalid protocol specified in url';
|
|
$response->results = '';
|
|
$response->error = 'Invalid protocol specified in url';
|
|
return $response;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// check if proxy (if used) should be bypassed for this url
|
|
$proxybypass = is_proxybypass($url);
|
|
|
|
if (!$ch = curl_init($url)) {
|
|
debugging('Can not init curl.');
|
|
return false;
|
|
}
|
|
|
|
// set extra headers
|
|
if (is_array($headers) ) {
|
|
$headers2 = array();
|
|
foreach ($headers as $key => $value) {
|
|
$headers2[] = "$key: $value";
|
|
}
|
|
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers2);
|
|
}
|
|
|
|
if ($skipcertverify) {
|
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
|
}
|
|
|
|
// use POST if requested
|
|
if (is_array($postdata)) {
|
|
$postdata = format_postdata_for_curlcall($postdata);
|
|
curl_setopt($ch, CURLOPT_POST, true);
|
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $postdata);
|
|
}
|
|
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
curl_setopt($ch, CURLOPT_HEADER, false);
|
|
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $connecttimeout);
|
|
|
|
if (!ini_get('open_basedir') and !ini_get('safe_mode')) {
|
|
// TODO: add version test for '7.10.5'
|
|
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
|
curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
|
|
}
|
|
|
|
if (!empty($CFG->proxyhost) and !$proxybypass) {
|
|
// SOCKS supported in PHP5 only
|
|
if (!empty($CFG->proxytype) and ($CFG->proxytype == 'SOCKS5')) {
|
|
if (defined('CURLPROXY_SOCKS5')) {
|
|
curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
|
|
} else {
|
|
curl_close($ch);
|
|
if ($fullresponse) {
|
|
$response = new stdClass();
|
|
$response->status = '0';
|
|
$response->headers = array();
|
|
$response->response_code = 'SOCKS5 proxy is not supported in PHP4';
|
|
$response->results = '';
|
|
$response->error = 'SOCKS5 proxy is not supported in PHP4';
|
|
return $response;
|
|
} else {
|
|
debugging("SOCKS5 proxy is not supported in PHP4.", DEBUG_ALL);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, false);
|
|
|
|
if (empty($CFG->proxyport)) {
|
|
curl_setopt($ch, CURLOPT_PROXY, $CFG->proxyhost);
|
|
} else {
|
|
curl_setopt($ch, CURLOPT_PROXY, $CFG->proxyhost.':'.$CFG->proxyport);
|
|
}
|
|
|
|
if (!empty($CFG->proxyuser) and !empty($CFG->proxypassword)) {
|
|
curl_setopt($ch, CURLOPT_PROXYUSERPWD, $CFG->proxyuser.':'.$CFG->proxypassword);
|
|
if (defined('CURLOPT_PROXYAUTH')) {
|
|
// any proxy authentication if PHP 5.1
|
|
curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_BASIC | CURLAUTH_NTLM);
|
|
}
|
|
}
|
|
}
|
|
|
|
// set up header and content handlers
|
|
$received = new stdClass();
|
|
$received->headers = array(); // received headers array
|
|
$received->tofile = $tofile;
|
|
$received->fh = null;
|
|
curl_setopt($ch, CURLOPT_HEADERFUNCTION, partial('download_file_content_header_handler', $received));
|
|
if ($tofile) {
|
|
curl_setopt($ch, CURLOPT_WRITEFUNCTION, partial('download_file_content_write_handler', $received));
|
|
}
|
|
|
|
if (!isset($CFG->curltimeoutkbitrate)) {
|
|
//use very slow rate of 56kbps as a timeout speed when not set
|
|
$bitrate = 56;
|
|
} else {
|
|
$bitrate = $CFG->curltimeoutkbitrate;
|
|
}
|
|
|
|
// try to calculate the proper amount for timeout from remote file size.
|
|
// if disabled or zero, we won't do any checks nor head requests.
|
|
if ($calctimeout && $bitrate > 0) {
|
|
//setup header request only options
|
|
curl_setopt_array ($ch, array(
|
|
CURLOPT_RETURNTRANSFER => false,
|
|
CURLOPT_NOBODY => true)
|
|
);
|
|
|
|
curl_exec($ch);
|
|
$info = curl_getinfo($ch);
|
|
$err = curl_error($ch);
|
|
|
|
if ($err === '' && $info['download_content_length'] > 0) { //no curl errors
|
|
$timeout = max($timeout, ceil($info['download_content_length'] * 8 / ($bitrate * 1024))); //adjust for large files only - take max timeout.
|
|
}
|
|
//reinstate affected curl options
|
|
curl_setopt_array ($ch, array(
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_NOBODY => false)
|
|
);
|
|
}
|
|
|
|
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
|
|
$result = curl_exec($ch);
|
|
|
|
// try to detect encoding problems
|
|
if ((curl_errno($ch) == 23 or curl_errno($ch) == 61) and defined('CURLOPT_ENCODING')) {
|
|
curl_setopt($ch, CURLOPT_ENCODING, 'none');
|
|
$result = curl_exec($ch);
|
|
}
|
|
|
|
if ($received->fh) {
|
|
fclose($received->fh);
|
|
}
|
|
|
|
if (curl_errno($ch)) {
|
|
$error = curl_error($ch);
|
|
$error_no = curl_errno($ch);
|
|
curl_close($ch);
|
|
|
|
if ($fullresponse) {
|
|
$response = new stdClass();
|
|
if ($error_no == 28) {
|
|
$response->status = '-100'; // mimic snoopy
|
|
} else {
|
|
$response->status = '0';
|
|
}
|
|
$response->headers = array();
|
|
$response->response_code = $error;
|
|
$response->results = false;
|
|
$response->error = $error;
|
|
return $response;
|
|
} else {
|
|
debugging("cURL request for \"$url\" failed with: $error ($error_no)", DEBUG_ALL);
|
|
return false;
|
|
}
|
|
|
|
} else {
|
|
$info = curl_getinfo($ch);
|
|
curl_close($ch);
|
|
|
|
if (empty($info['http_code'])) {
|
|
// for security reasons we support only true http connections (Location: file:// exploit prevention)
|
|
$response = new stdClass();
|
|
$response->status = '0';
|
|
$response->headers = array();
|
|
$response->response_code = 'Unknown cURL error';
|
|
$response->results = false; // do NOT change this, we really want to ignore the result!
|
|
$response->error = 'Unknown cURL error';
|
|
|
|
} else {
|
|
$response = new stdClass();;
|
|
$response->status = (string)$info['http_code'];
|
|
$response->headers = $received->headers;
|
|
$response->response_code = $received->headers[0];
|
|
$response->results = $result;
|
|
$response->error = '';
|
|
}
|
|
|
|
if ($fullresponse) {
|
|
return $response;
|
|
} else if ($info['http_code'] != 200) {
|
|
debugging("cURL request for \"$url\" failed, HTTP response code: ".$response->response_code, DEBUG_ALL);
|
|
return false;
|
|
} else {
|
|
return $response->results;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* internal implementation
|
|
* @param stdClass $received
|
|
* @param resource $ch
|
|
* @param mixed $header
|
|
* @return int header length
|
|
*/
|
|
function download_file_content_header_handler($received, $ch, $header) {
|
|
$received->headers[] = $header;
|
|
return strlen($header);
|
|
}
|
|
|
|
/**
|
|
* internal implementation
|
|
* @param stdClass $received
|
|
* @param resource $ch
|
|
* @param mixed $data
|
|
*/
|
|
function download_file_content_write_handler($received, $ch, $data) {
|
|
if (!$received->fh) {
|
|
$received->fh = fopen($received->tofile, 'w');
|
|
if ($received->fh === false) {
|
|
// bad luck, file creation or overriding failed
|
|
return 0;
|
|
}
|
|
}
|
|
if (fwrite($received->fh, $data) === false) {
|
|
// bad luck, write failed, let's abort completely
|
|
return 0;
|
|
}
|
|
return strlen($data);
|
|
}
|
|
|
|
/**
|
|
* Returns a list of information about file t ypes based on extensions
|
|
*
|
|
* @category files
|
|
* @return array List of information about file types based on extensions.
|
|
* Associative array of extension (lower-case) to associative array
|
|
* from 'element name' to data. Current element names are 'type' and 'icon'.
|
|
* Unknown types should use the 'xxx' entry which includes defaults.
|
|
*/
|
|
function get_mimetypes_array() {
|
|
static $mimearray = array (
|
|
'xxx' => array ('type'=>'document/unknown', 'icon'=>'unknown'),
|
|
'3gp' => array ('type'=>'video/quicktime', 'icon'=>'video'),
|
|
'aac' => array ('type'=>'audio/aac', 'icon'=>'audio'),
|
|
'ai' => array ('type'=>'application/postscript', 'icon'=>'image'),
|
|
'aif' => array ('type'=>'audio/x-aiff', 'icon'=>'audio'),
|
|
'aiff' => array ('type'=>'audio/x-aiff', 'icon'=>'audio'),
|
|
'aifc' => array ('type'=>'audio/x-aiff', 'icon'=>'audio'),
|
|
'applescript' => array ('type'=>'text/plain', 'icon'=>'text'),
|
|
'asc' => array ('type'=>'text/plain', 'icon'=>'text'),
|
|
'asm' => array ('type'=>'text/plain', 'icon'=>'text'),
|
|
'au' => array ('type'=>'audio/au', 'icon'=>'audio'),
|
|
'avi' => array ('type'=>'video/x-ms-wm', 'icon'=>'avi'),
|
|
'bmp' => array ('type'=>'image/bmp', 'icon'=>'image'),
|
|
'c' => array ('type'=>'text/plain', 'icon'=>'text'),
|
|
'cct' => array ('type'=>'shockwave/director', 'icon'=>'flash'),
|
|
'cpp' => array ('type'=>'text/plain', 'icon'=>'text'),
|
|
'cs' => array ('type'=>'application/x-csh', 'icon'=>'text'),
|
|
'css' => array ('type'=>'text/css', 'icon'=>'text'),
|
|
'csv' => array ('type'=>'text/csv', 'icon'=>'excel'),
|
|
'dv' => array ('type'=>'video/x-dv', 'icon'=>'video'),
|
|
'dmg' => array ('type'=>'application/octet-stream', 'icon'=>'dmg'),
|
|
|
|
'doc' => array ('type'=>'application/msword', 'icon'=>'word'),
|
|
'docx' => array ('type'=>'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'icon'=>'docx'),
|
|
'docm' => array ('type'=>'application/vnd.ms-word.document.macroEnabled.12', 'icon'=>'docm'),
|
|
'dotx' => array ('type'=>'application/vnd.openxmlformats-officedocument.wordprocessingml.template', 'icon'=>'dotx'),
|
|
'dotm' => array ('type'=>'application/vnd.ms-word.template.macroEnabled.12', 'icon'=>'dotm'),
|
|
|
|
'dcr' => array ('type'=>'application/x-director', 'icon'=>'flash'),
|
|
'dif' => array ('type'=>'video/x-dv', 'icon'=>'video'),
|
|
'dir' => array ('type'=>'application/x-director', 'icon'=>'flash'),
|
|
'dxr' => array ('type'=>'application/x-director', 'icon'=>'flash'),
|
|
'eps' => array ('type'=>'application/postscript', 'icon'=>'pdf'),
|
|
'fdf' => array ('type'=>'application/pdf', 'icon'=>'pdf'),
|
|
'flv' => array ('type'=>'video/x-flv', 'icon'=>'video'),
|
|
'f4v' => array ('type'=>'video/mp4', 'icon'=>'video'),
|
|
'gif' => array ('type'=>'image/gif', 'icon'=>'image'),
|
|
'gtar' => array ('type'=>'application/x-gtar', 'icon'=>'zip'),
|
|
'tgz' => array ('type'=>'application/g-zip', 'icon'=>'zip'),
|
|
'gz' => array ('type'=>'application/g-zip', 'icon'=>'zip'),
|
|
'gzip' => array ('type'=>'application/g-zip', 'icon'=>'zip'),
|
|
'h' => array ('type'=>'text/plain', 'icon'=>'text'),
|
|
'hpp' => array ('type'=>'text/plain', 'icon'=>'text'),
|
|
'hqx' => array ('type'=>'application/mac-binhex40', 'icon'=>'zip'),
|
|
'htc' => array ('type'=>'text/x-component', 'icon'=>'text'),
|
|
'html' => array ('type'=>'text/html', 'icon'=>'html'),
|
|
'xhtml'=> array ('type'=>'application/xhtml+xml', 'icon'=>'html'),
|
|
'htm' => array ('type'=>'text/html', 'icon'=>'html'),
|
|
'ico' => array ('type'=>'image/vnd.microsoft.icon', 'icon'=>'image'),
|
|
'ics' => array ('type'=>'text/calendar', 'icon'=>'text'),
|
|
'isf' => array ('type'=>'application/inspiration', 'icon'=>'isf'),
|
|
'ist' => array ('type'=>'application/inspiration.template', 'icon'=>'isf'),
|
|
'java' => array ('type'=>'text/plain', 'icon'=>'text'),
|
|
'jcb' => array ('type'=>'text/xml', 'icon'=>'jcb'),
|
|
'jcl' => array ('type'=>'text/xml', 'icon'=>'jcl'),
|
|
'jcw' => array ('type'=>'text/xml', 'icon'=>'jcw'),
|
|
'jmt' => array ('type'=>'text/xml', 'icon'=>'jmt'),
|
|
'jmx' => array ('type'=>'text/xml', 'icon'=>'jmx'),
|
|
'jpe' => array ('type'=>'image/jpeg', 'icon'=>'image'),
|
|
'jpeg' => array ('type'=>'image/jpeg', 'icon'=>'image'),
|
|
'jpg' => array ('type'=>'image/jpeg', 'icon'=>'image'),
|
|
'jqz' => array ('type'=>'text/xml', 'icon'=>'jqz'),
|
|
'js' => array ('type'=>'application/x-javascript', 'icon'=>'text'),
|
|
'latex'=> array ('type'=>'application/x-latex', 'icon'=>'text'),
|
|
'm' => array ('type'=>'text/plain', 'icon'=>'text'),
|
|
'mbz' => array ('type'=>'application/vnd.moodle.backup', 'icon'=>'moodle'),
|
|
'mov' => array ('type'=>'video/quicktime', 'icon'=>'video'),
|
|
'movie'=> array ('type'=>'video/x-sgi-movie', 'icon'=>'video'),
|
|
'm3u' => array ('type'=>'audio/x-mpegurl', 'icon'=>'audio'),
|
|
'mp3' => array ('type'=>'audio/mp3', 'icon'=>'audio'),
|
|
'mp4' => array ('type'=>'video/mp4', 'icon'=>'video'),
|
|
'm4v' => array ('type'=>'video/mp4', 'icon'=>'video'),
|
|
'm4a' => array ('type'=>'audio/mp4', 'icon'=>'audio'),
|
|
'mpeg' => array ('type'=>'video/mpeg', 'icon'=>'video'),
|
|
'mpe' => array ('type'=>'video/mpeg', 'icon'=>'video'),
|
|
'mpg' => array ('type'=>'video/mpeg', 'icon'=>'video'),
|
|
|
|
'odt' => array ('type'=>'application/vnd.oasis.opendocument.text', 'icon'=>'odt'),
|
|
'ott' => array ('type'=>'application/vnd.oasis.opendocument.text-template', 'icon'=>'odt'),
|
|
'oth' => array ('type'=>'application/vnd.oasis.opendocument.text-web', 'icon'=>'odt'),
|
|
'odm' => array ('type'=>'application/vnd.oasis.opendocument.text-master', 'icon'=>'odm'),
|
|
'odg' => array ('type'=>'application/vnd.oasis.opendocument.graphics', 'icon'=>'odg'),
|
|
'otg' => array ('type'=>'application/vnd.oasis.opendocument.graphics-template', 'icon'=>'odg'),
|
|
'odp' => array ('type'=>'application/vnd.oasis.opendocument.presentation', 'icon'=>'odp'),
|
|
'otp' => array ('type'=>'application/vnd.oasis.opendocument.presentation-template', 'icon'=>'odp'),
|
|
'ods' => array ('type'=>'application/vnd.oasis.opendocument.spreadsheet', 'icon'=>'ods'),
|
|
'ots' => array ('type'=>'application/vnd.oasis.opendocument.spreadsheet-template', 'icon'=>'ods'),
|
|
'odc' => array ('type'=>'application/vnd.oasis.opendocument.chart', 'icon'=>'odc'),
|
|
'odf' => array ('type'=>'application/vnd.oasis.opendocument.formula', 'icon'=>'odf'),
|
|
'odb' => array ('type'=>'application/vnd.oasis.opendocument.database', 'icon'=>'odb'),
|
|
'odi' => array ('type'=>'application/vnd.oasis.opendocument.image', 'icon'=>'odi'),
|
|
'oga' => array ('type'=>'audio/ogg', 'icon'=>'audio'),
|
|
'ogg' => array ('type'=>'audio/ogg', 'icon'=>'audio'),
|
|
'ogv' => array ('type'=>'video/ogg', 'icon'=>'video'),
|
|
|
|
'pct' => array ('type'=>'image/pict', 'icon'=>'image'),
|
|
'pdf' => array ('type'=>'application/pdf', 'icon'=>'pdf'),
|
|
'php' => array ('type'=>'text/plain', 'icon'=>'text'),
|
|
'pic' => array ('type'=>'image/pict', 'icon'=>'image'),
|
|
'pict' => array ('type'=>'image/pict', 'icon'=>'image'),
|
|
'png' => array ('type'=>'image/png', 'icon'=>'image'),
|
|
|
|
'pps' => array ('type'=>'application/vnd.ms-powerpoint', 'icon'=>'powerpoint'),
|
|
'ppt' => array ('type'=>'application/vnd.ms-powerpoint', 'icon'=>'powerpoint'),
|
|
'pptx' => array ('type'=>'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'icon'=>'pptx'),
|
|
'pptm' => array ('type'=>'application/vnd.ms-powerpoint.presentation.macroEnabled.12', 'icon'=>'pptm'),
|
|
'potx' => array ('type'=>'application/vnd.openxmlformats-officedocument.presentationml.template', 'icon'=>'potx'),
|
|
'potm' => array ('type'=>'application/vnd.ms-powerpoint.template.macroEnabled.12', 'icon'=>'potm'),
|
|
'ppam' => array ('type'=>'application/vnd.ms-powerpoint.addin.macroEnabled.12', 'icon'=>'ppam'),
|
|
'ppsx' => array ('type'=>'application/vnd.openxmlformats-officedocument.presentationml.slideshow', 'icon'=>'ppsx'),
|
|
'ppsm' => array ('type'=>'application/vnd.ms-powerpoint.slideshow.macroEnabled.12', 'icon'=>'ppsm'),
|
|
|
|
'ps' => array ('type'=>'application/postscript', 'icon'=>'pdf'),
|
|
'qt' => array ('type'=>'video/quicktime', 'icon'=>'video'),
|
|
'ra' => array ('type'=>'audio/x-realaudio-plugin', 'icon'=>'audio'),
|
|
'ram' => array ('type'=>'audio/x-pn-realaudio-plugin', 'icon'=>'audio'),
|
|
'rhb' => array ('type'=>'text/xml', 'icon'=>'xml'),
|
|
'rm' => array ('type'=>'audio/x-pn-realaudio-plugin', 'icon'=>'audio'),
|
|
'rmvb' => array ('type'=>'application/vnd.rn-realmedia-vbr', 'icon'=>'video'),
|
|
'rtf' => array ('type'=>'text/rtf', 'icon'=>'text'),
|
|
'rtx' => array ('type'=>'text/richtext', 'icon'=>'text'),
|
|
'rv' => array ('type'=>'audio/x-pn-realaudio-plugin', 'icon'=>'video'),
|
|
'sh' => array ('type'=>'application/x-sh', 'icon'=>'text'),
|
|
'sit' => array ('type'=>'application/x-stuffit', 'icon'=>'zip'),
|
|
'smi' => array ('type'=>'application/smil', 'icon'=>'text'),
|
|
'smil' => array ('type'=>'application/smil', 'icon'=>'text'),
|
|
'sqt' => array ('type'=>'text/xml', 'icon'=>'xml'),
|
|
'svg' => array ('type'=>'image/svg+xml', 'icon'=>'image'),
|
|
'svgz' => array ('type'=>'image/svg+xml', 'icon'=>'image'),
|
|
'swa' => array ('type'=>'application/x-director', 'icon'=>'flash'),
|
|
'swf' => array ('type'=>'application/x-shockwave-flash', 'icon'=>'flash'),
|
|
'swfl' => array ('type'=>'application/x-shockwave-flash', 'icon'=>'flash'),
|
|
|
|
'sxw' => array ('type'=>'application/vnd.sun.xml.writer', 'icon'=>'odt'),
|
|
'stw' => array ('type'=>'application/vnd.sun.xml.writer.template', 'icon'=>'odt'),
|
|
'sxc' => array ('type'=>'application/vnd.sun.xml.calc', 'icon'=>'odt'),
|
|
'stc' => array ('type'=>'application/vnd.sun.xml.calc.template', 'icon'=>'odt'),
|
|
'sxd' => array ('type'=>'application/vnd.sun.xml.draw', 'icon'=>'odt'),
|
|
'std' => array ('type'=>'application/vnd.sun.xml.draw.template', 'icon'=>'odt'),
|
|
'sxi' => array ('type'=>'application/vnd.sun.xml.impress', 'icon'=>'odt'),
|
|
'sti' => array ('type'=>'application/vnd.sun.xml.impress.template', 'icon'=>'odt'),
|
|
'sxg' => array ('type'=>'application/vnd.sun.xml.writer.global', 'icon'=>'odt'),
|
|
'sxm' => array ('type'=>'application/vnd.sun.xml.math', 'icon'=>'odt'),
|
|
|
|
'tar' => array ('type'=>'application/x-tar', 'icon'=>'zip'),
|
|
'tif' => array ('type'=>'image/tiff', 'icon'=>'image'),
|
|
'tiff' => array ('type'=>'image/tiff', 'icon'=>'image'),
|
|
'tex' => array ('type'=>'application/x-tex', 'icon'=>'text'),
|
|
'texi' => array ('type'=>'application/x-texinfo', 'icon'=>'text'),
|
|
'texinfo' => array ('type'=>'application/x-texinfo', 'icon'=>'text'),
|
|
'tsv' => array ('type'=>'text/tab-separated-values', 'icon'=>'text'),
|
|
'txt' => array ('type'=>'text/plain', 'icon'=>'text'),
|
|
'wav' => array ('type'=>'audio/wav', 'icon'=>'audio'),
|
|
'webm' => array ('type'=>'video/webm', 'icon'=>'video'),
|
|
'wmv' => array ('type'=>'video/x-ms-wmv', 'icon'=>'avi'),
|
|
'asf' => array ('type'=>'video/x-ms-asf', 'icon'=>'avi'),
|
|
'xdp' => array ('type'=>'application/pdf', 'icon'=>'pdf'),
|
|
'xfd' => array ('type'=>'application/pdf', 'icon'=>'pdf'),
|
|
'xfdf' => array ('type'=>'application/pdf', 'icon'=>'pdf'),
|
|
|
|
'xls' => array ('type'=>'application/vnd.ms-excel', 'icon'=>'excel'),
|
|
'xlsx' => array ('type'=>'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'icon'=>'xlsx'),
|
|
'xlsm' => array ('type'=>'application/vnd.ms-excel.sheet.macroEnabled.12', 'icon'=>'xlsm'),
|
|
'xltx' => array ('type'=>'application/vnd.openxmlformats-officedocument.spreadsheetml.template', 'icon'=>'xltx'),
|
|
'xltm' => array ('type'=>'application/vnd.ms-excel.template.macroEnabled.12', 'icon'=>'xltm'),
|
|
'xlsb' => array ('type'=>'application/vnd.ms-excel.sheet.binary.macroEnabled.12', 'icon'=>'xlsb'),
|
|
'xlam' => array ('type'=>'application/vnd.ms-excel.addin.macroEnabled.12', 'icon'=>'xlam'),
|
|
|
|
'xml' => array ('type'=>'application/xml', 'icon'=>'xml'),
|
|
'xsl' => array ('type'=>'text/xml', 'icon'=>'xml'),
|
|
'zip' => array ('type'=>'application/zip', 'icon'=>'zip')
|
|
);
|
|
return $mimearray;
|
|
}
|
|
|
|
/**
|
|
* Obtains information about a filetype based on its extension. Will
|
|
* use a default if no information is present about that particular
|
|
* extension.
|
|
*
|
|
* @category files
|
|
* @param string $element Desired information (usually 'icon'
|
|
* for icon filename or 'type' for MIME type)
|
|
* @param string $filename Filename we're looking up
|
|
* @return string Requested piece of information from array
|
|
*/
|
|
function mimeinfo($element, $filename) {
|
|
global $CFG;
|
|
$mimeinfo = get_mimetypes_array();
|
|
|
|
if (preg_match('/\.([a-z0-9]+)$/i', $filename, $match)) {
|
|
if (isset($mimeinfo[strtolower($match[1])][$element])) {
|
|
return $mimeinfo[strtolower($match[1])][$element];
|
|
} else {
|
|
if ($element == 'icon32') {
|
|
if (isset($mimeinfo[strtolower($match[1])]['icon'])) {
|
|
$filename = $mimeinfo[strtolower($match[1])]['icon'];
|
|
} else {
|
|
$filename = 'unknown';
|
|
}
|
|
$filename .= '-32';
|
|
if (file_exists($CFG->dirroot.'/pix/f/'.$filename.'.png') or file_exists($CFG->dirroot.'/pix/f/'.$filename.'.gif')) {
|
|
return $filename;
|
|
} else {
|
|
return 'unknown-32';
|
|
}
|
|
} else {
|
|
return $mimeinfo['xxx'][$element]; // By default
|
|
}
|
|
}
|
|
} else {
|
|
if ($element == 'icon32') {
|
|
return 'unknown-32';
|
|
}
|
|
return $mimeinfo['xxx'][$element]; // By default
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Obtains information about a filetype based on the MIME type rather than
|
|
* the other way around.
|
|
*
|
|
* @category files
|
|
* @param string $element Desired information (usually 'icon')
|
|
* @param string $mimetype MIME type we're looking up
|
|
* @return string Requested piece of information from array
|
|
*/
|
|
function mimeinfo_from_type($element, $mimetype) {
|
|
$mimeinfo = get_mimetypes_array();
|
|
|
|
foreach($mimeinfo as $values) {
|
|
if ($values['type']==$mimetype) {
|
|
if (isset($values[$element])) {
|
|
return $values[$element];
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return $mimeinfo['xxx'][$element]; // Default
|
|
}
|
|
|
|
/**
|
|
* Get information about a filetype based on the icon file.
|
|
*
|
|
* @category files
|
|
* @param string $element Desired information (usually 'icon')
|
|
* @param string $icon Icon file name without extension
|
|
* @param bool $all return all matching entries (defaults to false - best (by ext)/last match)
|
|
* @return string Requested piece of information from array
|
|
*/
|
|
function mimeinfo_from_icon($element, $icon, $all=false) {
|
|
$mimeinfo = get_mimetypes_array();
|
|
|
|
if (preg_match("/\/(.*)/", $icon, $matches)) {
|
|
$icon = $matches[1];
|
|
}
|
|
// Try to get the extension
|
|
$extension = '';
|
|
if (($cutat = strrpos($icon, '.')) !== false && $cutat < strlen($icon)-1) {
|
|
$extension = substr($icon, $cutat + 1);
|
|
}
|
|
$info = array($mimeinfo['xxx'][$element]); // Default
|
|
foreach($mimeinfo as $key => $values) {
|
|
if ($values['icon']==$icon) {
|
|
if (isset($values[$element])) {
|
|
$info[$key] = $values[$element];
|
|
}
|
|
//No break, for example for 'excel' we don't want 'csv'!
|
|
}
|
|
}
|
|
if ($all) {
|
|
if (count($info) > 1) {
|
|
array_shift($info); // take off document/unknown if we have better options
|
|
}
|
|
return array_values($info); // Keep keys out when requesting all
|
|
}
|
|
|
|
// Requested only one, try to get the best by extension coincidence, else return the last
|
|
if ($extension && isset($info[$extension])) {
|
|
return $info[$extension];
|
|
}
|
|
|
|
return array_pop($info); // Return last match (mimicking behaviour/comment inside foreach loop)
|
|
}
|
|
|
|
/**
|
|
* Returns the relative icon path for a given mime type
|
|
*
|
|
* This function should be used in conjunction with $OUTPUT->pix_url to produce
|
|
* a return the full path to an icon.
|
|
*
|
|
* <code>
|
|
* $mimetype = 'image/jpg';
|
|
* $icon = $OUTPUT->pix_url(file_mimetype_icon($mimetype));
|
|
* echo '<img src="'.$icon.'" alt="'.$mimetype.'" />';
|
|
* </code>
|
|
*
|
|
* @category files
|
|
* @todo MDL-31074 When an $OUTPUT->icon method is available this function should be altered
|
|
* to conform with that.
|
|
* @param string $mimetype The mimetype to fetch an icon for
|
|
* @param int $size The size of the icon. Not yet implemented
|
|
* @return string The relative path to the icon
|
|
*/
|
|
function file_mimetype_icon($mimetype, $size = NULL) {
|
|
global $CFG;
|
|
|
|
$icon = mimeinfo_from_type('icon', $mimetype);
|
|
if ($size) {
|
|
if (file_exists("$CFG->dirroot/pix/f/$icon-$size.png") or file_exists("$CFG->dirroot/pix/f/$icon-$size.gif")) {
|
|
$icon = "$icon-$size";
|
|
}
|
|
}
|
|
return 'f/'.$icon;
|
|
}
|
|
|
|
/**
|
|
* Returns the relative icon path for a given file name
|
|
*
|
|
* This function should be used in conjunction with $OUTPUT->pix_url to produce
|
|
* a return the full path to an icon.
|
|
*
|
|
* <code>
|
|
* $filename = 'jpg';
|
|
* $icon = $OUTPUT->pix_url(file_extension_icon($filename));
|
|
* echo '<img src="'.$icon.'" alt="blah" />';
|
|
* </code>
|
|
*
|
|
* @todo MDL-31074 When an $OUTPUT->icon method is available this function should be altered
|
|
* to conform with that.
|
|
* @todo MDL-31074 Implement $size
|
|
* @category files
|
|
* @param string $filename The filename to get the icon for
|
|
* @param int $size The size of the icon. Defaults to null can also be 32
|
|
* @return string
|
|
*/
|
|
function file_extension_icon($filename, $size = NULL) {
|
|
global $CFG;
|
|
|
|
$icon = mimeinfo('icon', $filename);
|
|
if ($size) {
|
|
if (file_exists("$CFG->dirroot/pix/f/$icon-$size.png") or file_exists("$CFG->dirroot/pix/f/$icon-$size.gif")) {
|
|
$icon = "$icon-$size";
|
|
}
|
|
}
|
|
return 'f/'.$icon;
|
|
}
|
|
|
|
/**
|
|
* Obtains descriptions for file types (e.g. 'Microsoft Word document') from the
|
|
* mimetypes.php language file.
|
|
*
|
|
* @param string $mimetype MIME type (can be obtained using the mimeinfo function)
|
|
* @param bool $capitalise If true, capitalises first character of result
|
|
* @return string Text description
|
|
*/
|
|
function get_mimetype_description($mimetype, $capitalise=false) {
|
|
if (get_string_manager()->string_exists($mimetype, 'mimetypes')) {
|
|
$result = get_string($mimetype, 'mimetypes');
|
|
} else {
|
|
$result = get_string('document/unknown','mimetypes');
|
|
}
|
|
if ($capitalise) {
|
|
$result=ucfirst($result);
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Requested file is not found or not accessible, does not return, terminates script
|
|
*
|
|
* @global stdClass $CFG
|
|
* @global stdClass $COURSE
|
|
*/
|
|
function send_file_not_found() {
|
|
global $CFG, $COURSE;
|
|
send_header_404();
|
|
print_error('filenotfound', 'error', $CFG->wwwroot.'/course/view.php?id='.$COURSE->id); //this is not displayed on IIS??
|
|
}
|
|
/**
|
|
* Helper function to send correct 404 for server.
|
|
*/
|
|
function send_header_404() {
|
|
if (substr(php_sapi_name(), 0, 3) == 'cgi') {
|
|
header("Status: 404 Not Found");
|
|
} else {
|
|
header('HTTP/1.0 404 not found');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check output buffering settings before sending file.
|
|
* Please note you should not send any other headers after calling this function.
|
|
*
|
|
* To be called only from lib/filelib.php !
|
|
*/
|
|
function prepare_file_content_sending() {
|
|
// We needed to be able to send headers up until now
|
|
if (headers_sent()) {
|
|
throw new file_serving_exception('Headers already sent, can not serve file.');
|
|
}
|
|
|
|
$olddebug = error_reporting(0);
|
|
|
|
// IE compatibility HACK - it does not like zlib compression much
|
|
// there is also a problem with the length header in older PHP versions
|
|
if (ini_get_bool('zlib.output_compression')) {
|
|
ini_set('zlib.output_compression', 'Off');
|
|
}
|
|
|
|
// flush and close all buffers if possible
|
|
while(ob_get_level()) {
|
|
if (!ob_end_flush()) {
|
|
// prevent infinite loop when buffer can not be closed
|
|
break;
|
|
}
|
|
}
|
|
|
|
error_reporting($olddebug);
|
|
|
|
//NOTE: we can not reliable test headers_sent() here because
|
|
// the headers might be sent which trying to close the buffers,
|
|
// this happens especially if browser does not support gzip or deflate
|
|
}
|
|
|
|
/**
|
|
* Handles the sending of temporary file to user, download is forced.
|
|
* File is deleted after abort or successful sending, does not return, script terminated
|
|
*
|
|
* @param string $path path to file, preferably from moodledata/temp/something; or content of file itself
|
|
* @param string $filename proposed file name when saving file
|
|
* @param bool $pathisstring If the path is string
|
|
*/
|
|
function send_temp_file($path, $filename, $pathisstring=false) {
|
|
global $CFG;
|
|
|
|
// close session - not needed anymore
|
|
@session_get_instance()->write_close();
|
|
|
|
if (!$pathisstring) {
|
|
if (!file_exists($path)) {
|
|
send_header_404();
|
|
print_error('filenotfound', 'error', $CFG->wwwroot.'/');
|
|
}
|
|
// executed after normal finish or abort
|
|
@register_shutdown_function('send_temp_file_finished', $path);
|
|
}
|
|
|
|
// if user is using IE, urlencode the filename so that multibyte file name will show up correctly on popup
|
|
if (check_browser_version('MSIE')) {
|
|
$filename = urlencode($filename);
|
|
}
|
|
|
|
$filesize = $pathisstring ? strlen($path) : filesize($path);
|
|
|
|
header('Content-Disposition: attachment; filename='.$filename);
|
|
header('Content-Length: '.$filesize);
|
|
if (strpos($CFG->wwwroot, 'https://') === 0) { //https sites - watch out for IE! KB812935 and KB316431
|
|
header('Cache-Control: max-age=10');
|
|
header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
|
|
header('Pragma: ');
|
|
} else { //normal http - prevent caching at all cost
|
|
header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0');
|
|
header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
|
|
header('Pragma: no-cache');
|
|
}
|
|
header('Accept-Ranges: none'); // Do not allow byteserving
|
|
|
|
//flush the buffers - save memory and disable sid rewrite
|
|
// this also disables zlib compression
|
|
prepare_file_content_sending();
|
|
|
|
// send the contents
|
|
if ($pathisstring) {
|
|
echo $path;
|
|
} else {
|
|
@readfile($path);
|
|
}
|
|
|
|
die; //no more chars to output
|
|
}
|
|
|
|
/**
|
|
* Internal callback function used by send_temp_file()
|
|
*
|
|
* @param string $path
|
|
*/
|
|
function send_temp_file_finished($path) {
|
|
if (file_exists($path)) {
|
|
@unlink($path);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles the sending of file data to the user's browser, including support for
|
|
* byteranges etc.
|
|
*
|
|
* @category files
|
|
* @global stdClass $CFG
|
|
* @global stdClass $COURSE
|
|
* @global moodle_session $SESSION
|
|
* @param string $path Path of file on disk (including real filename), or actual content of file as string
|
|
* @param string $filename Filename to send
|
|
* @param int $lifetime Number of seconds before the file should expire from caches (default 24 hours)
|
|
* @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only
|
|
* @param bool $pathisstring If true (default false), $path is the content to send and not the pathname
|
|
* @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin
|
|
* @param string $mimetype Include to specify the MIME type; leave blank to have it guess the type from $filename
|
|
* @param bool $dontdie - return control to caller afterwards. this is not recommended and only used for cleanup tasks.
|
|
* if this is passed as true, ignore_user_abort is called. if you don't want your processing to continue on cancel,
|
|
* you must detect this case when control is returned using connection_aborted. Please not that session is closed
|
|
* and should not be reopened.
|
|
* @return null script execution stopped unless $dontdie is true
|
|
*/
|
|
function send_file($path, $filename, $lifetime = 'default' , $filter=0, $pathisstring=false, $forcedownload=false, $mimetype='', $dontdie=false) {
|
|
global $CFG, $COURSE, $SESSION;
|
|
|
|
if ($dontdie) {
|
|
ignore_user_abort(true);
|
|
}
|
|
|
|
// MDL-11789, apply $CFG->filelifetime here
|
|
if ($lifetime === 'default') {
|
|
if (!empty($CFG->filelifetime)) {
|
|
$lifetime = $CFG->filelifetime;
|
|
} else {
|
|
$lifetime = 86400;
|
|
}
|
|
}
|
|
|
|
session_get_instance()->write_close(); // unlock session during fileserving
|
|
|
|
// Use given MIME type if specified, otherwise guess it using mimeinfo.
|
|
// IE, Konqueror and Opera open html file directly in browser from web even when directed to save it to disk :-O
|
|
// only Firefox saves all files locally before opening when content-disposition: attachment stated
|
|
$isFF = check_browser_version('Firefox', '1.5'); // only FF > 1.5 properly tested
|
|
$mimetype = ($forcedownload and !$isFF) ? 'application/x-forcedownload' :
|
|
($mimetype ? $mimetype : mimeinfo('type', $filename));
|
|
|
|
$lastmodified = $pathisstring ? time() : filemtime($path);
|
|
$filesize = $pathisstring ? strlen($path) : filesize($path);
|
|
|
|
/* - MDL-13949
|
|
//Adobe Acrobat Reader XSS prevention
|
|
if ($mimetype=='application/pdf' or mimeinfo('type', $filename)=='application/pdf') {
|
|
//please note that it prevents opening of pdfs in browser when http referer disabled
|
|
//or file linked from another site; browser caching of pdfs is now disabled too
|
|
if (!empty($_SERVER['HTTP_RANGE'])) {
|
|
//already byteserving
|
|
$lifetime = 1; // >0 needed for byteserving
|
|
} else if (empty($_SERVER['HTTP_REFERER']) or strpos($_SERVER['HTTP_REFERER'], $CFG->wwwroot)!==0) {
|
|
$mimetype = 'application/x-forcedownload';
|
|
$forcedownload = true;
|
|
$lifetime = 0;
|
|
} else {
|
|
$lifetime = 1; // >0 needed for byteserving
|
|
}
|
|
}
|
|
*/
|
|
|
|
if ($lifetime > 0 && !empty($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
|
|
// get unixtime of request header; clip extra junk off first
|
|
$since = strtotime(preg_replace('/;.*$/','',$_SERVER["HTTP_IF_MODIFIED_SINCE"]));
|
|
if ($since && $since >= $lastmodified) {
|
|
header('HTTP/1.1 304 Not Modified');
|
|
header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
|
|
header('Cache-Control: max-age='.$lifetime);
|
|
header('Content-Type: '.$mimetype);
|
|
if ($dontdie) {
|
|
return;
|
|
}
|
|
die;
|
|
}
|
|
}
|
|
|
|
//do not put '@' before the next header to detect incorrect moodle configurations,
|
|
//error should be better than "weird" empty lines for admins/users
|
|
header('Last-Modified: '. gmdate('D, d M Y H:i:s', $lastmodified) .' GMT');
|
|
|
|
// if user is using IE, urlencode the filename so that multibyte file name will show up correctly on popup
|
|
if (check_browser_version('MSIE')) {
|
|
$filename = rawurlencode($filename);
|
|
}
|
|
|
|
if ($forcedownload) {
|
|
header('Content-Disposition: attachment; filename="'.$filename.'"');
|
|
} else {
|
|
header('Content-Disposition: inline; filename="'.$filename.'"');
|
|
}
|
|
|
|
if ($lifetime > 0) {
|
|
header('Cache-Control: max-age='.$lifetime);
|
|
header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
|
|
header('Pragma: ');
|
|
|
|
if (empty($CFG->disablebyteserving) && !$pathisstring && $mimetype != 'text/plain' && $mimetype != 'text/html') {
|
|
|
|
header('Accept-Ranges: bytes');
|
|
|
|
if (!empty($_SERVER['HTTP_RANGE']) && strpos($_SERVER['HTTP_RANGE'],'bytes=') !== FALSE) {
|
|
// byteserving stuff - for acrobat reader and download accelerators
|
|
// see: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35
|
|
// inspired by: http://www.coneural.org/florian/papers/04_byteserving.php
|
|
$ranges = false;
|
|
if (preg_match_all('/(\d*)-(\d*)/', $_SERVER['HTTP_RANGE'], $ranges, PREG_SET_ORDER)) {
|
|
foreach ($ranges as $key=>$value) {
|
|
if ($ranges[$key][1] == '') {
|
|
//suffix case
|
|
$ranges[$key][1] = $filesize - $ranges[$key][2];
|
|
$ranges[$key][2] = $filesize - 1;
|
|
} else if ($ranges[$key][2] == '' || $ranges[$key][2] > $filesize - 1) {
|
|
//fix range length
|
|
$ranges[$key][2] = $filesize - 1;
|
|
}
|
|
if ($ranges[$key][2] != '' && $ranges[$key][2] < $ranges[$key][1]) {
|
|
//invalid byte-range ==> ignore header
|
|
$ranges = false;
|
|
break;
|
|
}
|
|
//prepare multipart header
|
|
$ranges[$key][0] = "\r\n--".BYTESERVING_BOUNDARY."\r\nContent-Type: $mimetype\r\n";
|
|
$ranges[$key][0] .= "Content-Range: bytes {$ranges[$key][1]}-{$ranges[$key][2]}/$filesize\r\n\r\n";
|
|
}
|
|
} else {
|
|
$ranges = false;
|
|
}
|
|
if ($ranges) {
|
|
$handle = fopen($path, 'rb');
|
|
byteserving_send_file($handle, $mimetype, $ranges, $filesize);
|
|
}
|
|
}
|
|
} else {
|
|
/// Do not byteserve (disabled, strings, text and html files).
|
|
header('Accept-Ranges: none');
|
|
}
|
|
} else { // Do not cache files in proxies and browsers
|
|
if (strpos($CFG->wwwroot, 'https://') === 0) { //https sites - watch out for IE! KB812935 and KB316431
|
|
header('Cache-Control: max-age=10');
|
|
header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
|
|
header('Pragma: ');
|
|
} else { //normal http - prevent caching at all cost
|
|
header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0');
|
|
header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
|
|
header('Pragma: no-cache');
|
|
}
|
|
header('Accept-Ranges: none'); // Do not allow byteserving when caching disabled
|
|
}
|
|
|
|
if (empty($filter)) {
|
|
if ($mimetype == 'text/plain') {
|
|
header('Content-Type: Text/plain; charset=utf-8'); //add encoding
|
|
} else {
|
|
header('Content-Type: '.$mimetype);
|
|
}
|
|
header('Content-Length: '.$filesize);
|
|
|
|
//flush the buffers - save memory and disable sid rewrite
|
|
//this also disables zlib compression
|
|
prepare_file_content_sending();
|
|
|
|
// send the contents
|
|
if ($pathisstring) {
|
|
echo $path;
|
|
} else {
|
|
@readfile($path);
|
|
}
|
|
|
|
} else { // Try to put the file through filters
|
|
if ($mimetype == 'text/html') {
|
|
$options = new stdClass();
|
|
$options->noclean = true;
|
|
$options->nocache = true; // temporary workaround for MDL-5136
|
|
$text = $pathisstring ? $path : implode('', file($path));
|
|
|
|
$text = file_modify_html_header($text);
|
|
$output = format_text($text, FORMAT_HTML, $options, $COURSE->id);
|
|
|
|
header('Content-Length: '.strlen($output));
|
|
header('Content-Type: text/html');
|
|
|
|
//flush the buffers - save memory and disable sid rewrite
|
|
//this also disables zlib compression
|
|
prepare_file_content_sending();
|
|
|
|
// send the contents
|
|
echo $output;
|
|
// only filter text if filter all files is selected
|
|
} else if (($mimetype == 'text/plain') and ($filter == 1)) {
|
|
$options = new stdClass();
|
|
$options->newlines = false;
|
|
$options->noclean = true;
|
|
$text = htmlentities($pathisstring ? $path : implode('', file($path)));
|
|
$output = '<pre>'. format_text($text, FORMAT_MOODLE, $options, $COURSE->id) .'</pre>';
|
|
|
|
header('Content-Length: '.strlen($output));
|
|
header('Content-Type: text/html; charset=utf-8'); //add encoding
|
|
|
|
//flush the buffers - save memory and disable sid rewrite
|
|
//this also disables zlib compression
|
|
prepare_file_content_sending();
|
|
|
|
// send the contents
|
|
echo $output;
|
|
|
|
} else { // Just send it out raw
|
|
header('Content-Length: '.$filesize);
|
|
header('Content-Type: '.$mimetype);
|
|
|
|
//flush the buffers - save memory and disable sid rewrite
|
|
//this also disables zlib compression
|
|
prepare_file_content_sending();
|
|
|
|
// send the contents
|
|
if ($pathisstring) {
|
|
echo $path;
|
|
}else {
|
|
@readfile($path);
|
|
}
|
|
}
|
|
}
|
|
if ($dontdie) {
|
|
return;
|
|
}
|
|
die; //no more chars to output!!!
|
|
}
|
|
|
|
/**
|
|
* Handles the sending of file data to the user's browser, including support for
|
|
* byteranges etc.
|
|
*
|
|
* The $options parameter supports the following keys:
|
|
* (string|null) preview - send the preview of the file (e.g. "thumb" for a thumbnail)
|
|
* (string|null) filename - overrides the implicit filename
|
|
* (bool) dontdie - return control to caller afterwards. this is not recommended and only used for cleanup tasks.
|
|
* if this is passed as true, ignore_user_abort is called. if you don't want your processing to continue on cancel,
|
|
* you must detect this case when control is returned using connection_aborted. Please not that session is closed
|
|
* and should not be reopened.
|
|
*
|
|
* @category files
|
|
* @global stdClass $CFG
|
|
* @global stdClass $COURSE
|
|
* @global moodle_session $SESSION
|
|
* @param stored_file $stored_file local file object
|
|
* @param int $lifetime Number of seconds before the file should expire from caches (default 24 hours)
|
|
* @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only
|
|
* @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin
|
|
* @param array $options additional options affecting the file serving
|
|
* @return null script execution stopped unless $options['dontdie'] is true
|
|
*/
|
|
function send_stored_file($stored_file, $lifetime=86400 , $filter=0, $forcedownload=false, array $options=array()) {
|
|
global $CFG, $COURSE, $SESSION;
|
|
|
|
if (empty($options['filename'])) {
|
|
$filename = null;
|
|
} else {
|
|
$filename = $options['filename'];
|
|
}
|
|
|
|
if (empty($options['dontdie'])) {
|
|
$dontdie = false;
|
|
} else {
|
|
$dontdie = true;
|
|
}
|
|
|
|
if (!empty($options['preview'])) {
|
|
// replace the file with its preview
|
|
$fs = get_file_storage();
|
|
$stored_file = $fs->get_file_preview($stored_file, $options['preview']);
|
|
if (!$stored_file) {
|
|
// unable to create a preview of the file
|
|
send_header_404();
|
|
die();
|
|
} else {
|
|
// preview images have fixed cache lifetime and they ignore forced download
|
|
// (they are generated by GD and therefore they are considered reasonably safe).
|
|
$lifetime = DAYSECS;
|
|
$filter = 0;
|
|
$forcedownload = false;
|
|
}
|
|
}
|
|
|
|
if (!$stored_file or $stored_file->is_directory()) {
|
|
// nothing to serve
|
|
if ($dontdie) {
|
|
return;
|
|
}
|
|
die;
|
|
}
|
|
|
|
if ($dontdie) {
|
|
ignore_user_abort(true);
|
|
}
|
|
|
|
session_get_instance()->write_close(); // unlock session during fileserving
|
|
|
|
// Use given MIME type if specified, otherwise guess it using mimeinfo.
|
|
// IE, Konqueror and Opera open html file directly in browser from web even when directed to save it to disk :-O
|
|
// only Firefox saves all files locally before opening when content-disposition: attachment stated
|
|
$filename = is_null($filename) ? $stored_file->get_filename() : $filename;
|
|
$isFF = check_browser_version('Firefox', '1.5'); // only FF > 1.5 properly tested
|
|
$mimetype = ($forcedownload and !$isFF) ? 'application/x-forcedownload' :
|
|
($stored_file->get_mimetype() ? $stored_file->get_mimetype() : mimeinfo('type', $filename));
|
|
|
|
$lastmodified = $stored_file->get_timemodified();
|
|
$filesize = $stored_file->get_filesize();
|
|
|
|
if ($lifetime > 0 && !empty($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
|
|
// get unixtime of request header; clip extra junk off first
|
|
$since = strtotime(preg_replace('/;.*$/','',$_SERVER["HTTP_IF_MODIFIED_SINCE"]));
|
|
if ($since && $since >= $lastmodified) {
|
|
header('HTTP/1.1 304 Not Modified');
|
|
header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
|
|
header('Cache-Control: max-age='.$lifetime);
|
|
header('Content-Type: '.$mimetype);
|
|
if ($dontdie) {
|
|
return;
|
|
}
|
|
die;
|
|
}
|
|
}
|
|
|
|
//do not put '@' before the next header to detect incorrect moodle configurations,
|
|
//error should be better than "weird" empty lines for admins/users
|
|
header('Last-Modified: '. gmdate('D, d M Y H:i:s', $lastmodified) .' GMT');
|
|
|
|
// if user is using IE, urlencode the filename so that multibyte file name will show up correctly on popup
|
|
if (check_browser_version('MSIE')) {
|
|
$filename = rawurlencode($filename);
|
|
}
|
|
|
|
if ($forcedownload) {
|
|
header('Content-Disposition: attachment; filename="'.$filename.'"');
|
|
} else {
|
|
header('Content-Disposition: inline; filename="'.$filename.'"');
|
|
}
|
|
|
|
if ($lifetime > 0) {
|
|
header('Cache-Control: max-age='.$lifetime);
|
|
header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
|
|
header('Pragma: ');
|
|
|
|
if (empty($CFG->disablebyteserving) && $mimetype != 'text/plain' && $mimetype != 'text/html') {
|
|
|
|
header('Accept-Ranges: bytes');
|
|
|
|
if (!empty($_SERVER['HTTP_RANGE']) && strpos($_SERVER['HTTP_RANGE'],'bytes=') !== FALSE) {
|
|
// byteserving stuff - for acrobat reader and download accelerators
|
|
// see: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35
|
|
// inspired by: http://www.coneural.org/florian/papers/04_byteserving.php
|
|
$ranges = false;
|
|
if (preg_match_all('/(\d*)-(\d*)/', $_SERVER['HTTP_RANGE'], $ranges, PREG_SET_ORDER)) {
|
|
foreach ($ranges as $key=>$value) {
|
|
if ($ranges[$key][1] == '') {
|
|
//suffix case
|
|
$ranges[$key][1] = $filesize - $ranges[$key][2];
|
|
$ranges[$key][2] = $filesize - 1;
|
|
} else if ($ranges[$key][2] == '' || $ranges[$key][2] > $filesize - 1) {
|
|
//fix range length
|
|
$ranges[$key][2] = $filesize - 1;
|
|
}
|
|
if ($ranges[$key][2] != '' && $ranges[$key][2] < $ranges[$key][1]) {
|
|
//invalid byte-range ==> ignore header
|
|
$ranges = false;
|
|
break;
|
|
}
|
|
//prepare multipart header
|
|
$ranges[$key][0] = "\r\n--".BYTESERVING_BOUNDARY."\r\nContent-Type: $mimetype\r\n";
|
|
$ranges[$key][0] .= "Content-Range: bytes {$ranges[$key][1]}-{$ranges[$key][2]}/$filesize\r\n\r\n";
|
|
}
|
|
} else {
|
|
$ranges = false;
|
|
}
|
|
if ($ranges) {
|
|
byteserving_send_file($stored_file->get_content_file_handle(), $mimetype, $ranges, $filesize);
|
|
}
|
|
}
|
|
} else {
|
|
/// Do not byteserve (disabled, strings, text and html files).
|
|
header('Accept-Ranges: none');
|
|
}
|
|
} else { // Do not cache files in proxies and browsers
|
|
if (strpos($CFG->wwwroot, 'https://') === 0) { //https sites - watch out for IE! KB812935 and KB316431
|
|
header('Cache-Control: max-age=10');
|
|
header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
|
|
header('Pragma: ');
|
|
} else { //normal http - prevent caching at all cost
|
|
header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0');
|
|
header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
|
|
header('Pragma: no-cache');
|
|
}
|
|
header('Accept-Ranges: none'); // Do not allow byteserving when caching disabled
|
|
}
|
|
|
|
if (empty($filter)) {
|
|
if ($mimetype == 'text/plain') {
|
|
header('Content-Type: Text/plain; charset=utf-8'); //add encoding
|
|
} else {
|
|
header('Content-Type: '.$mimetype);
|
|
}
|
|
header('Content-Length: '.$filesize);
|
|
|
|
//flush the buffers - save memory and disable sid rewrite
|
|
//this also disables zlib compression
|
|
prepare_file_content_sending();
|
|
|
|
// send the contents
|
|
$stored_file->readfile();
|
|
|
|
} else { // Try to put the file through filters
|
|
if ($mimetype == 'text/html') {
|
|
$options = new stdClass();
|
|
$options->noclean = true;
|
|
$options->nocache = true; // temporary workaround for MDL-5136
|
|
$text = $stored_file->get_content();
|
|
$text = file_modify_html_header($text);
|
|
$output = format_text($text, FORMAT_HTML, $options, $COURSE->id);
|
|
|
|
header('Content-Length: '.strlen($output));
|
|
header('Content-Type: text/html');
|
|
|
|
//flush the buffers - save memory and disable sid rewrite
|
|
//this also disables zlib compression
|
|
prepare_file_content_sending();
|
|
|
|
// send the contents
|
|
echo $output;
|
|
|
|
} else if (($mimetype == 'text/plain') and ($filter == 1)) {
|
|
// only filter text if filter all files is selected
|
|
$options = new stdClass();
|
|
$options->newlines = false;
|
|
$options->noclean = true;
|
|
$text = $stored_file->get_content();
|
|
$output = '<pre>'. format_text($text, FORMAT_MOODLE, $options, $COURSE->id) .'</pre>';
|
|
|
|
header('Content-Length: '.strlen($output));
|
|
header('Content-Type: text/html; charset=utf-8'); //add encoding
|
|
|
|
//flush the buffers - save memory and disable sid rewrite
|
|
//this also disables zlib compression
|
|
prepare_file_content_sending();
|
|
|
|
// send the contents
|
|
echo $output;
|
|
|
|
} else { // Just send it out raw
|
|
header('Content-Length: '.$filesize);
|
|
header('Content-Type: '.$mimetype);
|
|
|
|
//flush the buffers - save memory and disable sid rewrite
|
|
//this also disables zlib compression
|
|
prepare_file_content_sending();
|
|
|
|
// send the contents
|
|
$stored_file->readfile();
|
|
}
|
|
}
|
|
if ($dontdie) {
|
|
return;
|
|
}
|
|
die; //no more chars to output!!!
|
|
}
|
|
|
|
/**
|
|
* Retrieves an array of records from a CSV file and places
|
|
* them into a given table structure
|
|
*
|
|
* @global stdClass $CFG
|
|
* @global moodle_database $DB
|
|
* @param string $file The path to a CSV file
|
|
* @param string $table The table to retrieve columns from
|
|
* @return bool|array Returns an array of CSV records or false
|
|
*/
|
|
function get_records_csv($file, $table) {
|
|
global $CFG, $DB;
|
|
|
|
if (!$metacolumns = $DB->get_columns($table)) {
|
|
return false;
|
|
}
|
|
|
|
if(!($handle = @fopen($file, 'r'))) {
|
|
print_error('get_records_csv failed to open '.$file);
|
|
}
|
|
|
|
$fieldnames = fgetcsv($handle, 4096);
|
|
if(empty($fieldnames)) {
|
|
fclose($handle);
|
|
return false;
|
|
}
|
|
|
|
$columns = array();
|
|
|
|
foreach($metacolumns as $metacolumn) {
|
|
$ord = array_search($metacolumn->name, $fieldnames);
|
|
if(is_int($ord)) {
|
|
$columns[$metacolumn->name] = $ord;
|
|
}
|
|
}
|
|
|
|
$rows = array();
|
|
|
|
while (($data = fgetcsv($handle, 4096)) !== false) {
|
|
$item = new stdClass;
|
|
foreach($columns as $name => $ord) {
|
|
$item->$name = $data[$ord];
|
|
}
|
|
$rows[] = $item;
|
|
}
|
|
|
|
fclose($handle);
|
|
return $rows;
|
|
}
|
|
|
|
/**
|
|
* Create a file with CSV contents
|
|
*
|
|
* @global stdClass $CFG
|
|
* @global moodle_database $DB
|
|
* @param string $file The file to put the CSV content into
|
|
* @param array $records An array of records to write to a CSV file
|
|
* @param string $table The table to get columns from
|
|
* @return bool success
|
|
*/
|
|
function put_records_csv($file, $records, $table = NULL) {
|
|
global $CFG, $DB;
|
|
|
|
if (empty($records)) {
|
|
return true;
|
|
}
|
|
|
|
$metacolumns = NULL;
|
|
if ($table !== NULL && !$metacolumns = $DB->get_columns($table)) {
|
|
return false;
|
|
}
|
|
|
|
echo "x";
|
|
|
|
if(!($fp = @fopen($CFG->tempdir.'/'.$file, 'w'))) {
|
|
print_error('put_records_csv failed to open '.$file);
|
|
}
|
|
|
|
$proto = reset($records);
|
|
if(is_object($proto)) {
|
|
$fields_records = array_keys(get_object_vars($proto));
|
|
}
|
|
else if(is_array($proto)) {
|
|
$fields_records = array_keys($proto);
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
echo "x";
|
|
|
|
if(!empty($metacolumns)) {
|
|
$fields_table = array_map(create_function('$a', 'return $a->name;'), $metacolumns);
|
|
$fields = array_intersect($fields_records, $fields_table);
|
|
}
|
|
else {
|
|
$fields = $fields_records;
|
|
}
|
|
|
|
fwrite($fp, implode(',', $fields));
|
|
fwrite($fp, "\r\n");
|
|
|
|
foreach($records as $record) {
|
|
$array = (array)$record;
|
|
$values = array();
|
|
foreach($fields as $field) {
|
|
if(strpos($array[$field], ',')) {
|
|
$values[] = '"'.str_replace('"', '\"', $array[$field]).'"';
|
|
}
|
|
else {
|
|
$values[] = $array[$field];
|
|
}
|
|
}
|
|
fwrite($fp, implode(',', $values)."\r\n");
|
|
}
|
|
|
|
fclose($fp);
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Recursively delete the file or folder with path $location. That is,
|
|
* if it is a file delete it. If it is a folder, delete all its content
|
|
* then delete it. If $location does not exist to start, that is not
|
|
* considered an error.
|
|
*
|
|
* @param string $location the path to remove.
|
|
* @return bool
|
|
*/
|
|
function fulldelete($location) {
|
|
if (empty($location)) {
|
|
// extra safety against wrong param
|
|
return false;
|
|
}
|
|
if (is_dir($location)) {
|
|
$currdir = opendir($location);
|
|
while (false !== ($file = readdir($currdir))) {
|
|
if ($file <> ".." && $file <> ".") {
|
|
$fullfile = $location."/".$file;
|
|
if (is_dir($fullfile)) {
|
|
if (!fulldelete($fullfile)) {
|
|
return false;
|
|
}
|
|
} else {
|
|
if (!unlink($fullfile)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
closedir($currdir);
|
|
if (! rmdir($location)) {
|
|
return false;
|
|
}
|
|
|
|
} else if (file_exists($location)) {
|
|
if (!unlink($location)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Send requested byterange of file.
|
|
*
|
|
* @param resource $handle A file handle
|
|
* @param string $mimetype The mimetype for the output
|
|
* @param array $ranges An array of ranges to send
|
|
* @param string $filesize The size of the content if only one range is used
|
|
* @todo MDL-31088 check if "multipart/x-byteranges" is more compatible with current readers/browsers/servers
|
|
*/
|
|
function byteserving_send_file($handle, $mimetype, $ranges, $filesize) {
|
|
$chunksize = 1*(1024*1024); // 1MB chunks - must be less than 2MB!
|
|
if ($handle === false) {
|
|
die;
|
|
}
|
|
if (count($ranges) == 1) { //only one range requested
|
|
$length = $ranges[0][2] - $ranges[0][1] + 1;
|
|
header('HTTP/1.1 206 Partial content');
|
|
header('Content-Length: '.$length);
|
|
header('Content-Range: bytes '.$ranges[0][1].'-'.$ranges[0][2].'/'.$filesize);
|
|
header('Content-Type: '.$mimetype);
|
|
|
|
//flush the buffers - save memory and disable sid rewrite
|
|
//this also disables zlib compression
|
|
prepare_file_content_sending();
|
|
|
|
$buffer = '';
|
|
fseek($handle, $ranges[0][1]);
|
|
while (!feof($handle) && $length > 0) {
|
|
@set_time_limit(60*60); //reset time limit to 60 min - should be enough for 1 MB chunk
|
|
$buffer = fread($handle, ($chunksize < $length ? $chunksize : $length));
|
|
echo $buffer;
|
|
flush();
|
|
$length -= strlen($buffer);
|
|
}
|
|
fclose($handle);
|
|
die;
|
|
} else { // multiple ranges requested - not tested much
|
|
$totallength = 0;
|
|
foreach($ranges as $range) {
|
|
$totallength += strlen($range[0]) + $range[2] - $range[1] + 1;
|
|
}
|
|
$totallength += strlen("\r\n--".BYTESERVING_BOUNDARY."--\r\n");
|
|
header('HTTP/1.1 206 Partial content');
|
|
header('Content-Length: '.$totallength);
|
|
header('Content-Type: multipart/byteranges; boundary='.BYTESERVING_BOUNDARY);
|
|
//TODO: check if "multipart/x-byteranges" is more compatible with current readers/browsers/servers
|
|
|
|
//flush the buffers - save memory and disable sid rewrite
|
|
//this also disables zlib compression
|
|
prepare_file_content_sending();
|
|
|
|
foreach($ranges as $range) {
|
|
$length = $range[2] - $range[1] + 1;
|
|
echo $range[0];
|
|
$buffer = '';
|
|
fseek($handle, $range[1]);
|
|
while (!feof($handle) && $length > 0) {
|
|
@set_time_limit(60*60); //reset time limit to 60 min - should be enough for 1 MB chunk
|
|
$buffer = fread($handle, ($chunksize < $length ? $chunksize : $length));
|
|
echo $buffer;
|
|
flush();
|
|
$length -= strlen($buffer);
|
|
}
|
|
}
|
|
echo "\r\n--".BYTESERVING_BOUNDARY."--\r\n";
|
|
fclose($handle);
|
|
die;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* add includes (js and css) into uploaded files
|
|
* before returning them, useful for themes and utf.js includes
|
|
*
|
|
* @global stdClass $CFG
|
|
* @param string $text text to search and replace
|
|
* @return string text with added head includes
|
|
* @todo MDL-21120
|
|
*/
|
|
function file_modify_html_header($text) {
|
|
// first look for <head> tag
|
|
global $CFG;
|
|
|
|
$stylesheetshtml = '';
|
|
/* foreach ($CFG->stylesheets as $stylesheet) {
|
|
//TODO: MDL-21120
|
|
$stylesheetshtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
|
|
}*/
|
|
|
|
$ufo = '';
|
|
if (filter_is_enabled('filter/mediaplugin')) {
|
|
// this script is needed by most media filter plugins.
|
|
$attributes = array('type'=>'text/javascript', 'src'=>$CFG->httpswwwroot . '/lib/ufo.js');
|
|
$ufo = html_writer::tag('script', '', $attributes) . "\n";
|
|
}
|
|
|
|
preg_match('/\<head\>|\<HEAD\>/', $text, $matches);
|
|
if ($matches) {
|
|
$replacement = '<head>'.$ufo.$stylesheetshtml;
|
|
$text = preg_replace('/\<head\>|\<HEAD\>/', $replacement, $text, 1);
|
|
return $text;
|
|
}
|
|
|
|
// if not, look for <html> tag, and stick <head> right after
|
|
preg_match('/\<html\>|\<HTML\>/', $text, $matches);
|
|
if ($matches) {
|
|
// replace <html> tag with <html><head>includes</head>
|
|
$replacement = '<html>'."\n".'<head>'.$ufo.$stylesheetshtml.'</head>';
|
|
$text = preg_replace('/\<html\>|\<HTML\>/', $replacement, $text, 1);
|
|
return $text;
|
|
}
|
|
|
|
// if not, look for <body> tag, and stick <head> before body
|
|
preg_match('/\<body\>|\<BODY\>/', $text, $matches);
|
|
if ($matches) {
|
|
$replacement = '<head>'.$ufo.$stylesheetshtml.'</head>'."\n".'<body>';
|
|
$text = preg_replace('/\<body\>|\<BODY\>/', $replacement, $text, 1);
|
|
return $text;
|
|
}
|
|
|
|
// if not, just stick a <head> tag at the beginning
|
|
$text = '<head>'.$ufo.$stylesheetshtml.'</head>'."\n".$text;
|
|
return $text;
|
|
}
|
|
|
|
/**
|
|
* RESTful cURL class
|
|
*
|
|
* This is a wrapper class for curl, it is quite easy to use:
|
|
* <code>
|
|
* $c = new curl;
|
|
* // enable cache
|
|
* $c = new curl(array('cache'=>true));
|
|
* // enable cookie
|
|
* $c = new curl(array('cookie'=>true));
|
|
* // enable proxy
|
|
* $c = new curl(array('proxy'=>true));
|
|
*
|
|
* // HTTP GET Method
|
|
* $html = $c->get('http://example.com');
|
|
* // HTTP POST Method
|
|
* $html = $c->post('http://example.com/', array('q'=>'words', 'name'=>'moodle'));
|
|
* // HTTP PUT Method
|
|
* $html = $c->put('http://example.com/', array('file'=>'/var/www/test.txt');
|
|
* </code>
|
|
*
|
|
* @package core_files
|
|
* @category files
|
|
* @copyright Dongsheng Cai <dongsheng@moodle.com>
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
|
|
*/
|
|
class curl {
|
|
/** @var bool Caches http request contents */
|
|
public $cache = false;
|
|
/** @var bool Uses proxy */
|
|
public $proxy = false;
|
|
/** @var string library version */
|
|
public $version = '0.4 dev';
|
|
/** @var array http's response */
|
|
public $response = array();
|
|
/** @var array http header */
|
|
public $header = array();
|
|
/** @var string cURL information */
|
|
public $info;
|
|
/** @var string error */
|
|
public $error;
|
|
|
|
/** @var array cURL options */
|
|
private $options;
|
|
/** @var string Proxy host */
|
|
private $proxy_host = '';
|
|
/** @var string Proxy auth */
|
|
private $proxy_auth = '';
|
|
/** @var string Proxy type */
|
|
private $proxy_type = '';
|
|
/** @var bool Debug mode on */
|
|
private $debug = false;
|
|
/** @var bool|string Path to cookie file */
|
|
private $cookie = false;
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @global stdClass $CFG
|
|
* @param array $options
|
|
*/
|
|
public function __construct($options = array()){
|
|
global $CFG;
|
|
if (!function_exists('curl_init')) {
|
|
$this->error = 'cURL module must be enabled!';
|
|
trigger_error($this->error, E_USER_ERROR);
|
|
return false;
|
|
}
|
|
// the options of curl should be init here.
|
|
$this->resetopt();
|
|
if (!empty($options['debug'])) {
|
|
$this->debug = true;
|
|
}
|
|
if(!empty($options['cookie'])) {
|
|
if($options['cookie'] === true) {
|
|
$this->cookie = $CFG->dataroot.'/curl_cookie.txt';
|
|
} else {
|
|
$this->cookie = $options['cookie'];
|
|
}
|
|
}
|
|
if (!empty($options['cache'])) {
|
|
if (class_exists('curl_cache')) {
|
|
if (!empty($options['module_cache'])) {
|
|
$this->cache = new curl_cache($options['module_cache']);
|
|
} else {
|
|
$this->cache = new curl_cache('misc');
|
|
}
|
|
}
|
|
}
|
|
if (!empty($CFG->proxyhost)) {
|
|
if (empty($CFG->proxyport)) {
|
|
$this->proxy_host = $CFG->proxyhost;
|
|
} else {
|
|
$this->proxy_host = $CFG->proxyhost.':'.$CFG->proxyport;
|
|
}
|
|
if (!empty($CFG->proxyuser) and !empty($CFG->proxypassword)) {
|
|
$this->proxy_auth = $CFG->proxyuser.':'.$CFG->proxypassword;
|
|
$this->setopt(array(
|
|
'proxyauth'=> CURLAUTH_BASIC | CURLAUTH_NTLM,
|
|
'proxyuserpwd'=>$this->proxy_auth));
|
|
}
|
|
if (!empty($CFG->proxytype)) {
|
|
if ($CFG->proxytype == 'SOCKS5') {
|
|
$this->proxy_type = CURLPROXY_SOCKS5;
|
|
} else {
|
|
$this->proxy_type = CURLPROXY_HTTP;
|
|
$this->setopt(array('httpproxytunnel'=>false));
|
|
}
|
|
$this->setopt(array('proxytype'=>$this->proxy_type));
|
|
}
|
|
}
|
|
if (!empty($this->proxy_host)) {
|
|
$this->proxy = array('proxy'=>$this->proxy_host);
|
|
}
|
|
}
|
|
/**
|
|
* Resets the CURL options that have already been set
|
|
*/
|
|
public function resetopt(){
|
|
$this->options = array();
|
|
$this->options['CURLOPT_USERAGENT'] = 'MoodleBot/1.0';
|
|
// True to include the header in the output
|
|
$this->options['CURLOPT_HEADER'] = 0;
|
|
// True to Exclude the body from the output
|
|
$this->options['CURLOPT_NOBODY'] = 0;
|
|
// TRUE to follow any "Location: " header that the server
|
|
// sends as part of the HTTP header (note this is recursive,
|
|
// PHP will follow as many "Location: " headers that it is sent,
|
|
// unless CURLOPT_MAXREDIRS is set).
|
|
//$this->options['CURLOPT_FOLLOWLOCATION'] = 1;
|
|
$this->options['CURLOPT_MAXREDIRS'] = 10;
|
|
$this->options['CURLOPT_ENCODING'] = '';
|
|
// TRUE to return the transfer as a string of the return
|
|
// value of curl_exec() instead of outputting it out directly.
|
|
$this->options['CURLOPT_RETURNTRANSFER'] = 1;
|
|
$this->options['CURLOPT_BINARYTRANSFER'] = 0;
|
|
$this->options['CURLOPT_SSL_VERIFYPEER'] = 0;
|
|
$this->options['CURLOPT_SSL_VERIFYHOST'] = 2;
|
|
$this->options['CURLOPT_CONNECTTIMEOUT'] = 30;
|
|
}
|
|
|
|
/**
|
|
* Reset Cookie
|
|
*/
|
|
public function resetcookie() {
|
|
if (!empty($this->cookie)) {
|
|
if (is_file($this->cookie)) {
|
|
$fp = fopen($this->cookie, 'w');
|
|
if (!empty($fp)) {
|
|
fwrite($fp, '');
|
|
fclose($fp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set curl options
|
|
*
|
|
* @param array $options If array is null, this function will
|
|
* reset the options to default value.
|
|
*/
|
|
public function setopt($options = array()) {
|
|
if (is_array($options)) {
|
|
foreach($options as $name => $val){
|
|
if (stripos($name, 'CURLOPT_') === false) {
|
|
$name = strtoupper('CURLOPT_'.$name);
|
|
}
|
|
$this->options[$name] = $val;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reset http method
|
|
*/
|
|
public function cleanopt(){
|
|
unset($this->options['CURLOPT_HTTPGET']);
|
|
unset($this->options['CURLOPT_POST']);
|
|
unset($this->options['CURLOPT_POSTFIELDS']);
|
|
unset($this->options['CURLOPT_PUT']);
|
|
unset($this->options['CURLOPT_INFILE']);
|
|
unset($this->options['CURLOPT_INFILESIZE']);
|
|
unset($this->options['CURLOPT_CUSTOMREQUEST']);
|
|
}
|
|
|
|
/**
|
|
* Set HTTP Request Header
|
|
*
|
|
* @param array $header
|
|
*/
|
|
public function setHeader($header) {
|
|
if (is_array($header)){
|
|
foreach ($header as $v) {
|
|
$this->setHeader($v);
|
|
}
|
|
} else {
|
|
$this->header[] = $header;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set HTTP Response Header
|
|
*
|
|
*/
|
|
public function getResponse(){
|
|
return $this->response;
|
|
}
|
|
|
|
/**
|
|
* private callback function
|
|
* Formatting HTTP Response Header
|
|
*
|
|
* @param resource $ch Apparently not used
|
|
* @param string $header
|
|
* @return int The strlen of the header
|
|
*/
|
|
private function formatHeader($ch, $header)
|
|
{
|
|
$this->count++;
|
|
if (strlen($header) > 2) {
|
|
list($key, $value) = explode(" ", rtrim($header, "\r\n"), 2);
|
|
$key = rtrim($key, ':');
|
|
if (!empty($this->response[$key])) {
|
|
if (is_array($this->response[$key])){
|
|
$this->response[$key][] = $value;
|
|
} else {
|
|
$tmp = $this->response[$key];
|
|
$this->response[$key] = array();
|
|
$this->response[$key][] = $tmp;
|
|
$this->response[$key][] = $value;
|
|
|
|
}
|
|
} else {
|
|
$this->response[$key] = $value;
|
|
}
|
|
}
|
|
return strlen($header);
|
|
}
|
|
|
|
/**
|
|
* Set options for individual curl instance
|
|
*
|
|
* @param resource $curl A curl handle
|
|
* @param array $options
|
|
* @return resource The curl handle
|
|
*/
|
|
private function apply_opt($curl, $options) {
|
|
// Clean up
|
|
$this->cleanopt();
|
|
// set cookie
|
|
if (!empty($this->cookie) || !empty($options['cookie'])) {
|
|
$this->setopt(array('cookiejar'=>$this->cookie,
|
|
'cookiefile'=>$this->cookie
|
|
));
|
|
}
|
|
|
|
// set proxy
|
|
if (!empty($this->proxy) || !empty($options['proxy'])) {
|
|
$this->setopt($this->proxy);
|
|
}
|
|
$this->setopt($options);
|
|
// reset before set options
|
|
curl_setopt($curl, CURLOPT_HEADERFUNCTION, array(&$this,'formatHeader'));
|
|
// set headers
|
|
if (empty($this->header)){
|
|
$this->setHeader(array(
|
|
'User-Agent: MoodleBot/1.0',
|
|
'Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7',
|
|
'Connection: keep-alive'
|
|
));
|
|
}
|
|
curl_setopt($curl, CURLOPT_HTTPHEADER, $this->header);
|
|
|
|
if ($this->debug){
|
|
echo '<h1>Options</h1>';
|
|
var_dump($this->options);
|
|
echo '<h1>Header</h1>';
|
|
var_dump($this->header);
|
|
}
|
|
|
|
// set options
|
|
foreach($this->options as $name => $val) {
|
|
if (is_string($name)) {
|
|
$name = constant(strtoupper($name));
|
|
}
|
|
curl_setopt($curl, $name, $val);
|
|
}
|
|
return $curl;
|
|
}
|
|
|
|
/**
|
|
* Download multiple files in parallel
|
|
*
|
|
* Calls {@link multi()} with specific download headers
|
|
*
|
|
* <code>
|
|
* $c = new curl;
|
|
* $c->download(array(
|
|
* array('url'=>'http://localhost/', 'file'=>fopen('a', 'wb')),
|
|
* array('url'=>'http://localhost/20/', 'file'=>fopen('b', 'wb'))
|
|
* ));
|
|
* </code>
|
|
*
|
|
* @param array $requests An array of files to request
|
|
* @param array $options An array of options to set
|
|
* @return array An array of results
|
|
*/
|
|
public function download($requests, $options = array()) {
|
|
$options['CURLOPT_BINARYTRANSFER'] = 1;
|
|
$options['RETURNTRANSFER'] = false;
|
|
return $this->multi($requests, $options);
|
|
}
|
|
|
|
/**
|
|
* Mulit HTTP Requests
|
|
* This function could run multi-requests in parallel.
|
|
*
|
|
* @param array $requests An array of files to request
|
|
* @param array $options An array of options to set
|
|
* @return array An array of results
|
|
*/
|
|
protected function multi($requests, $options = array()) {
|
|
$count = count($requests);
|
|
$handles = array();
|
|
$results = array();
|
|
$main = curl_multi_init();
|
|
for ($i = 0; $i < $count; $i++) {
|
|
$url = $requests[$i];
|
|
foreach($url as $n=>$v){
|
|
$options[$n] = $url[$n];
|
|
}
|
|
$handles[$i] = curl_init($url['url']);
|
|
$this->apply_opt($handles[$i], $options);
|
|
curl_multi_add_handle($main, $handles[$i]);
|
|
}
|
|
$running = 0;
|
|
do {
|
|
curl_multi_exec($main, $running);
|
|
} while($running > 0);
|
|
for ($i = 0; $i < $count; $i++) {
|
|
if (!empty($options['CURLOPT_RETURNTRANSFER'])) {
|
|
$results[] = true;
|
|
} else {
|
|
$results[] = curl_multi_getcontent($handles[$i]);
|
|
}
|
|
curl_multi_remove_handle($main, $handles[$i]);
|
|
}
|
|
curl_multi_close($main);
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Single HTTP Request
|
|
*
|
|
* @param string $url The URL to request
|
|
* @param array $options
|
|
* @return bool
|
|
*/
|
|
protected function request($url, $options = array()){
|
|
// create curl instance
|
|
$curl = curl_init($url);
|
|
$options['url'] = $url;
|
|
$this->apply_opt($curl, $options);
|
|
if ($this->cache && $ret = $this->cache->get($this->options)) {
|
|
return $ret;
|
|
} else {
|
|
$ret = curl_exec($curl);
|
|
if ($this->cache) {
|
|
$this->cache->set($this->options, $ret);
|
|
}
|
|
}
|
|
|
|
$this->info = curl_getinfo($curl);
|
|
$this->error = curl_error($curl);
|
|
|
|
if ($this->debug){
|
|
echo '<h1>Return Data</h1>';
|
|
var_dump($ret);
|
|
echo '<h1>Info</h1>';
|
|
var_dump($this->info);
|
|
echo '<h1>Error</h1>';
|
|
var_dump($this->error);
|
|
}
|
|
|
|
curl_close($curl);
|
|
|
|
if (empty($this->error)){
|
|
return $ret;
|
|
} else {
|
|
return $this->error;
|
|
// exception is not ajax friendly
|
|
//throw new moodle_exception($this->error, 'curl');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* HTTP HEAD method
|
|
*
|
|
* @see request()
|
|
*
|
|
* @param string $url
|
|
* @param array $options
|
|
* @return bool
|
|
*/
|
|
public function head($url, $options = array()){
|
|
$options['CURLOPT_HTTPGET'] = 0;
|
|
$options['CURLOPT_HEADER'] = 1;
|
|
$options['CURLOPT_NOBODY'] = 1;
|
|
return $this->request($url, $options);
|
|
}
|
|
|
|
/**
|
|
* HTTP POST method
|
|
*
|
|
* @param string $url
|
|
* @param array|string $params
|
|
* @param array $options
|
|
* @return bool
|
|
*/
|
|
public function post($url, $params = '', $options = array()){
|
|
$options['CURLOPT_POST'] = 1;
|
|
if (is_array($params)) {
|
|
$this->_tmp_file_post_params = array();
|
|
foreach ($params as $key => $value) {
|
|
if ($value instanceof stored_file) {
|
|
$value->add_to_curl_request($this, $key);
|
|
} else {
|
|
$this->_tmp_file_post_params[$key] = $value;
|
|
}
|
|
}
|
|
$options['CURLOPT_POSTFIELDS'] = $this->_tmp_file_post_params;
|
|
unset($this->_tmp_file_post_params);
|
|
} else {
|
|
// $params is the raw post data
|
|
$options['CURLOPT_POSTFIELDS'] = $params;
|
|
}
|
|
return $this->request($url, $options);
|
|
}
|
|
|
|
/**
|
|
* HTTP GET method
|
|
*
|
|
* @param string $url
|
|
* @param array $params
|
|
* @param array $options
|
|
* @return bool
|
|
*/
|
|
public function get($url, $params = array(), $options = array()){
|
|
$options['CURLOPT_HTTPGET'] = 1;
|
|
|
|
if (!empty($params)){
|
|
$url .= (stripos($url, '?') !== false) ? '&' : '?';
|
|
$url .= http_build_query($params, '', '&');
|
|
}
|
|
return $this->request($url, $options);
|
|
}
|
|
|
|
/**
|
|
* HTTP PUT method
|
|
*
|
|
* @param string $url
|
|
* @param array $params
|
|
* @param array $options
|
|
* @return bool
|
|
*/
|
|
public function put($url, $params = array(), $options = array()){
|
|
$file = $params['file'];
|
|
if (!is_file($file)){
|
|
return null;
|
|
}
|
|
$fp = fopen($file, 'r');
|
|
$size = filesize($file);
|
|
$options['CURLOPT_PUT'] = 1;
|
|
$options['CURLOPT_INFILESIZE'] = $size;
|
|
$options['CURLOPT_INFILE'] = $fp;
|
|
if (!isset($this->options['CURLOPT_USERPWD'])){
|
|
$this->setopt(array('CURLOPT_USERPWD'=>'anonymous: noreply@moodle.org'));
|
|
}
|
|
$ret = $this->request($url, $options);
|
|
fclose($fp);
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* HTTP DELETE method
|
|
*
|
|
* @param string $url
|
|
* @param array $param
|
|
* @param array $options
|
|
* @return bool
|
|
*/
|
|
public function delete($url, $param = array(), $options = array()){
|
|
$options['CURLOPT_CUSTOMREQUEST'] = 'DELETE';
|
|
if (!isset($options['CURLOPT_USERPWD'])) {
|
|
$options['CURLOPT_USERPWD'] = 'anonymous: noreply@moodle.org';
|
|
}
|
|
$ret = $this->request($url, $options);
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* HTTP TRACE method
|
|
*
|
|
* @param string $url
|
|
* @param array $options
|
|
* @return bool
|
|
*/
|
|
public function trace($url, $options = array()){
|
|
$options['CURLOPT_CUSTOMREQUEST'] = 'TRACE';
|
|
$ret = $this->request($url, $options);
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* HTTP OPTIONS method
|
|
*
|
|
* @param string $url
|
|
* @param array $options
|
|
* @return bool
|
|
*/
|
|
public function options($url, $options = array()){
|
|
$options['CURLOPT_CUSTOMREQUEST'] = 'OPTIONS';
|
|
$ret = $this->request($url, $options);
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* Get curl information
|
|
*
|
|
* @return string
|
|
*/
|
|
public function get_info() {
|
|
return $this->info;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This class is used by cURL class, use case:
|
|
*
|
|
* <code>
|
|
* $CFG->repositorycacheexpire = 120;
|
|
* $CFG->curlcache = 120;
|
|
*
|
|
* $c = new curl(array('cache'=>true), 'module_cache'=>'repository');
|
|
* $ret = $c->get('http://www.google.com');
|
|
* </code>
|
|
*
|
|
* @package core_files
|
|
* @copyright Dongsheng Cai <dongsheng@moodle.com>
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
*/
|
|
class curl_cache {
|
|
/** @var string Path to cache directory */
|
|
public $dir = '';
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @global stdClass $CFG
|
|
* @param string $module which module is using curl_cache
|
|
*/
|
|
function __construct($module = 'repository'){
|
|
global $CFG;
|
|
if (!empty($module)) {
|
|
$this->dir = $CFG->cachedir.'/'.$module.'/';
|
|
} else {
|
|
$this->dir = $CFG->cachedir.'/misc/';
|
|
}
|
|
if (!file_exists($this->dir)) {
|
|
mkdir($this->dir, $CFG->directorypermissions, true);
|
|
}
|
|
if ($module == 'repository') {
|
|
if (empty($CFG->repositorycacheexpire)) {
|
|
$CFG->repositorycacheexpire = 120;
|
|
}
|
|
$this->ttl = $CFG->repositorycacheexpire;
|
|
} else {
|
|
if (empty($CFG->curlcache)) {
|
|
$CFG->curlcache = 120;
|
|
}
|
|
$this->ttl = $CFG->curlcache;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get cached value
|
|
*
|
|
* @global stdClass $CFG
|
|
* @global stdClass $USER
|
|
* @param mixed $param
|
|
* @return bool|string
|
|
*/
|
|
public function get($param){
|
|
global $CFG, $USER;
|
|
$this->cleanup($this->ttl);
|
|
$filename = 'u'.$USER->id.'_'.md5(serialize($param));
|
|
if(file_exists($this->dir.$filename)) {
|
|
$lasttime = filemtime($this->dir.$filename);
|
|
if(time()-$lasttime > $this->ttl)
|
|
{
|
|
return false;
|
|
} else {
|
|
$fp = fopen($this->dir.$filename, 'r');
|
|
$size = filesize($this->dir.$filename);
|
|
$content = fread($fp, $size);
|
|
return unserialize($content);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Set cache value
|
|
*
|
|
* @global object $CFG
|
|
* @global object $USER
|
|
* @param mixed $param
|
|
* @param mixed $val
|
|
*/
|
|
public function set($param, $val){
|
|
global $CFG, $USER;
|
|
$filename = 'u'.$USER->id.'_'.md5(serialize($param));
|
|
$fp = fopen($this->dir.$filename, 'w');
|
|
fwrite($fp, serialize($val));
|
|
fclose($fp);
|
|
}
|
|
|
|
/**
|
|
* Remove cache files
|
|
*
|
|
* @param int $expire The number os seconds before expiry
|
|
*/
|
|
public function cleanup($expire){
|
|
if($dir = opendir($this->dir)){
|
|
while (false !== ($file = readdir($dir))) {
|
|
if(!is_dir($file) && $file != '.' && $file != '..') {
|
|
$lasttime = @filemtime($this->dir.$file);
|
|
if(time() - $lasttime > $expire){
|
|
@unlink($this->dir.$file);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* delete current user's cache file
|
|
*
|
|
* @global object $CFG
|
|
* @global object $USER
|
|
*/
|
|
public function refresh(){
|
|
global $CFG, $USER;
|
|
if($dir = opendir($this->dir)){
|
|
while (false !== ($file = readdir($dir))) {
|
|
if(!is_dir($file) && $file != '.' && $file != '..') {
|
|
if(strpos($file, 'u'.$USER->id.'_')!==false){
|
|
@unlink($this->dir.$file);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This class is used to parse lib/file/file_types.mm which help get file extensions by file types.
|
|
*
|
|
* The file_types.mm file can be edited by freemind in graphic environment.
|
|
*
|
|
* @package core_files
|
|
* @category files
|
|
* @copyright 2009 Dongsheng Cai <dongsheng@moodle.com>
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
*/
|
|
class filetype_parser {
|
|
/**
|
|
* Check file_types.mm file, setup variables
|
|
*
|
|
* @global stdClass $CFG
|
|
* @param string $file
|
|
*/
|
|
public function __construct($file = '') {
|
|
global $CFG;
|
|
if (empty($file)) {
|
|
$this->file = $CFG->libdir.'/filestorage/file_types.mm';
|
|
} else {
|
|
$this->file = $file;
|
|
}
|
|
$this->tree = array();
|
|
$this->result = array();
|
|
}
|
|
|
|
/**
|
|
* A private function to browse xml nodes
|
|
*
|
|
* @param array $parent
|
|
* @param array $types
|
|
*/
|
|
private function _browse_nodes($parent, $types) {
|
|
$key = (string)$parent['TEXT'];
|
|
if(isset($parent->node)) {
|
|
$this->tree[$key] = array();
|
|
if (in_array((string)$parent['TEXT'], $types)) {
|
|
$this->_select_nodes($parent, $this->result);
|
|
} else {
|
|
foreach($parent->node as $v){
|
|
$this->_browse_nodes($v, $types);
|
|
}
|
|
}
|
|
} else {
|
|
$this->tree[] = $key;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A private function to select text nodes
|
|
*
|
|
* @param array $parent
|
|
*/
|
|
private function _select_nodes($parent){
|
|
if(isset($parent->node)) {
|
|
foreach($parent->node as $v){
|
|
$this->_select_nodes($v, $this->result);
|
|
}
|
|
} else {
|
|
$this->result[] = (string)$parent['TEXT'];
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Get file extensions by file types names.
|
|
*
|
|
* @param array $types
|
|
* @return mixed
|
|
*/
|
|
public function get_extensions($types) {
|
|
if (!is_array($types)) {
|
|
$types = array($types);
|
|
}
|
|
$this->result = array();
|
|
if ((is_array($types) && in_array('*', $types)) ||
|
|
$types == '*' || empty($types)) {
|
|
return array('*');
|
|
}
|
|
foreach ($types as $key=>$value){
|
|
if (strpos($value, '.') !== false) {
|
|
$this->result[] = $value;
|
|
unset($types[$key]);
|
|
}
|
|
}
|
|
if (file_exists($this->file)) {
|
|
$xml = simplexml_load_file($this->file);
|
|
foreach($xml->node->node as $v){
|
|
if (in_array((string)$v['TEXT'], $types)) {
|
|
$this->_select_nodes($v);
|
|
} else {
|
|
$this->_browse_nodes($v, $types);
|
|
}
|
|
}
|
|
} else {
|
|
exit('Failed to open file lib/filestorage/file_types.mm');
|
|
}
|
|
return $this->result;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This function delegates file serving to individual plugins
|
|
*
|
|
* @param string $relativepath
|
|
* @param bool $forcedownload
|
|
* @param null|string $preview the preview mode, defaults to serving the original file
|
|
* @todo MDL-31088 file serving improments
|
|
*/
|
|
function file_pluginfile($relativepath, $forcedownload, $preview = null) {
|
|
global $DB, $CFG, $USER;
|
|
// relative path must start with '/'
|
|
if (!$relativepath) {
|
|
print_error('invalidargorconf');
|
|
} else if ($relativepath[0] != '/') {
|
|
print_error('pathdoesnotstartslash');
|
|
}
|
|
|
|
// extract relative path components
|
|
$args = explode('/', ltrim($relativepath, '/'));
|
|
|
|
if (count($args) < 3) { // always at least context, component and filearea
|
|
print_error('invalidarguments');
|
|
}
|
|
|
|
$contextid = (int)array_shift($args);
|
|
$component = clean_param(array_shift($args), PARAM_COMPONENT);
|
|
$filearea = clean_param(array_shift($args), PARAM_AREA);
|
|
|
|
list($context, $course, $cm) = get_context_info_array($contextid);
|
|
|
|
$fs = get_file_storage();
|
|
|
|
// ========================================================================================================================
|
|
if ($component === 'blog') {
|
|
// Blog file serving
|
|
if ($context->contextlevel != CONTEXT_SYSTEM) {
|
|
send_file_not_found();
|
|
}
|
|
if ($filearea !== 'attachment' and $filearea !== 'post') {
|
|
send_file_not_found();
|
|
}
|
|
|
|
if (empty($CFG->bloglevel)) {
|
|
print_error('siteblogdisable', 'blog');
|
|
}
|
|
|
|
$entryid = (int)array_shift($args);
|
|
if (!$entry = $DB->get_record('post', array('module'=>'blog', 'id'=>$entryid))) {
|
|
send_file_not_found();
|
|
}
|
|
if ($CFG->bloglevel < BLOG_GLOBAL_LEVEL) {
|
|
require_login();
|
|
if (isguestuser()) {
|
|
print_error('noguest');
|
|
}
|
|
if ($CFG->bloglevel == BLOG_USER_LEVEL) {
|
|
if ($USER->id != $entry->userid) {
|
|
send_file_not_found();
|
|
}
|
|
}
|
|
}
|
|
|
|
if ('publishstate' === 'public') {
|
|
if ($CFG->forcelogin) {
|
|
require_login();
|
|
}
|
|
|
|
} else if ('publishstate' === 'site') {
|
|
require_login();
|
|
//ok
|
|
} else if ('publishstate' === 'draft') {
|
|
require_login();
|
|
if ($USER->id != $entry->userid) {
|
|
send_file_not_found();
|
|
}
|
|
}
|
|
|
|
$filename = array_pop($args);
|
|
$filepath = $args ? '/'.implode('/', $args).'/' : '/';
|
|
|
|
if (!$file = $fs->get_file($context->id, $component, $filearea, $entryid, $filepath, $filename) or $file->is_directory()) {
|
|
send_file_not_found();
|
|
}
|
|
|
|
send_stored_file($file, 10*60, 0, true, array('preview' => $preview)); // download MUST be forced - security!
|
|
|
|
// ========================================================================================================================
|
|
} else if ($component === 'grade') {
|
|
if (($filearea === 'outcome' or $filearea === 'scale') and $context->contextlevel == CONTEXT_SYSTEM) {
|
|
// Global gradebook files
|
|
if ($CFG->forcelogin) {
|
|
require_login();
|
|
}
|
|
|
|
$fullpath = "/$context->id/$component/$filearea/".implode('/', $args);
|
|
|
|
if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
|
|
send_file_not_found();
|
|
}
|
|
|
|
session_get_instance()->write_close(); // unlock session during fileserving
|
|
send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
|
|
|
|
} else if ($filearea === 'feedback' and $context->contextlevel == CONTEXT_COURSE) {
|
|
//TODO: nobody implemented this yet in grade edit form!!
|
|
send_file_not_found();
|
|
|
|
if ($CFG->forcelogin || $course->id != SITEID) {
|
|
require_login($course);
|
|
}
|
|
|
|
$fullpath = "/$context->id/$component/$filearea/".implode('/', $args);
|
|
|
|
if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
|
|
send_file_not_found();
|
|
}
|
|
|
|
session_get_instance()->write_close(); // unlock session during fileserving
|
|
send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
|
|
} else {
|
|
send_file_not_found();
|
|
}
|
|
|
|
// ========================================================================================================================
|
|
} else if ($component === 'tag') {
|
|
if ($filearea === 'description' and $context->contextlevel == CONTEXT_SYSTEM) {
|
|
|
|
// All tag descriptions are going to be public but we still need to respect forcelogin
|
|
if ($CFG->forcelogin) {
|
|
require_login();
|
|
}
|
|
|
|
$fullpath = "/$context->id/tag/description/".implode('/', $args);
|
|
|
|
if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
|
|
send_file_not_found();
|
|
}
|
|
|
|
session_get_instance()->write_close(); // unlock session during fileserving
|
|
send_stored_file($file, 60*60, 0, true, array('preview' => $preview));
|
|
|
|
} else {
|
|
send_file_not_found();
|
|
}
|
|
|
|
// ========================================================================================================================
|
|
} else if ($component === 'calendar') {
|
|
if ($filearea === 'event_description' and $context->contextlevel == CONTEXT_SYSTEM) {
|
|
|
|
// All events here are public the one requirement is that we respect forcelogin
|
|
if ($CFG->forcelogin) {
|
|
require_login();
|
|
}
|
|
|
|
// Get the event if from the args array
|
|
$eventid = array_shift($args);
|
|
|
|
// Load the event from the database
|
|
if (!$event = $DB->get_record('event', array('id'=>(int)$eventid, 'eventtype'=>'site'))) {
|
|
send_file_not_found();
|
|
}
|
|
// Check that we got an event and that it's userid is that of the user
|
|
|
|
// Get the file and serve if successful
|
|
$filename = array_pop($args);
|
|
$filepath = $args ? '/'.implode('/', $args).'/' : '/';
|
|
if (!$file = $fs->get_file($context->id, $component, $filearea, $eventid, $filepath, $filename) or $file->is_directory()) {
|
|
send_file_not_found();
|
|
}
|
|
|
|
session_get_instance()->write_close(); // unlock session during fileserving
|
|
send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
|
|
|
|
} else if ($filearea === 'event_description' and $context->contextlevel == CONTEXT_USER) {
|
|
|
|
// Must be logged in, if they are not then they obviously can't be this user
|
|
require_login();
|
|
|
|
// Don't want guests here, potentially saves a DB call
|
|
if (isguestuser()) {
|
|
send_file_not_found();
|
|
}
|
|
|
|
// Get the event if from the args array
|
|
$eventid = array_shift($args);
|
|
|
|
// Load the event from the database - user id must match
|
|
if (!$event = $DB->get_record('event', array('id'=>(int)$eventid, 'userid'=>$USER->id, 'eventtype'=>'user'))) {
|
|
send_file_not_found();
|
|
}
|
|
|
|
// Get the file and serve if successful
|
|
$filename = array_pop($args);
|
|
$filepath = $args ? '/'.implode('/', $args).'/' : '/';
|
|
if (!$file = $fs->get_file($context->id, $component, $filearea, $eventid, $filepath, $filename) or $file->is_directory()) {
|
|
send_file_not_found();
|
|
}
|
|
|
|
session_get_instance()->write_close(); // unlock session during fileserving
|
|
send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
|
|
|
|
} else if ($filearea === 'event_description' and $context->contextlevel == CONTEXT_COURSE) {
|
|
|
|
// Respect forcelogin and require login unless this is the site.... it probably
|
|
// should NEVER be the site
|
|
if ($CFG->forcelogin || $course->id != SITEID) {
|
|
require_login($course);
|
|
}
|
|
|
|
// Must be able to at least view the course
|
|
if (!is_enrolled($context) and !is_viewing($context)) {
|
|
//TODO: hmm, do we really want to block guests here?
|
|
send_file_not_found();
|
|
}
|
|
|
|
// Get the event id
|
|
$eventid = array_shift($args);
|
|
|
|
// Load the event from the database we need to check whether it is
|
|
// a) valid course event
|
|
// b) a group event
|
|
// Group events use the course context (there is no group context)
|
|
if (!$event = $DB->get_record('event', array('id'=>(int)$eventid, 'courseid'=>$course->id))) {
|
|
send_file_not_found();
|
|
}
|
|
|
|
// If its a group event require either membership of view all groups capability
|
|
if ($event->eventtype === 'group') {
|
|
if (!has_capability('moodle/site:accessallgroups', $context) && !groups_is_member($event->groupid, $USER->id)) {
|
|
send_file_not_found();
|
|
}
|
|
} else if ($event->eventtype === 'course') {
|
|
//ok
|
|
} else {
|
|
// some other type
|
|
send_file_not_found();
|
|
}
|
|
|
|
// If we get this far we can serve the file
|
|
$filename = array_pop($args);
|
|
$filepath = $args ? '/'.implode('/', $args).'/' : '/';
|
|
if (!$file = $fs->get_file($context->id, $component, $filearea, $eventid, $filepath, $filename) or $file->is_directory()) {
|
|
send_file_not_found();
|
|
}
|
|
|
|
session_get_instance()->write_close(); // unlock session during fileserving
|
|
send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
|
|
|
|
} else {
|
|
send_file_not_found();
|
|
}
|
|
|
|
// ========================================================================================================================
|
|
} else if ($component === 'user') {
|
|
if ($filearea === 'icon' and $context->contextlevel == CONTEXT_USER) {
|
|
$redirect = false;
|
|
if (count($args) == 1) {
|
|
$themename = theme_config::DEFAULT_THEME;
|
|
$filename = array_shift($args);
|
|
} else {
|
|
$themename = array_shift($args);
|
|
$filename = array_shift($args);
|
|
}
|
|
if ((!empty($CFG->forcelogin) and !isloggedin()) ||
|
|
(!empty($CFG->forceloginforprofileimage) && (!isloggedin() || isguestuser()))) {
|
|
// protect images if login required and not logged in;
|
|
// also if login is required for profile images and is not logged in or guest
|
|
// do not use require_login() because it is expensive and not suitable here anyway
|
|
$redirect = true;
|
|
}
|
|
if (!$redirect and ($filename !== 'f1' and $filename !== 'f2')) {
|
|
$filename = 'f1';
|
|
$redirect = true;
|
|
}
|
|
if (!$redirect && !$file = $fs->get_file($context->id, 'user', 'icon', 0, '/', $filename.'/.png')) {
|
|
if (!$file = $fs->get_file($context->id, 'user', 'icon', 0, '/', $filename.'/.jpg')) {
|
|
$redirect = true;
|
|
}
|
|
}
|
|
if ($redirect) {
|
|
$theme = theme_config::load($themename);
|
|
redirect($theme->pix_url('u/'.$filename, 'moodle'));
|
|
}
|
|
send_stored_file($file, 60*60*24, 0, false, array('preview' => $preview)); // enable long caching, there are many images on each page
|
|
|
|
} else if ($filearea === 'private' and $context->contextlevel == CONTEXT_USER) {
|
|
require_login();
|
|
|
|
if (isguestuser()) {
|
|
send_file_not_found();
|
|
}
|
|
|
|
if ($USER->id !== $context->instanceid) {
|
|
send_file_not_found();
|
|
}
|
|
|
|
$filename = array_pop($args);
|
|
$filepath = $args ? '/'.implode('/', $args).'/' : '/';
|
|
if (!$file = $fs->get_file($context->id, $component, $filearea, 0, $filepath, $filename) or $file->is_directory()) {
|
|
send_file_not_found();
|
|
}
|
|
|
|
session_get_instance()->write_close(); // unlock session during fileserving
|
|
send_stored_file($file, 0, 0, true, array('preview' => $preview)); // must force download - security!
|
|
|
|
} else if ($filearea === 'profile' and $context->contextlevel == CONTEXT_USER) {
|
|
|
|
if ($CFG->forcelogin) {
|
|
require_login();
|
|
}
|
|
|
|
$userid = $context->instanceid;
|
|
|
|
if ($USER->id == $userid) {
|
|
// always can access own
|
|
|
|
} else if (!empty($CFG->forceloginforprofiles)) {
|
|
require_login();
|
|
|
|
if (isguestuser()) {
|
|
send_file_not_found();
|
|
}
|
|
|
|
// we allow access to site profile of all course contacts (usually teachers)
|
|
if (!has_coursecontact_role($userid) && !has_capability('moodle/user:viewdetails', $context)) {
|
|
send_file_not_found();
|
|
}
|
|
|
|
$canview = false;
|
|
if (has_capability('moodle/user:viewdetails', $context)) {
|
|
$canview = true;
|
|
} else {
|
|
$courses = enrol_get_my_courses();
|
|
}
|
|
|
|
while (!$canview && count($courses) > 0) {
|
|
$course = array_shift($courses);
|
|
if (has_capability('moodle/user:viewdetails', get_context_instance(CONTEXT_COURSE, $course->id))) {
|
|
$canview = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
$filename = array_pop($args);
|
|
$filepath = $args ? '/'.implode('/', $args).'/' : '/';
|
|
if (!$file = $fs->get_file($context->id, $component, $filearea, 0, $filepath, $filename) or $file->is_directory()) {
|
|
send_file_not_found();
|
|
}
|
|
|
|
session_get_instance()->write_close(); // unlock session during fileserving
|
|
send_stored_file($file, 0, 0, true, array('preview' => $preview)); // must force download - security!
|
|
|
|
} else if ($filearea === 'profile' and $context->contextlevel == CONTEXT_COURSE) {
|
|
$userid = (int)array_shift($args);
|
|
$usercontext = get_context_instance(CONTEXT_USER, $userid);
|
|
|
|
if ($CFG->forcelogin) {
|
|
require_login();
|
|
}
|
|
|
|
if (!empty($CFG->forceloginforprofiles)) {
|
|
require_login();
|
|
if (isguestuser()) {
|
|
print_error('noguest');
|
|
}
|
|
|
|
//TODO: review this logic of user profile access prevention
|
|
if (!has_coursecontact_role($userid) and !has_capability('moodle/user:viewdetails', $usercontext)) {
|
|
print_error('usernotavailable');
|
|
}
|
|
if (!has_capability('moodle/user:viewdetails', $context) && !has_capability('moodle/user:viewdetails', $usercontext)) {
|
|
print_error('cannotviewprofile');
|
|
}
|
|
if (!is_enrolled($context, $userid)) {
|
|
print_error('notenrolledprofile');
|
|
}
|
|
if (groups_get_course_groupmode($course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
|
|
print_error('groupnotamember');
|
|
}
|
|
}
|
|
|
|
$filename = array_pop($args);
|
|
$filepath = $args ? '/'.implode('/', $args).'/' : '/';
|
|
if (!$file = $fs->get_file($usercontext->id, 'user', 'profile', 0, $filepath, $filename) or $file->is_directory()) {
|
|
send_file_not_found();
|
|
}
|
|
|
|
session_get_instance()->write_close(); // unlock session during fileserving
|
|
send_stored_file($file, 0, 0, true, array('preview' => $preview)); // must force download - security!
|
|
|
|
} else if ($filearea === 'backup' and $context->contextlevel == CONTEXT_USER) {
|
|
require_login();
|
|
|
|
if (isguestuser()) {
|
|
send_file_not_found();
|
|
}
|
|
$userid = $context->instanceid;
|
|
|
|
if ($USER->id != $userid) {
|
|
send_file_not_found();
|
|
}
|
|
|
|
$filename = array_pop($args);
|
|
$filepath = $args ? '/'.implode('/', $args).'/' : '/';
|
|
if (!$file = $fs->get_file($context->id, 'user', 'backup', 0, $filepath, $filename) or $file->is_directory()) {
|
|
send_file_not_found();
|
|
}
|
|
|
|
session_get_instance()->write_close(); // unlock session during fileserving
|
|
send_stored_file($file, 0, 0, true, array('preview' => $preview)); // must force download - security!
|
|
|
|
} else {
|
|
send_file_not_found();
|
|
}
|
|
|
|
// ========================================================================================================================
|
|
} else if ($component === 'coursecat') {
|
|
if ($context->contextlevel != CONTEXT_COURSECAT) {
|
|
send_file_not_found();
|
|
}
|
|
|
|
if ($filearea === 'description') {
|
|
if ($CFG->forcelogin) {
|
|
// no login necessary - unless login forced everywhere
|
|
require_login();
|
|
}
|
|
|
|
$filename = array_pop($args);
|
|
$filepath = $args ? '/'.implode('/', $args).'/' : '/';
|
|
if (!$file = $fs->get_file($context->id, 'coursecat', 'description', 0, $filepath, $filename) or $file->is_directory()) {
|
|
send_file_not_found();
|
|
}
|
|
|
|
session_get_instance()->write_close(); // unlock session during fileserving
|
|
send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
|
|
} else {
|
|
send_file_not_found();
|
|
}
|
|
|
|
// ========================================================================================================================
|
|
} else if ($component === 'course') {
|
|
if ($context->contextlevel != CONTEXT_COURSE) {
|
|
send_file_not_found();
|
|
}
|
|
|
|
if ($filearea === 'summary') {
|
|
if ($CFG->forcelogin) {
|
|
require_login();
|
|
}
|
|
|
|
$filename = array_pop($args);
|
|
$filepath = $args ? '/'.implode('/', $args).'/' : '/';
|
|
if (!$file = $fs->get_file($context->id, 'course', 'summary', 0, $filepath, $filename) or $file->is_directory()) {
|
|
send_file_not_found();
|
|
}
|
|
|
|
session_get_instance()->write_close(); // unlock session during fileserving
|
|
send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
|
|
|
|
} else if ($filearea === 'section') {
|
|
if ($CFG->forcelogin) {
|
|
require_login($course);
|
|
} else if ($course->id != SITEID) {
|
|
require_login($course);
|
|
}
|
|
|
|
$sectionid = (int)array_shift($args);
|
|
|
|
if (!$section = $DB->get_record('course_sections', array('id'=>$sectionid, 'course'=>$course->id))) {
|
|
send_file_not_found();
|
|
}
|
|
|
|
if ($course->numsections < $section->section) {
|
|
if (!has_capability('moodle/course:update', $context)) {
|
|
// block access to unavailable sections if can not edit course
|
|
send_file_not_found();
|
|
}
|
|
}
|
|
|
|
$filename = array_pop($args);
|
|
$filepath = $args ? '/'.implode('/', $args).'/' : '/';
|
|
if (!$file = $fs->get_file($context->id, 'course', 'section', $sectionid, $filepath, $filename) or $file->is_directory()) {
|
|
send_file_not_found();
|
|
}
|
|
|
|
session_get_instance()->write_close(); // unlock session during fileserving
|
|
send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
|
|
|
|
} else {
|
|
send_file_not_found();
|
|
}
|
|
|
|
} else if ($component === 'group') {
|
|
if ($context->contextlevel != CONTEXT_COURSE) {
|
|
send_file_not_found();
|
|
}
|
|
|
|
require_course_login($course, true, null, false);
|
|
|
|
$groupid = (int)array_shift($args);
|
|
|
|
$group = $DB->get_record('groups', array('id'=>$groupid, 'courseid'=>$course->id), '*', MUST_EXIST);
|
|
if (($course->groupmodeforce and $course->groupmode == SEPARATEGROUPS) and !has_capability('moodle/site:accessallgroups', $context) and !groups_is_member($group->id, $USER->id)) {
|
|
// do not allow access to separate group info if not member or teacher
|
|
send_file_not_found();
|
|
}
|
|
|
|
if ($filearea === 'description') {
|
|
|
|
require_login($course);
|
|
|
|
$filename = array_pop($args);
|
|
$filepath = $args ? '/'.implode('/', $args).'/' : '/';
|
|
if (!$file = $fs->get_file($context->id, 'group', 'description', $group->id, $filepath, $filename) or $file->is_directory()) {
|
|
send_file_not_found();
|
|
}
|
|
|
|
session_get_instance()->write_close(); // unlock session during fileserving
|
|
send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
|
|
|
|
} else if ($filearea === 'icon') {
|
|
$filename = array_pop($args);
|
|
|
|
if ($filename !== 'f1' and $filename !== 'f2') {
|
|
send_file_not_found();
|
|
}
|
|
if (!$file = $fs->get_file($context->id, 'group', 'icon', $group->id, '/', $filename.'.png')) {
|
|
if (!$file = $fs->get_file($context->id, 'group', 'icon', $group->id, '/', $filename.'.jpg')) {
|
|
send_file_not_found();
|
|
}
|
|
}
|
|
|
|
session_get_instance()->write_close(); // unlock session during fileserving
|
|
send_stored_file($file, 60*60, 0, false, array('preview' => $preview));
|
|
|
|
} else {
|
|
send_file_not_found();
|
|
}
|
|
|
|
} else if ($component === 'grouping') {
|
|
if ($context->contextlevel != CONTEXT_COURSE) {
|
|
send_file_not_found();
|
|
}
|
|
|
|
require_login($course);
|
|
|
|
$groupingid = (int)array_shift($args);
|
|
|
|
// note: everybody has access to grouping desc images for now
|
|
if ($filearea === 'description') {
|
|
|
|
$filename = array_pop($args);
|
|
$filepath = $args ? '/'.implode('/', $args).'/' : '/';
|
|
if (!$file = $fs->get_file($context->id, 'grouping', 'description', $groupingid, $filepath, $filename) or $file->is_directory()) {
|
|
send_file_not_found();
|
|
}
|
|
|
|
session_get_instance()->write_close(); // unlock session during fileserving
|
|
send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
|
|
|
|
} else {
|
|
send_file_not_found();
|
|
}
|
|
|
|
// ========================================================================================================================
|
|
} else if ($component === 'backup') {
|
|
if ($filearea === 'course' and $context->contextlevel == CONTEXT_COURSE) {
|
|
require_login($course);
|
|
require_capability('moodle/backup:downloadfile', $context);
|
|
|
|
$filename = array_pop($args);
|
|
$filepath = $args ? '/'.implode('/', $args).'/' : '/';
|
|
if (!$file = $fs->get_file($context->id, 'backup', 'course', 0, $filepath, $filename) or $file->is_directory()) {
|
|
send_file_not_found();
|
|
}
|
|
|
|
session_get_instance()->write_close(); // unlock session during fileserving
|
|
send_stored_file($file, 0, 0, $forcedownload, array('preview' => $preview));
|
|
|
|
} else if ($filearea === 'section' and $context->contextlevel == CONTEXT_COURSE) {
|
|
require_login($course);
|
|
require_capability('moodle/backup:downloadfile', $context);
|
|
|
|
$sectionid = (int)array_shift($args);
|
|
|
|
$filename = array_pop($args);
|
|
$filepath = $args ? '/'.implode('/', $args).'/' : '/';
|
|
if (!$file = $fs->get_file($context->id, 'backup', 'section', $sectionid, $filepath, $filename) or $file->is_directory()) {
|
|
send_file_not_found();
|
|
}
|
|
|
|
session_get_instance()->write_close();
|
|
send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
|
|
|
|
} else if ($filearea === 'activity' and $context->contextlevel == CONTEXT_MODULE) {
|
|
require_login($course, false, $cm);
|
|
require_capability('moodle/backup:downloadfile', $context);
|
|
|
|
$filename = array_pop($args);
|
|
$filepath = $args ? '/'.implode('/', $args).'/' : '/';
|
|
if (!$file = $fs->get_file($context->id, 'backup', 'activity', 0, $filepath, $filename) or $file->is_directory()) {
|
|
send_file_not_found();
|
|
}
|
|
|
|
session_get_instance()->write_close();
|
|
send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
|
|
|
|
} else if ($filearea === 'automated' and $context->contextlevel == CONTEXT_COURSE) {
|
|
// Backup files that were generated by the automated backup systems.
|
|
|
|
require_login($course);
|
|
require_capability('moodle/site:config', $context);
|
|
|
|
$filename = array_pop($args);
|
|
$filepath = $args ? '/'.implode('/', $args).'/' : '/';
|
|
if (!$file = $fs->get_file($context->id, 'backup', 'automated', 0, $filepath, $filename) or $file->is_directory()) {
|
|
send_file_not_found();
|
|
}
|
|
|
|
session_get_instance()->write_close(); // unlock session during fileserving
|
|
send_stored_file($file, 0, 0, $forcedownload, array('preview' => $preview));
|
|
|
|
} else {
|
|
send_file_not_found();
|
|
}
|
|
|
|
// ========================================================================================================================
|
|
} else if ($component === 'question') {
|
|
require_once($CFG->libdir . '/questionlib.php');
|
|
question_pluginfile($course, $context, 'question', $filearea, $args, $forcedownload);
|
|
send_file_not_found();
|
|
|
|
// ========================================================================================================================
|
|
} else if ($component === 'grading') {
|
|
if ($filearea === 'description') {
|
|
// files embedded into the form definition description
|
|
|
|
if ($context->contextlevel == CONTEXT_SYSTEM) {
|
|
require_login();
|
|
|
|
} else if ($context->contextlevel >= CONTEXT_COURSE) {
|
|
require_login($course, false, $cm);
|
|
|
|
} else {
|
|
send_file_not_found();
|
|
}
|
|
|
|
$formid = (int)array_shift($args);
|
|
|
|
$sql = "SELECT ga.id
|
|
FROM {grading_areas} ga
|
|
JOIN {grading_definitions} gd ON (gd.areaid = ga.id)
|
|
WHERE gd.id = ? AND ga.contextid = ?";
|
|
$areaid = $DB->get_field_sql($sql, array($formid, $context->id), IGNORE_MISSING);
|
|
|
|
if (!$areaid) {
|
|
send_file_not_found();
|
|
}
|
|
|
|
$fullpath = "/$context->id/$component/$filearea/$formid/".implode('/', $args);
|
|
|
|
if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
|
|
send_file_not_found();
|
|
}
|
|
|
|
session_get_instance()->write_close(); // unlock session during fileserving
|
|
send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview));
|
|
}
|
|
|
|
// ========================================================================================================================
|
|
} else if (strpos($component, 'mod_') === 0) {
|
|
$modname = substr($component, 4);
|
|
if (!file_exists("$CFG->dirroot/mod/$modname/lib.php")) {
|
|
send_file_not_found();
|
|
}
|
|
require_once("$CFG->dirroot/mod/$modname/lib.php");
|
|
|
|
if ($context->contextlevel == CONTEXT_MODULE) {
|
|
if ($cm->modname !== $modname) {
|
|
// somebody tries to gain illegal access, cm type must match the component!
|
|
send_file_not_found();
|
|
}
|
|
}
|
|
|
|
if ($filearea === 'intro') {
|
|
if (!plugin_supports('mod', $modname, FEATURE_MOD_INTRO, true)) {
|
|
send_file_not_found();
|
|
}
|
|
require_course_login($course, true, $cm);
|
|
|
|
// all users may access it
|
|
$filename = array_pop($args);
|
|
$filepath = $args ? '/'.implode('/', $args).'/' : '/';
|
|
if (!$file = $fs->get_file($context->id, 'mod_'.$modname, 'intro', 0, $filepath, $filename) or $file->is_directory()) {
|
|
send_file_not_found();
|
|
}
|
|
|
|
$lifetime = isset($CFG->filelifetime) ? $CFG->filelifetime : 86400;
|
|
|
|
// finally send the file
|
|
send_stored_file($file, $lifetime, 0, false, array('preview' => $preview));
|
|
}
|
|
|
|
$filefunction = $component.'_pluginfile';
|
|
$filefunctionold = $modname.'_pluginfile';
|
|
if (function_exists($filefunction)) {
|
|
// if the function exists, it must send the file and terminate. Whatever it returns leads to "not found"
|
|
$filefunction($course, $cm, $context, $filearea, $args, $forcedownload, array('preview' => $preview));
|
|
} else if (function_exists($filefunctionold)) {
|
|
// if the function exists, it must send the file and terminate. Whatever it returns leads to "not found"
|
|
$filefunctionold($course, $cm, $context, $filearea, $args, $forcedownload, array('preview' => $preview));
|
|
}
|
|
|
|
send_file_not_found();
|
|
|
|
// ========================================================================================================================
|
|
} else if (strpos($component, 'block_') === 0) {
|
|
$blockname = substr($component, 6);
|
|
// note: no more class methods in blocks please, that is ....
|
|
if (!file_exists("$CFG->dirroot/blocks/$blockname/lib.php")) {
|
|
send_file_not_found();
|
|
}
|
|
require_once("$CFG->dirroot/blocks/$blockname/lib.php");
|
|
|
|
if ($context->contextlevel == CONTEXT_BLOCK) {
|
|
$birecord = $DB->get_record('block_instances', array('id'=>$context->instanceid), '*',MUST_EXIST);
|
|
if ($birecord->blockname !== $blockname) {
|
|
// somebody tries to gain illegal access, cm type must match the component!
|
|
send_file_not_found();
|
|
}
|
|
} else {
|
|
$birecord = null;
|
|
}
|
|
|
|
$filefunction = $component.'_pluginfile';
|
|
if (function_exists($filefunction)) {
|
|
// if the function exists, it must send the file and terminate. Whatever it returns leads to "not found"
|
|
$filefunction($course, $birecord, $context, $filearea, $args, $forcedownload, array('preview' => $preview));
|
|
}
|
|
|
|
send_file_not_found();
|
|
|
|
// ========================================================================================================================
|
|
} else if (strpos($component, '_') === false) {
|
|
// all core subsystems have to be specified above, no more guessing here!
|
|
send_file_not_found();
|
|
|
|
} else {
|
|
// try to serve general plugin file in arbitrary context
|
|
$dir = get_component_directory($component);
|
|
if (!file_exists("$dir/lib.php")) {
|
|
send_file_not_found();
|
|
}
|
|
include_once("$dir/lib.php");
|
|
|
|
$filefunction = $component.'_pluginfile';
|
|
if (function_exists($filefunction)) {
|
|
// if the function exists, it must send the file and terminate. Whatever it returns leads to "not found"
|
|
$filefunction($course, $cm, $context, $filearea, $args, $forcedownload, array('preview' => $preview));
|
|
}
|
|
|
|
send_file_not_found();
|
|
}
|
|
|
|
}
|