mirror of
https://github.com/getgrav/grav.git
synced 2026-05-07 21:46:00 +02:00
Merge branch develop into feature/password_improvement
This commit is contained in:
15
CHANGELOG.md
15
CHANGELOG.md
@@ -1,3 +1,18 @@
|
||||
# v0.9.24
|
||||
## 04/15/2015
|
||||
|
||||
1. [](#new)
|
||||
* Added support for chunked downloads of Assets
|
||||
* Added new `onBeforeDownload()` event
|
||||
* Added new `download()` and `getMimeType()` methods to Utils class
|
||||
* Added configuration option for supported page types
|
||||
* Added assets and media timestamp options (off by default)
|
||||
* Added page expires configuration option
|
||||
2. [](#bugfix)
|
||||
* Fixed issue with Nginx/Gzip and `ob_flush()` throwing error
|
||||
* Fixed assets actions on 'direct media' URLs
|
||||
* Fix for 'direct assets` with any parameters
|
||||
|
||||
# v0.9.23
|
||||
## 04/09/2015
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"maximebf/debugbar": "dev-master",
|
||||
"filp/whoops": "1.2.*@dev",
|
||||
"monolog/monolog": "~1.0",
|
||||
"gregwar/image": "~2.0",
|
||||
"gregwar/image": "~2.0",
|
||||
"ircmaxell/password-compat": "1.0.*",
|
||||
"mrclay/minify": "dev-master",
|
||||
"donatj/phpuseragentparser": "dev-master",
|
||||
|
||||
@@ -30,6 +30,8 @@ pages:
|
||||
special_chars: # List of special characters to automatically convert to entities
|
||||
'>': 'gt'
|
||||
'<': 'lt'
|
||||
types: 'txt|xml|html|json|rss|atom' # Pipe separated list of valid page types
|
||||
expires: 604800 # Page expires time in seconds (default 7 days)
|
||||
|
||||
cache:
|
||||
enabled: true # Set to true to enable caching
|
||||
@@ -40,6 +42,7 @@ cache:
|
||||
lifetime: 604800 # Lifetime of cached data in seconds (0 = infinite)
|
||||
gzip: false # GZip compress the page output
|
||||
|
||||
|
||||
twig:
|
||||
cache: true # Set to true to enable twig caching
|
||||
debug: false # Enable Twig debug
|
||||
@@ -55,6 +58,7 @@ assets: # Configuration for Assets Manager (JS, C
|
||||
css_rewrite: true # Rewrite any CSS relative URLs during pipelining
|
||||
js_pipeline: false # The JS pipeline is the unification of multiple JS resources into one file
|
||||
js_minify: true # Minify the JS during pipelining
|
||||
enable_asset_timestamp: false # Enable asset timetsamps
|
||||
collections:
|
||||
jquery: system://assets/jquery/jquery-2.1.3.min.js
|
||||
|
||||
@@ -72,5 +76,8 @@ images:
|
||||
default_image_quality: 85 # Default image quality to use when resampling images (85%)
|
||||
debug: false # Show an overlay over images indicating the pixel depth of the image when working with retina for example
|
||||
|
||||
media:
|
||||
enable_media_timestamp: false # Enable media timetsamps
|
||||
|
||||
security:
|
||||
default_hash: $2y$10$kwsyMVwM8/7j0K/6LHT.g.Fs49xOCTp2b8hh/S5.dPJuJcJB6T.UK
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
// Some standard defines
|
||||
define('GRAV', true);
|
||||
define('GRAV_VERSION', '0.9.23');
|
||||
define('GRAV_VERSION', '0.9.24');
|
||||
define('DS', '/');
|
||||
|
||||
// Directories and Paths
|
||||
|
||||
@@ -71,6 +71,7 @@ class Assets
|
||||
// Some configuration variables
|
||||
protected $config;
|
||||
protected $base_url;
|
||||
protected $timestamp = '';
|
||||
|
||||
// Default values for pipeline settings
|
||||
protected $css_minify = true;
|
||||
@@ -82,7 +83,6 @@ class Assets
|
||||
protected $css_no_pipeline = array();
|
||||
protected $js_no_pipeline = array();
|
||||
|
||||
|
||||
public function __construct(array $options = array())
|
||||
{
|
||||
// Forward config options
|
||||
@@ -154,6 +154,12 @@ class Assets
|
||||
}
|
||||
}
|
||||
|
||||
// Set timestamp
|
||||
if (isset($config['enable_asset_timestamp']) && $config['enable_asset_timestamp'] === true) {
|
||||
$this->timestamp = '?' . self::getGrav()['cache']->getKey();
|
||||
}
|
||||
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -422,11 +428,11 @@ class Assets
|
||||
$output .= '<link href="' . $this->pipeline(CSS_ASSET) . '"' . $attributes . ' />' . "\n";
|
||||
|
||||
foreach ($this->css_no_pipeline as $file) {
|
||||
$output .= '<link href="' . $file['asset'] . '"' . $attributes . ' />' . "\n";
|
||||
$output .= '<link href="' . $file['asset'] . $this->timestamp . '"' . $attributes . ' />' . "\n";
|
||||
}
|
||||
} else {
|
||||
foreach ($this->css as $file) {
|
||||
$output .= '<link href="' . $file['asset'] . '"' . $attributes . ' />' . "\n";
|
||||
$output .= '<link href="' . $file['asset'] . $this->timestamp . '"' . $attributes . ' />' . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -480,11 +486,11 @@ class Assets
|
||||
if ($this->js_pipeline) {
|
||||
$output .= '<script src="' . $this->pipeline(JS_ASSET) . '"' . $attributes . ' ></script>' . "\n";
|
||||
foreach ($this->js_no_pipeline as $file) {
|
||||
$output .= '<script src="' . $file['asset'] . '"' . $attributes . ' ' . $file['loading']. '></script>' . "\n";
|
||||
$output .= '<script src="' . $file['asset'] . $this->timestamp . '"' . $attributes . ' ' . $file['loading']. '></script>' . "\n";
|
||||
}
|
||||
} else {
|
||||
foreach ($this->js as $file) {
|
||||
$output .= '<script src="' . $file['asset'] . '"' . $attributes . ' ' . $file['loading'].'></script>' . "\n";
|
||||
$output .= '<script src="' . $file['asset'] . $this->timestamp . '"' . $attributes . ' ' . $file['loading'].'></script>' . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
namespace Grav\Common;
|
||||
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\Page\Medium\ImageMedium;
|
||||
use Grav\Common\Page\Pages;
|
||||
use Grav\Common\Service\ConfigServiceProvider;
|
||||
use Grav\Common\Service\ErrorServiceProvider;
|
||||
@@ -10,7 +11,6 @@ use Grav\Common\Service\StreamsServiceProvider;
|
||||
use RocketTheme\Toolbox\DI\Container;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use RocketTheme\Toolbox\Event\EventDispatcher;
|
||||
use Grav\Common\Page\Medium\Medium;
|
||||
|
||||
/**
|
||||
* Grav
|
||||
@@ -99,32 +99,34 @@ class Grav extends Container
|
||||
/** @var Pages $pages */
|
||||
$pages = $c['pages'];
|
||||
|
||||
// If base URI is set, we want to remove it from the URL.
|
||||
$path = '/' . ltrim(Folder::getRelativePath($c['uri']->route(), $pages->base()), '/');
|
||||
/** @var Uri $uri */
|
||||
$uri = $c['uri'];
|
||||
|
||||
$path = $uri->path();
|
||||
|
||||
$page = $pages->dispatch($path);
|
||||
|
||||
if (!$page || !$page->routable()) {
|
||||
|
||||
// special case where a media file is requested
|
||||
$path_parts = pathinfo($path);
|
||||
|
||||
$page = $c['pages']->dispatch($path_parts['dirname'], true);
|
||||
if ($page) {
|
||||
$media = $page->media()->all();
|
||||
$media_file = urldecode($path_parts['basename']);
|
||||
|
||||
$parsed_url = parse_url(urldecode($uri->basename()));
|
||||
|
||||
$media_file = $parsed_url['path'];
|
||||
|
||||
// if this is a media object, try actions first
|
||||
if (isset($media[$media_file])) {
|
||||
$medium = $media[$media_file];
|
||||
|
||||
// loop through actions for the image and call them
|
||||
foreach ($c['uri']->query(null, true) as $action => $params) {
|
||||
if (in_array($action, Medium::$valid_actions)) {
|
||||
foreach ($uri->query(null, true) as $action => $params) {
|
||||
if (in_array($action, ImageMedium::$magic_actions)) {
|
||||
call_user_func_array(array(&$medium, $action), explode(',', $params));
|
||||
}
|
||||
}
|
||||
header('Content-type: '. $medium->get('mime'));
|
||||
echo file_get_contents($medium->path());
|
||||
die;
|
||||
Utils::download($medium->path(), false);
|
||||
} else {
|
||||
Utils::download($page->path() . DIRECTORY_SEPARATOR . $uri->basename(), true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,6 +298,8 @@ class Grav extends Container
|
||||
$extension = $this['uri']->extension();
|
||||
header('Content-type: ' . $this->mime($extension));
|
||||
|
||||
header('Expires: '.gmdate('D, d M Y H:i:s \G\M\T', time() + $this['config']->get('system.pages.expires')));
|
||||
|
||||
// Set debugger data in headers
|
||||
if (!($extension === null || $extension == 'html')) {
|
||||
$this['debugger']->enabled(false);
|
||||
@@ -345,7 +349,7 @@ class Grav extends Container
|
||||
header("Connection: close\r\n");
|
||||
|
||||
ob_end_flush(); // regular buffer
|
||||
ob_flush();
|
||||
@ob_flush();
|
||||
flush();
|
||||
|
||||
if (function_exists('fastcgi_finish_request')) {
|
||||
|
||||
@@ -42,7 +42,7 @@ class ImageMedium extends Medium
|
||||
public static $magic_actions = [
|
||||
'resize', 'forceResize', 'cropResize', 'crop', 'zoomCrop',
|
||||
'negate', 'brightness', 'contrast', 'grayscale', 'emboss',
|
||||
'smooth', 'sharp', 'edge', 'colorize', 'sepia'
|
||||
'smooth', 'sharp', 'edge', 'colorize', 'sepia', 'enableProgressive'
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -127,7 +127,7 @@ class ImageMedium extends Medium
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
return self::$grav['base_url'] . $output . $this->urlHash();
|
||||
return self::$grav['base_url'] . $output . $this->querystring() . $this->urlHash();
|
||||
}
|
||||
|
||||
|
||||
@@ -299,7 +299,7 @@ class ImageMedium extends Medium
|
||||
}
|
||||
|
||||
if (!in_array($method, self::$magic_actions)) {
|
||||
return $this;
|
||||
return parent::__call($method, $args);
|
||||
}
|
||||
|
||||
// Always initialize image.
|
||||
|
||||
@@ -60,6 +60,10 @@ class Medium extends Data implements RenderableInterface
|
||||
{
|
||||
parent::__construct($items, $blueprint);
|
||||
|
||||
if (self::getGrav()['config']->get('media.enable_media_timestamp', true)) {
|
||||
$this->querystring('&' . self::getGrav()['cache']->getKey());
|
||||
}
|
||||
|
||||
$this->def('mime', 'application/octet-stream');
|
||||
$this->reset();
|
||||
}
|
||||
@@ -129,7 +133,33 @@ class Medium extends Data implements RenderableInterface
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
return self::$grav['base_url'] . $output . $this->urlHash();
|
||||
return self::$grav['base_url'] . $output . $this->querystring() . $this->urlHash();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set querystring for the file's url
|
||||
*
|
||||
* @param string $hash
|
||||
* @param boolean $withHash
|
||||
* @return string
|
||||
*/
|
||||
public function querystring($querystring = null, $withQuestionmark = true)
|
||||
{
|
||||
if ($querystring) {
|
||||
$this->set('querystring', ltrim($querystring, '?&'));
|
||||
|
||||
foreach ($this->alternatives as $alt) {
|
||||
$alt->querystring($querystring, $withQuestionmark);
|
||||
}
|
||||
}
|
||||
|
||||
$querystring = $this->get('querystring', '');
|
||||
|
||||
if ($withQuestionmark && !empty($querystring)) {
|
||||
return '?' . $querystring;
|
||||
} else {
|
||||
return $querystring;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -337,6 +367,17 @@ class Medium extends Data implements RenderableInterface
|
||||
*/
|
||||
public function __call($method, $args)
|
||||
{
|
||||
$qs = $method;
|
||||
if (count($args) > 1 || (count($args) == 1 && !empty($args[0]))) {
|
||||
$qs .= '=' . implode(',', array_map(function ($a) { return urlencode($a); }, $args));
|
||||
}
|
||||
|
||||
if (!empty($qs)) {
|
||||
$this->querystring($this->querystring(null, false) . '&' . $qs);
|
||||
}
|
||||
|
||||
self::$grav['debugger']->addMessage($this->querystring());
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
@@ -131,8 +131,7 @@ class MediumFactory
|
||||
$debug = $medium->get('debug');
|
||||
$medium->set('debug', false);
|
||||
|
||||
$file = $medium->resize($width, $height)->setPrettyName($basename)->url();
|
||||
$file = preg_replace('|'. preg_quote(self::getGrav()['base_url_relative']) .'$|', '', GRAV_ROOT) . $file;
|
||||
$file = $medium->resize($width, $height)->path();
|
||||
|
||||
$medium->set('debug', $debug);
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ class Uri
|
||||
{
|
||||
public $url;
|
||||
|
||||
protected $basename;
|
||||
protected $base;
|
||||
protected $root;
|
||||
protected $bits;
|
||||
@@ -64,6 +65,7 @@ class Uri
|
||||
$this->base = $base;
|
||||
$this->root = $base . $root_path;
|
||||
$this->url = $base . $uri;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,7 +86,11 @@ class Uri
|
||||
|
||||
// remove the extension if there is one set
|
||||
$parts = pathinfo($uri);
|
||||
if (preg_match("/\.(txt|xml|html|json|rss|atom)$/", $parts['basename'])) {
|
||||
|
||||
// set the original basename
|
||||
$this->basename = $parts['basename'];
|
||||
|
||||
if (preg_match("/\.(".$config->get('system.pages.types').")$/", $parts['basename'])) {
|
||||
$uri = rtrim($parts['dirname'], '/').'/'.$parts['filename'];
|
||||
$this->extension = $parts['extension'];
|
||||
}
|
||||
@@ -282,6 +288,17 @@ class Uri
|
||||
return $this->host();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the basename of the URI
|
||||
*
|
||||
* @return String The basename of the URI
|
||||
*/
|
||||
public function basename()
|
||||
{
|
||||
return $this->basename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the base of the URI
|
||||
*
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<?php
|
||||
namespace Grav\Common;
|
||||
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
|
||||
/**
|
||||
* Misc utilities.
|
||||
*
|
||||
@@ -8,6 +10,8 @@ namespace Grav\Common;
|
||||
*/
|
||||
abstract class Utils
|
||||
{
|
||||
use GravTrait;
|
||||
|
||||
/**
|
||||
* @param string $haystack
|
||||
* @param string $needle
|
||||
@@ -231,4 +235,165 @@ abstract class Utils
|
||||
{
|
||||
return substr(str_shuffle("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), 0, $length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the ability to download a file to the browser
|
||||
*
|
||||
* @param $file the full path to the file to be downloaded
|
||||
* @param bool $force_download as opposed to letting browser choose if to download or render
|
||||
*/
|
||||
public static function download($file, $force_download = true)
|
||||
{
|
||||
if (file_exists($file)) {
|
||||
// fire download event
|
||||
self::getGrav()->fireEvent('onBeforeDownload', new Event(['file' => $file]));
|
||||
|
||||
$file_parts = pathinfo($file);
|
||||
$filesize = filesize($file);
|
||||
$range = false;
|
||||
|
||||
set_time_limit(0);
|
||||
ignore_user_abort(false);
|
||||
ini_set('output_buffering', 0);
|
||||
ini_set('zlib.output_compression', 0);
|
||||
|
||||
if ($force_download) {
|
||||
header('Content-Description: File Transfer');
|
||||
header('Content-Type: application/octet-stream');
|
||||
header('Content-Disposition: attachment; filename='.$file_parts['basename']);
|
||||
header('Content-Transfer-Encoding: binary');
|
||||
header('Expires: 0');
|
||||
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
|
||||
header('Pragma: public');
|
||||
} else {
|
||||
header("Content-Type: " . Utils::getMimeType($file_parts['extension']));
|
||||
}
|
||||
header('Content-Length: ' . $filesize);
|
||||
|
||||
// 8kb chunks for now
|
||||
$chunk = 8 * 1024;
|
||||
|
||||
$fh = fopen($file, "rb");
|
||||
|
||||
if ($fh === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Repeat reading until EOF
|
||||
while (!feof($fh)) {
|
||||
echo fread($fh, $chunk);
|
||||
|
||||
ob_flush(); // flush output
|
||||
flush();
|
||||
}
|
||||
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mimetype based on filename
|
||||
*
|
||||
* @param $extension Extension of file (eg .txt)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getMimeType($extension)
|
||||
{
|
||||
$extension = strtolower($extension);
|
||||
|
||||
switch($extension)
|
||||
{
|
||||
case "js":
|
||||
return "application/x-javascript";
|
||||
|
||||
case "json":
|
||||
return "application/json";
|
||||
|
||||
case "jpg":
|
||||
case "jpeg":
|
||||
case "jpe":
|
||||
return "image/jpg";
|
||||
|
||||
case "png":
|
||||
case "gif":
|
||||
case "bmp":
|
||||
case "tiff":
|
||||
return "image/" . $extension;
|
||||
|
||||
case "css":
|
||||
return "text/css";
|
||||
|
||||
case "xml":
|
||||
return "application/xml";
|
||||
|
||||
case "doc":
|
||||
case "docx":
|
||||
return "application/msword";
|
||||
|
||||
case "xls":
|
||||
case "xlt":
|
||||
case "xlm":
|
||||
case "xld":
|
||||
case "xla":
|
||||
case "xlc":
|
||||
case "xlw":
|
||||
case "xll":
|
||||
return "application/vnd.ms-excel";
|
||||
|
||||
case "ppt":
|
||||
case "pps":
|
||||
return "application/vnd.ms-powerpoint";
|
||||
|
||||
case "rtf":
|
||||
return "application/rtf";
|
||||
|
||||
case "pdf":
|
||||
return "application/pdf";
|
||||
|
||||
case "html":
|
||||
case "htm":
|
||||
case "php":
|
||||
return "text/html";
|
||||
|
||||
case "txt":
|
||||
return "text/plain";
|
||||
|
||||
case "mpeg":
|
||||
case "mpg":
|
||||
case "mpe":
|
||||
return "video/mpeg";
|
||||
|
||||
case "mp3":
|
||||
return "audio/mpeg3";
|
||||
|
||||
case "wav":
|
||||
return "audio/wav";
|
||||
|
||||
case "aiff":
|
||||
case "aif":
|
||||
return "audio/aiff";
|
||||
|
||||
case "avi":
|
||||
return "video/msvideo";
|
||||
|
||||
case "wmv":
|
||||
return "video/x-ms-wmv";
|
||||
|
||||
case "mov":
|
||||
return "video/quicktime";
|
||||
|
||||
case "zip":
|
||||
return "application/zip";
|
||||
|
||||
case "tar":
|
||||
return "application/x-tar";
|
||||
|
||||
case "swf":
|
||||
return "application/x-shockwave-flash";
|
||||
|
||||
default:
|
||||
return "application/octet-stream";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user