Skip to content

Instantly share code, notes, and snippets.

@noelboss
Last active June 21, 2018 15:15
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save noelboss/7815ff2fea96b41544672f5e1a15f1f5 to your computer and use it in GitHub Desktop.
Save noelboss/7815ff2fea96b41544672f5e1a15f1f5 to your computer and use it in GitHub Desktop.
Range Download – Usage: wire('modules')->get('ProcessPageView')->sendFile($page, $filename);
<?php
/**
* © noelboss.com
*
* This source file is subject to the license file that is bundled
* with this source code in the file LICENSE.
*
* File created/changed: 2018-06-21T16:24:02+02:00
*/
namespace ProcessWire;
/*
* This _init.php file is called automatically by ProcessWire before every page render.
*/
class RangeDownloadBoss extends WireData implements Module
{
/**
* getModuleInfo is a module required by all modules to tell ProcessWire about them.
*
* @return array
*/
public static function getModuleInfo()
{
return [
// The module'ss title, typically a little more descriptive than the class name
'title' => 'Range Download Boss',
// version number
'version' => '0.0.1',
// summary is brief description of what this module is
'summary' => 'Adds range downloads for videos etc.',
// Optional URL to more information about the module
'href' => '',
// singular=true: indicates that only one instance of the module is allowed.
// This is usually what you want for modules that attach hooks.
'singular' => true,
// autoload=true: indicates the module should be started with ProcessWire.
// This is necessary for any modules that attach runtime hooks, otherwise those
// hooks won't get attached unless some other code calls the module on it's own.
// Note that autoload modules are almost always also 'singular' (seen above).
'autoload' => true,
// Optional font-awesome icon name, minus the 'fa-' part
'icon' => 'download',
];
}
public function init()
{
wire()->addHookBefore('ProcessPageView::sendFile', $this, 'sendFileHook');
}
public function sendFileHook(HookEvent $event)
{
$page = $event->arguments[0];
$file;
if ($page->hasField('files')) {
$file = $page->files->get($event->arguments[1]);
} elseif ($page->hasField('image')) {
$file = $page->image->get($event->arguments[1]);
}
if (!$file instanceof Pagefile && !$file instanceof Pageimage) {
return;
}
$this->downloadRange($file);
}
/**
* Send a File as Range.
*
* @param HookEvent $event
* @param mixed $file
*/
private function downloadRange($file)
{
if (!is_file($file->filename)) {
throw new Wire404Exception('Page not found');
}
$fp = @fopen($file->filename, 'rb');
$size = filesize($file->filename); // File size
$length = $size; // Content length
$start = 0; // Start byte
$end = $size - 1; // End byte
if (isset(wire('config')->fileContentTypes[$file->ext])) {
$ctype = wire('config')->fileContentTypes[$file->ext];
} else {
$ctype = mime_content_type($file->filename);
}
header('Content-type: '.$ctype);
header("Accept-Ranges: 0-$length");
if (isset($_SERVER['HTTP_RANGE'])) {
$c_start = $start;
$c_end = $end;
list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
if (strpos($range, ',') !== false) {
header('HTTP/1.1 416 Requested Range Not Satisfiable');
header("Content-Range: bytes $start-$end/$size");
exit;
}
if ($range == '-') {
$c_start = $size - substr($range, 1);
} else {
$range = explode('-', $range);
$c_start = $range[0];
$c_end = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $size;
}
$c_end = ($c_end > $end) ? $end : $c_end;
if ($c_start > $c_end || $c_start > $size - 1 || $c_end >= $size) {
header('HTTP/1.1 416 Requested Range Not Satisfiable');
header("Content-Range: bytes $start-$end/$size");
exit;
}
$start = $c_start;
$end = $c_end;
$length = $end - $start + 1;
fseek($fp, $start);
header('HTTP/1.1 206 Partial Content');
}
header("Content-Range: bytes $start-$end/$size");
header('Content-Length: '.$length);
$buffer = 1024 * 8;
while (!feof($fp) && ($p = ftell($fp)) <= $end) {
if ($p + $buffer > $end) {
$buffer = $end - $p + 1;
}
set_time_limit(0);
echo fread($fp, $buffer);
flush();
}
fclose($fp);
exit();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment