/** * Return a JSON response containing the response provided. * * @param string $status Status of the response, typically 'success' or 'failure'. * @param string $responsetext On success, the email address of the user, * otherwise a reason for the failure. * @return void Outputs the JSON and terminates the script. */ function badges_send_response($status, $responsetext) { $out = new stdClass(); $out->status = $status; if ($status == 'success') { $out->email = $responsetext; } else { $out->reason = $responsetext; send_header_404(); } echo json_encode($out); exit; }
/** * Serves scorm content, introduction images and packages. Implements needed access control ;-) * * @package mod_scorm * @category files * @param stdClass $course course object * @param stdClass $cm course module object * @param stdClass $context context object * @param string $filearea file area * @param array $args extra arguments * @param bool $forcedownload whether or not force download * @param array $options additional options affecting the file serving * @return bool false if file not found, does not return if found - just send the file */ function scorm_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) { global $CFG; if ($context->contextlevel != CONTEXT_MODULE) { return false; } require_login($course, true, $cm); $lifetime = null; if ($filearea === 'content') { $revision = (int)array_shift($args); // Prevents caching problems - ignored here. $relativepath = implode('/', $args); $fullpath = "/$context->id/mod_scorm/content/0/$relativepath"; // TODO: add any other access restrictions here if needed! } else if ($filearea === 'package') { if (!has_capability('moodle/course:manageactivities', $context)) { return false; } $relativepath = implode('/', $args); $fullpath = "/$context->id/mod_scorm/package/0/$relativepath"; $lifetime = 0; // No caching here. } else if ($filearea === 'imsmanifest') { // This isn't a real filearea, it's a url parameter for this type of package. $revision = (int)array_shift($args); // Prevents caching problems - ignored here. $relativepath = implode('/', $args); // Get imsmanifest file. $fs = get_file_storage(); $files = $fs->get_area_files($context->id, 'mod_scorm', 'package', 0, '', false); $file = reset($files); // Check that the package file is an imsmanifest.xml file - if not then this method is not allowed. $packagefilename = $file->get_filename(); if (strtolower($packagefilename) !== 'imsmanifest.xml') { return false; } $file->send_relative_file($relativepath); } else { return false; } $fs = get_file_storage(); if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { if ($filearea === 'content') { // Return file not found straight away to improve performance. send_header_404(); die; } return false; } // Finally send the file. send_stored_file($file, $lifetime, 0, false, $options); }
/** * 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; // Guess the file's MIME type. $mimetype = get_mimetype_for_sending($filename); // close session - not needed anymore \core\session\manager::write_close(); if (!$pathisstring) { if (!file_exists($path)) { send_header_404(); print_error('filenotfound', 'error', $CFG->wwwroot . '/'); } // executed after normal finish or abort core_shutdown_manager::register_function('send_temp_file_finished', array($path)); } // if user is using IE, urlencode the filename so that multibyte file name will show up correctly on popup if (core_useragent::is_ie()) { $filename = urlencode($filename); } header('Content-Disposition: attachment; filename="' . $filename . '"'); if (is_https()) { // HTTPS sites - watch out for IE! KB812935 and KB316431. header('Cache-Control: private, max-age=10, no-transform'); 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, no-transform'); header('Expires: ' . gmdate('D, d M Y H:i:s', 0) . ' GMT'); header('Pragma: no-cache'); } // send the contents - we can not accelerate this because the file will be deleted asap if ($pathisstring) { readstring_accel($path, $mimetype, false); } else { readfile_accel($path, $mimetype, false); @unlink($path); } die; //no more chars to output }
/** * 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; if (check_browser_version('Firefox', '1.5')) { // only FF is known to correctly save to disk before opening... $mimetype = mimeinfo('type', $filename); } else { $mimetype = 'application/x-forcedownload'; } // 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); } header('Content-Disposition: attachment; filename="' . $filename . '"'); 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'); } // send the contents - we can not accelerate this because the file will be deleted asap if ($pathisstring) { readstring_accel($path, $mimetype, false); } else { readfile_accel($path, $mimetype, false); @unlink($path); } die; //no more chars to output }
/** * Serves scorm content, introduction images and packages. Implements needed access control ;-) * * @package mod_scorm * @category files * @param stdClass $course course object * @param stdClass $cm course module object * @param stdClass $context context object * @param string $filearea file area * @param array $args extra arguments * @param bool $forcedownload whether or not force download * @param array $options additional options affecting the file serving * @return bool false if file not found, does not return if found - just send the file */ function scorm_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options = array()) { global $CFG, $DB; if ($context->contextlevel != CONTEXT_MODULE) { return false; } require_login($course, true, $cm); $canmanageactivity = has_capability('moodle/course:manageactivities', $context); $lifetime = null; // Check SCORM availability. if (!$canmanageactivity) { require_once $CFG->dirroot . '/mod/scorm/locallib.php'; $scorm = $DB->get_record('scorm', array('id' => $cm->instance), 'id, timeopen, timeclose', MUST_EXIST); list($available, $warnings) = scorm_get_availability_status($scorm); if (!$available) { return false; } } if ($filearea === 'content') { $revision = (int) array_shift($args); // Prevents caching problems - ignored here. $relativepath = implode('/', $args); $fullpath = "/{$context->id}/mod_scorm/content/0/{$relativepath}"; // TODO: add any other access restrictions here if needed! } else { if ($filearea === 'package') { // Check if the global setting for disabling package downloads is enabled. $protectpackagedownloads = get_config('scorm', 'protectpackagedownloads'); if ($protectpackagedownloads and !$canmanageactivity) { return false; } $revision = (int) array_shift($args); // Prevents caching problems - ignored here. $relativepath = implode('/', $args); $fullpath = "/{$context->id}/mod_scorm/package/0/{$relativepath}"; $lifetime = 0; // No caching here. } else { if ($filearea === 'imsmanifest') { // This isn't a real filearea, it's a url parameter for this type of package. $revision = (int) array_shift($args); // Prevents caching problems - ignored here. $relativepath = implode('/', $args); // Get imsmanifest file. $fs = get_file_storage(); $files = $fs->get_area_files($context->id, 'mod_scorm', 'package', 0, '', false); $file = reset($files); // Check that the package file is an imsmanifest.xml file - if not then this method is not allowed. $packagefilename = $file->get_filename(); if (strtolower($packagefilename) !== 'imsmanifest.xml') { return false; } $file->send_relative_file($relativepath); } else { return false; } } } $fs = get_file_storage(); if (!($file = $fs->get_file_by_hash(sha1($fullpath))) or $file->is_directory()) { if ($filearea === 'content') { // Return file not found straight away to improve performance. send_header_404(); die; } return false; } // Finally send the file. send_stored_file($file, $lifetime, 0, false, $options); }
/** * Serves scorm content, introduction images and packages. Implements needed access control ;-) * * @package mod_scorm * @category files * @param stdClass $course course object * @param stdClass $cm course module object * @param stdClass $context context object * @param string $filearea file area * @param array $args extra arguments * @param bool $forcedownload whether or not force download * @param array $options additional options affecting the file serving * @return bool false if file not found, does not return if found - just send the file */ function scorm_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) { global $CFG; if ($context->contextlevel != CONTEXT_MODULE) { return false; } require_login($course, true, $cm); $lifetime = isset($CFG->filelifetime) ? $CFG->filelifetime : 86400; if ($filearea === 'content') { $revision = (int)array_shift($args); // prevents caching problems - ignored here $relativepath = implode('/', $args); $fullpath = "/$context->id/mod_scorm/content/0/$relativepath"; // TODO: add any other access restrictions here if needed! } else if ($filearea === 'package') { if (!has_capability('moodle/course:manageactivities', $context)) { return false; } $relativepath = implode('/', $args); $fullpath = "/$context->id/mod_scorm/package/0/$relativepath"; $lifetime = 0; // no caching here } else { return false; } $fs = get_file_storage(); if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { if ($filearea === 'content') { //return file not found straight away to improve performance. send_header_404(); die; } return false; } // finally send the file send_stored_file($file, $lifetime, 0, false, $options); }
/** * 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 * @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; 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; } } // handle external resource if ($stored_file->is_external_file()) { $stored_file->send_file($lifetime, $filter, $forcedownload, $options); die; } 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)); // 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: '); } 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'); } } if (empty($filter)) { // send the contents readfile_accel($stored_file, $mimetype, !$dontdie); } 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); readstring_accel($output, $mimetype, false); } 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>'; readstring_accel($output, $mimetype, false); } else { // Just send it out raw readfile_accel($stored_file, $mimetype, !$dontdie); } } } 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!!! }
/** * Serves Tin Can content, introduction images and packages. Implements needed access control ;-) * * @package mod_tincanlaunch * @category files * @param stdClass $course course object * @param stdClass $cm course module object * @param stdClass $context context object * @param string $filearea file area * @param array $args extra arguments * @param bool $forcedownload whether or not force download * @param array $options additional options affecting the file serving * @return bool false if file not found, does not return if found - just send the file */ function tincanlaunch_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options = array()) { global $CFG, $DB; if ($context->contextlevel != CONTEXT_MODULE) { return false; } require_login($course, true, $cm); $canmanageactivity = has_capability('moodle/course:manageactivities', $context); if ($filearea === 'content') { $filename = array_pop($args); $filepath = implode('/', $args); $lifetime = null; } elseif ($filearea === 'package') { $relativepath = implode('/', $args); $fullpath = "/{$context->id}/tincanlaunch/package/0/{$relativepath}"; $lifetime = 0; // No caching here. } else { return false; } $fs = get_file_storage(); if (!($file = $fs->get_file($context->id, 'mod_tincanlaunch', 'content', 0, '/' . $filepath . '/', $filename)) or $file->is_directory()) { if ($filearea === 'content') { // Return file not found straight away to improve performance. send_header_404(); die; } return false; } // Finally send the file. send_stored_file($file, $lifetime, 0, false, $options); }
/** * 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 }