This commit is contained in:
2019-09-27 00:29:18 +02:00
parent 40af43681e
commit b857814072
470 changed files with 37562 additions and 10060 deletions
File diff suppressed because it is too large Load Diff
+198
View File
@@ -0,0 +1,198 @@
<?php
/**
* @package Grav\Common\Assets
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Assets;
use Grav\Common\Assets\Traits\AssetUtilsTrait;
use Grav\Common\Grav;
use Grav\Common\Uri;
use Grav\Common\Utils;
use Grav\Framework\Object\PropertyObject;
abstract class BaseAsset extends PropertyObject
{
use AssetUtilsTrait;
protected const CSS_ASSET = true;
protected const JS_ASSET = false;
/** @const Regex to match CSS import content */
protected const CSS_IMPORT_REGEX = '{@import(.*?);}';
protected $asset;
protected $asset_type;
protected $order;
protected $group;
protected $position;
protected $priority;
protected $attributes = [];
protected $timestamp;
protected $modified;
protected $remote;
protected $query = '';
// Private Bits
private $base_url;
private $fetch_command;
private $css_rewrite = false;
private $css_minify = false;
abstract function render();
public function __construct(array $elements = [], $key = null)
{
$base_config = [
'group' => 'head',
'position' => 'pipeline',
'priority' => 10,
'modified' => null,
'asset' => null
];
// Merge base defaults
$elements = array_merge($base_config, $elements);
parent::__construct($elements, $key);
}
public function init($asset, $options)
{
$config = Grav::instance()['config'];
$uri = Grav::instance()['uri'];
// set attributes
foreach ($options as $key => $value) {
if ($this->hasProperty($key)) {
$this->setProperty($key, $value);
} else {
$this->attributes[$key] = $value;
}
}
// Force priority to be an int
$this->priority = (int) $this->priority;
// Do some special stuff for CSS/JS (not inline)
if (!Utils::startsWith($this->getType(), 'inline')) {
$this->base_url = rtrim($uri->rootUrl($config->get('system.absolute_urls')), '/') . '/';
$this->remote = static::isRemoteLink($asset);
// Move this to render?
if (!$this->remote) {
$asset_parts = parse_url($asset);
if (isset($asset_parts['query'])) {
$this->query = $asset_parts['query'];
unset($asset_parts['query']);
$asset = Uri::buildUrl($asset_parts);
}
$locator = Grav::instance()['locator'];
if ($locator->isStream($asset)) {
$path = $locator->findResource($asset, true);
} else {
$path = GRAV_ROOT . $asset;
}
// If local file is missing return
if ($path === false) {
return false;
}
$file = new \SplFileInfo($path);
$asset = $this->buildLocalLink($file->getPathname());
$this->modified = $file->isFile() ? $file->getMTime() : false;
}
}
$this->asset = $asset;
return $this;
}
public function getAsset()
{
return $this->asset;
}
public function getRemote()
{
return $this->remote;
}
public function setPosition($position)
{
$this->position = $position;
return $this;
}
/**
*
* Get the last modification time of asset
*
* @param string $asset the asset string reference
*
* @return string the last modifcation time or false on error
*/
// protected function getLastModificationTime($asset)
// {
// $file = GRAV_ROOT . $asset;
// if (Grav::instance()['locator']->isStream($asset)) {
// $file = $this->buildLocalLink($asset, true);
// }
//
// return file_exists($file) ? filemtime($file) : false;
// }
/**
*
* Build local links including grav asset shortcodes
*
* @param string $asset the asset string reference
*
* @return string the final link url to the asset
*/
protected function buildLocalLink($asset)
{
if ($asset) {
return $this->base_url . ltrim(Utils::replaceFirstOccurrence(GRAV_ROOT, '', $asset), '/');
}
return false;
}
/**
* Implements JsonSerializable interface.
*
* @return array
*/
public function jsonSerialize()
{
return ['type' => $this->getType(), 'elements' => $this->getElements()];
}
/**
* Placeholder for AssetUtilsTrait method
*
* @param string $file
* @param string $dir
* @param bool $local
*/
protected function cssRewrite($file, $dir, $local)
{
return;
}
}
+40
View File
@@ -0,0 +1,40 @@
<?php
/**
* @package Grav\Common\Assets
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Assets;
use Grav\Common\Utils;
class Css extends BaseAsset
{
public function __construct(array $elements = [], $key = null)
{
$base_options = [
'asset_type' => 'css',
'attributes' => [
'type' => 'text/css',
'rel' => 'stylesheet'
]
];
$merged_attributes = Utils::arrayMergeRecursiveUnique($base_options, $elements);
parent::__construct($merged_attributes, $key);
}
public function render()
{
if (isset($this->attributes['loading']) && $this->attributes['loading'] === 'inline') {
$buffer = $this->gatherLinks( [$this], self::CSS_ASSET);
return "<style>\n" . trim($buffer) . "\n</style>\n";
}
return '<link href="' . trim($this->asset) . $this->renderQueryString() . '"' . $this->renderAttributes() . ">\n";
}
}
@@ -0,0 +1,32 @@
<?php
/**
* @package Grav\Common\Assets
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Assets;
use Grav\Common\Utils;
class InlineCss extends BaseAsset
{
public function __construct(array $elements = [], $key = null)
{
$base_options = [
'asset_type' => 'css',
'position' => 'after'
];
$merged_attributes = Utils::arrayMergeRecursiveUnique($base_options, $elements);
parent::__construct($merged_attributes, $key);
}
public function render()
{
return '<style' . $this->renderAttributes(). ">\n" . trim($this->asset) . "\n</style>\n";
}
}
@@ -0,0 +1,32 @@
<?php
/**
* @package Grav\Common\Assets
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Assets;
use Grav\Common\Utils;
class InlineJs extends BaseAsset
{
public function __construct(array $elements = [], $key = null)
{
$base_options = [
'asset_type' => 'js',
'position' => 'after'
];
$merged_attributes = Utils::arrayMergeRecursiveUnique($base_options, $elements);
parent::__construct($merged_attributes, $key);
}
public function render()
{
return '<script' . $this->renderAttributes(). ">\n" . trim($this->asset) . "\n</script>\n";
}
}
+36
View File
@@ -0,0 +1,36 @@
<?php
/**
* @package Grav\Common\Assets
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Assets;
use Grav\Common\Utils;
class Js extends BaseAsset
{
public function __construct(array $elements = [], $key = null)
{
$base_options = [
'asset_type' => 'js',
];
$merged_attributes = Utils::arrayMergeRecursiveUnique($base_options, $elements);
parent::__construct($merged_attributes, $key);
}
public function render()
{
if (isset($this->attributes['loading']) && $this->attributes['loading'] === 'inline') {
$buffer = $this->gatherLinks( [$this], self::JS_ASSET);
return '<script' . $this->renderAttributes() . ">\n" . trim($buffer) . "\n</script>\n";
}
return '<script src="' . trim($this->asset) . $this->renderQueryString() . '"' . $this->renderAttributes() . "></script>\n";
}
}
+273
View File
@@ -0,0 +1,273 @@
<?php
/**
* @package Grav\Common\Assets
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Assets;
use Grav\Common\Assets\Traits\AssetUtilsTrait;
use Grav\Common\Config\Config;
use Grav\Common\Grav;
use Grav\Common\Uri;
use Grav\Common\Utils;
use Grav\Framework\Object\PropertyObject;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
class Pipeline extends PropertyObject
{
use AssetUtilsTrait;
protected const CSS_ASSET = true;
protected const JS_ASSET = false;
/** @const Regex to match CSS urls */
protected const CSS_URL_REGEX = '{url\(([\'\"]?)(.*?)\1\)}';
/** @const Regex to match CSS sourcemap comments */
protected const CSS_SOURCEMAP_REGEX = '{\/\*# (.*?) \*\/}';
/** @const Regex to match CSS import content */
protected const CSS_IMPORT_REGEX = '{@import(.*?);}';
protected const FIRST_FORWARDSLASH_REGEX = '{^\/{1}\w}';
protected $css_minify;
protected $css_minify_windows;
protected $css_rewrite;
protected $js_minify;
protected $js_minify_windows;
protected $base_url;
protected $assets_dir;
protected $assets_url;
protected $timestamp;
protected $attributes;
protected $query;
protected $asset;
/**
* Closure used by the pipeline to fetch assets.
*
* Useful when file_get_contents() function is not available in your PHP
* installation or when you want to apply any kind of preprocessing to
* your assets before they get pipelined.
*
* The closure will receive as the only parameter a string with the path/URL of the asset and
* it should return the content of the asset file as a string.
*
* @var \Closure
*/
protected $fetch_command;
public function __construct(array $elements = [], ?string $key = null)
{
parent::__construct($elements, $key);
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
/** @var Config $config */
$config = Grav::instance()['config'];
/** @var Uri $uri */
$uri = Grav::instance()['uri'];
$this->base_url = rtrim($uri->rootUrl($config->get('system.absolute_urls')), '/') . '/';
$this->assets_dir = $locator->findResource('asset://') . DS;
$this->assets_url = $locator->findResource('asset://', false);
}
/**
* Minify and concatenate CSS
*
* @param array $assets
* @param string $group
* @param array $attributes
*
* @return bool|string URL or generated content if available, else false
*/
public function renderCss($assets, $group, $attributes = [])
{
// temporary list of assets to pipeline
$inline_group = false;
if (array_key_exists('loading', $attributes) && $attributes['loading'] === 'inline') {
$inline_group = true;
unset($attributes['loading']);
}
// Store Attributes
$this->attributes = array_merge(['type' => 'text/css', 'rel' => 'stylesheet'], $attributes);
// Compute uid based on assets and timestamp
$json_assets = json_encode($assets);
$uid = md5($json_assets . $this->css_minify . $this->css_rewrite . $group);
$file = $uid . '.css';
$relative_path = "{$this->base_url}{$this->assets_url}/{$file}";
$buffer = null;
if (file_exists($this->assets_dir . $file)) {
$buffer = file_get_contents($this->assets_dir . $file) . "\n";
} else {
//if nothing found get out of here!
if (empty($assets)) {
return false;
}
// Concatenate files
$buffer = $this->gatherLinks($assets, self::CSS_ASSET);
// Minify if required
if ($this->shouldMinify('css')) {
$minifier = new \MatthiasMullie\Minify\CSS();
$minifier->add($buffer);
$buffer = $minifier->minify();
}
// Write file
if (trim($buffer) !== '') {
file_put_contents($this->assets_dir . $file, $buffer);
}
}
if ($inline_group) {
$output = "<style>\n" . $buffer . "\n</style>\n";
} else {
$this->asset = $relative_path;
$output = '<link href="' . $relative_path . $this->renderQueryString() . '"' . $this->renderAttributes() . ">\n";
}
return $output;
}
/**
* Minify and concatenate JS files.
*
* @param array $assets
* @param string $group
* @param array $attributes
*
* @return bool|string URL or generated content if available, else false
*/
public function renderJs($assets, $group, $attributes = [])
{
// temporary list of assets to pipeline
$inline_group = false;
if (array_key_exists('loading', $attributes) && $attributes['loading'] === 'inline') {
$inline_group = true;
unset($attributes['loading']);
}
// Store Attributes
$this->attributes = $attributes;
// Compute uid based on assets and timestamp
$json_assets = json_encode($assets);
$uid = md5($json_assets . $this->js_minify . $group);
$file = $uid . '.js';
$relative_path = "{$this->base_url}{$this->assets_url}/{$file}";
$buffer = null;
if (file_exists($this->assets_dir . $file)) {
$buffer = file_get_contents($this->assets_dir . $file) . "\n";
} else {
//if nothing found get out of here!
if (empty($assets)) {
return false;
}
// Concatenate files
$buffer = $this->gatherLinks($assets, self::JS_ASSET);
// Minify if required
if ($this->shouldMinify('js')) {
$minifier = new \MatthiasMullie\Minify\JS();
$minifier->add($buffer);
$buffer = $minifier->minify();
}
// Write file
if (trim($buffer) !== '') {
file_put_contents($this->assets_dir . $file, $buffer);
}
}
if ($inline_group) {
$output = '<script' . $this->renderAttributes(). ">\n" . $buffer . "\n</script>\n";
} else {
$this->asset = $relative_path;
$output = '<script src="' . $relative_path . $this->renderQueryString() . '"' . $this->renderAttributes() . "></script>\n";
}
return $output;
}
/**
* Finds relative CSS urls() and rewrites the URL with an absolute one
*
* @param string $file the css source file
* @param string $dir , $local relative path to the css file
* @param bool $local is this a local or remote asset
*
* @return mixed
*/
protected function cssRewrite($file, $dir, $local)
{
// Strip any sourcemap comments
$file = preg_replace(self::CSS_SOURCEMAP_REGEX, '', $file);
// Find any css url() elements, grab the URLs and calculate an absolute path
// Then replace the old url with the new one
$file = (string)preg_replace_callback(self::CSS_URL_REGEX, function ($matches) use ($dir, $local) {
$old_url = $matches[2];
// Ensure link is not rooted to web server, a data URL, or to a remote host
if (preg_match(self::FIRST_FORWARDSLASH_REGEX, $old_url) || Utils::startsWith($old_url, 'data:') || $this->isRemoteLink($old_url)) {
return $matches[0];
}
// clean leading /
$old_url = Utils::normalizePath($dir . '/' . $old_url);
if (preg_match(self::FIRST_FORWARDSLASH_REGEX, $old_url)) {
$old_url = ltrim($old_url, '/');
}
$new_url = ($local ? $this->base_url: '') . $old_url;
$fixed = str_replace($matches[2], $new_url, $matches[0]);
return $fixed;
}, $file);
return $file;
}
private function shouldMinify($type = 'css')
{
$check = $type . '_minify';
$win_check = $type . '_minify_windows';
$minify = (bool) $this->$check;
// If this is a Windows server, and minify_windows is false (default value) skip the
// minification process because it will cause Apache to die/crash due to insufficient
// ThreadStackSize in httpd.conf - See: https://bugs.php.net/bug.php?id=47689
if (stripos(php_uname('s'), 'WIN') === 0 && !$this->{$win_check}) {
$minify = false;
}
return $minify;
}
}
@@ -0,0 +1,182 @@
<?php
/**
* @package Grav\Common\Assets\Traits
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Assets\Traits;
use Grav\Common\Grav;
use Grav\Common\Utils;
trait AssetUtilsTrait
{
/**
* Determine whether a link is local or remote.
* Understands both "http://" and "https://" as well as protocol agnostic links "//"
*
* @param string $link
* @return bool
*/
public static function isRemoteLink($link)
{
$base = Grav::instance()['uri']->rootUrl(true);
// Sanity check for local URLs with absolute URL's enabled
if (Utils::startsWith($link, $base)) {
return false;
}
return (0 === strpos($link, 'http://') || 0 === strpos($link, 'https://') || 0 === strpos($link, '//'));
}
/**
* Download and concatenate the content of several links.
*
* @param array $assets
* @param bool $css
*
* @return string
*/
protected function gatherLinks(array $assets, $css = true)
{
$buffer = '';
foreach ($assets as $id => $asset) {
$local = true;
$link = $asset->getAsset();
$relative_path = $link;
if (static::isRemoteLink($link)) {
$local = false;
if (0 === strpos($link, '//')) {
$link = 'http:' . $link;
}
$relative_dir = \dirname($relative_path);
} else {
// Fix to remove relative dir if grav is in one
if (($this->base_url !== '/') && Utils::startsWith($relative_path, $this->base_url)) {
$base_url = '#' . preg_quote($this->base_url, '#') . '#';
$relative_path = ltrim(preg_replace($base_url, '/', $link, 1), '/');
}
$relative_dir = \dirname($relative_path);
$link = ROOT_DIR . $relative_path;
}
$file = ($this->fetch_command instanceof \Closure) ? @$this->fetch_command->__invoke($link) : @file_get_contents($link);
// No file found, skip it...
if ($file === false) {
continue;
}
// Double check last character being
if (!$css) {
$file = rtrim($file, ' ;') . ';';
}
// If this is CSS + the file is local + rewrite enabled
if ($css && $this->css_rewrite) {
$file = $this->cssRewrite($file, $relative_dir, $local);
}
$file = rtrim($file) . PHP_EOL;
$buffer .= $file;
}
// Pull out @imports and move to top
if ($css) {
$buffer = $this->moveImports($buffer);
}
return $buffer;
}
/**
* Moves @import statements to the top of the file per the CSS specification
*
* @param string $file the file containing the combined CSS files
*
* @return string the modified file with any @imports at the top of the file
*/
protected function moveImports($file)
{
$imports = [];
$file = (string)preg_replace_callback(self::CSS_IMPORT_REGEX, function ($matches) use (&$imports) {
$imports[] = $matches[0];
return '';
}, $file);
return implode("\n", $imports) . "\n\n" . $file;
}
/**
*
* Build an HTML attribute string from an array.
*
* @return string
*/
protected function renderAttributes()
{
$html = '';
$no_key = ['loading'];
foreach ($this->attributes as $key => $value) {
if (is_numeric($key)) {
$key = $value;
}
if (\is_array($value)) {
$value = implode(' ', $value);
}
if (\in_array($key, $no_key, true)) {
$element = htmlentities($value, ENT_QUOTES, 'UTF-8', false);
} else {
$element = $key . '="' . htmlentities($value, ENT_QUOTES, 'UTF-8', false) . '"';
}
$html .= ' ' . $element;
}
return $html;
}
/**
* Render Querystring
*
* @param string $asset
* @return string
*/
protected function renderQueryString($asset = null)
{
$querystring = '';
$asset = $asset ?? $this->asset;
if (!empty($this->query)) {
if (Utils::contains($asset, '?')) {
$querystring .= '&' . $this->query;
} else {
$querystring .= '?' . $this->query;
}
}
if ($this->timestamp) {
if (Utils::contains($asset, '?') || $querystring) {
$querystring .= '&' . $this->timestamp;
} else {
$querystring .= '?' . $this->timestamp;
}
}
return $querystring;
}
}
@@ -0,0 +1,125 @@
<?php
/**
* @package Grav\Common\Assets\Traits
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Assets\Traits;
use Grav\Common\Assets;
trait LegacyAssetsTrait
{
/**
* @param array $args
* @param string $type
* @return array
*/
protected function unifyLegacyArguments($args, $type = Assets::CSS_TYPE)
{
// First argument is always the asset
array_shift($args);
if (count($args) === 0) {
return [];
}
// New options array format
if (count($args) === 1 && is_array($args[0])) {
return $args[0];
}
// Handle obscure case where options array is mixed with a priority
if (count($args) === 2 && is_array($args[0]) && is_int($args[1])) {
$arguments = $args[0];
$arguments['priority'] = $args[1];
return $arguments;
}
switch ($type) {
case(Assets::JS_TYPE):
$defaults = ['priority' => null, 'pipeline' => true, 'loading' => null, 'group' => null];
$arguments = $this->createArgumentsFromLegacy($args, $defaults);
break;
case(Assets::INLINE_JS_TYPE):
$defaults = ['priority' => null, 'group' => null, 'attributes' => null];
$arguments = $this->createArgumentsFromLegacy($args, $defaults);
// special case to handle old attributes being passed in
if (isset($arguments['attributes'])) {
$old_attributes = $arguments['attributes'];
$arguments = array_merge($arguments, $old_attributes);
}
unset($arguments['attributes']);
break;
case(Assets::INLINE_CSS_TYPE):
$defaults = ['priority' => null, 'group' => null];
$arguments = $this->createArgumentsFromLegacy($args, $defaults);
break;
default:
case(Assets::CSS_TYPE):
$defaults = ['priority' => null, 'pipeline' => true, 'group' => null, 'loading' => null];
$arguments = $this->createArgumentsFromLegacy($args, $defaults);
}
return $arguments;
}
protected function createArgumentsFromLegacy(array $args, array $defaults)
{
// Remove arguments with old default values.
$arguments = [];
foreach ($args as $arg) {
$default = current($defaults);
if ($arg !== $default) {
$arguments[key($defaults)] = $arg;
}
next($defaults);
}
return $arguments;
}
/**
* Convenience wrapper for async loading of JavaScript
*
* @param string|array $asset
* @param int $priority
* @param bool $pipeline
* @param string $group name of the group
*
* @return \Grav\Common\Assets
* @deprecated Please use dynamic method with ['loading' => 'async'].
*/
public function addAsyncJs($asset, $priority = 10, $pipeline = true, $group = 'head')
{
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use dynamic method with [\'loading\' => \'async\']', E_USER_DEPRECATED);
return $this->addJs($asset, $priority, $pipeline, 'async', $group);
}
/**
* Convenience wrapper for deferred loading of JavaScript
*
* @param string|array $asset
* @param int $priority
* @param bool $pipeline
* @param string $group name of the group
*
* @return \Grav\Common\Assets
* @deprecated Please use dynamic method with ['loading' => 'defer'].
*/
public function addDeferJs($asset, $priority = 10, $pipeline = true, $group = 'head')
{
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use dynamic method with [\'loading\' => \'defer\']', E_USER_DEPRECATED);
return $this->addJs($asset, $priority, $pipeline, 'defer', $group);
}
}
@@ -0,0 +1,343 @@
<?php
/**
* @package Grav\Common\Assets\Traits
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Assets\Traits;
use Grav\Common\Grav;
trait TestingAssetsTrait
{
/**
* Determines if an asset exists as a collection, CSS or JS reference
*
* @param string $asset
*
* @return bool
*/
public function exists($asset)
{
return isset($this->collections[$asset]) || isset($this->assets_css[$asset]) || isset($this->assets_js[$asset]);
}
/**
* Return the array of all the registered collections
*
* @return array
*/
public function getCollections()
{
return $this->collections;
}
/**
* Set the array of collections explicitly
*
* @param array $collections
*
* @return $this
*/
public function setCollection($collections)
{
$this->collections = $collections;
return $this;
}
/**
* Return the array of all the registered CSS assets
* If a $key is provided, it will try to return only that asset
* else it will return null
*
* @param null|string $key the asset key
* @return array
*/
public function getCss($key = null)
{
if (null !== $key) {
$asset_key = md5($key);
return $this->assets_css[$asset_key] ?? null;
}
return $this->assets_css;
}
/**
* Return the array of all the registered JS assets
* If a $key is provided, it will try to return only that asset
* else it will return null
*
* @param null|string $key the asset key
* @return array
*/
public function getJs($key = null)
{
if (null !== $key) {
$asset_key = md5($key);
return $this->assets_js[$asset_key] ?? null;
}
return $this->assets_js;
}
/**
* Set the whole array of CSS assets
*
* @param array $css
*
* @return $this
*/
public function setCss($css)
{
$this->assets_css = $css;
return $this;
}
/**
* Set the whole array of JS assets
*
* @param array $js
*
* @return $this
*/
public function setJs($js)
{
$this->assets_js = $js;
return $this;
}
/**
* Removes an item from the CSS array if set
*
* @param string $key The asset key
*
* @return $this
*/
public function removeCss($key)
{
$asset_key = md5($key);
if (isset($this->assets_css[$asset_key])) {
unset($this->assets_css[$asset_key]);
}
return $this;
}
/**
* Removes an item from the JS array if set
*
* @param string $key The asset key
*
* @return $this
*/
public function removeJs($key)
{
$asset_key = md5($key);
if (isset($this->assets_js[$asset_key])) {
unset($this->assets_js[$asset_key]);
}
return $this;
}
/**
* Sets the state of CSS Pipeline
*
* @param bool $value
*
* @return $this
*/
public function setCssPipeline($value)
{
$this->css_pipeline = (bool)$value;
return $this;
}
/**
* Sets the state of JS Pipeline
*
* @param bool $value
*
* @return $this
*/
public function setJsPipeline($value)
{
$this->js_pipeline = (bool)$value;
return $this;
}
/**
* Reset all assets.
*
* @return $this
*/
public function reset()
{
$this->resetCss();
$this->resetJs();
$this->setCssPipeline(false);
$this->setJsPipeline(false);
return $this;
}
/**
* Reset JavaScript assets.
*
* @return $this
*/
public function resetJs()
{
$this->assets_js = [];
return $this;
}
/**
* Reset CSS assets.
*
* @return $this
*/
public function resetCss()
{
$this->assets_css = [];
return $this;
}
/**
* Explicitly set's a timestamp for assets
*
* @param string|int $value
*/
public function setTimestamp($value)
{
$this->timestamp = $value;
}
/**
* Get the timestamp for assets
*
* @param bool $include_join
* @return string
*/
public function getTimestamp($include_join = true)
{
if ($this->timestamp) {
return $include_join ? '?' . $this->timestamp : $this->timestamp;
}
return null;
}
/**
* Add all assets matching $pattern within $directory.
*
* @param string $directory Relative to the Grav root path, or a stream identifier
* @param string $pattern (regex)
*
* @return $this
*/
public function addDir($directory, $pattern = self::DEFAULT_REGEX)
{
$root_dir = rtrim(ROOT_DIR, '/');
// Check if $directory is a stream.
if (strpos($directory, '://')) {
$directory = Grav::instance()['locator']->findResource($directory, null);
}
// Get files
$files = $this->rglob($root_dir . DIRECTORY_SEPARATOR . $directory, $pattern, $root_dir . '/');
// No luck? Nothing to do
if (!$files) {
return $this;
}
// Add CSS files
if ($pattern === self::CSS_REGEX) {
foreach ($files as $file) {
$this->addCss($file);
}
return $this;
}
// Add JavaScript files
if ($pattern === self::JS_REGEX) {
foreach ($files as $file) {
$this->addJs($file);
}
return $this;
}
// Unknown pattern.
foreach ($files as $asset) {
$this->add($asset);
}
return $this;
}
/**
* Add all JavaScript assets within $directory
*
* @param string $directory Relative to the Grav root path, or a stream identifier
*
* @return $this
*/
public function addDirJs($directory)
{
return $this->addDir($directory, self::JS_REGEX);
}
/**
* Add all CSS assets within $directory
*
* @param string $directory Relative to the Grav root path, or a stream identifier
*
* @return $this
*/
public function addDirCss($directory)
{
return $this->addDir($directory, self::CSS_REGEX);
}
/**
* Recursively get files matching $pattern within $directory.
*
* @param string $directory
* @param string $pattern (regex)
* @param string $ltrim Will be trimmed from the left of the file path
*
* @return array
*/
protected function rglob($directory, $pattern, $ltrim = null)
{
$iterator = new \RegexIterator(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($directory,
\FilesystemIterator::SKIP_DOTS)), $pattern);
$offset = \strlen($ltrim);
$files = [];
foreach ($iterator as $file) {
$files[] = substr($file->getPathname(), $offset);
}
return $files;
}
}
+261
View File
@@ -0,0 +1,261 @@
<?php
/**
* @package Grav\Common\Backup
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Backup;
use Grav\Common\Filesystem\Archiver;
use Grav\Common\Filesystem\Folder;
use Grav\Common\Inflector;
use Grav\Common\Scheduler\Job;
use Grav\Common\Scheduler\Scheduler;
use Grav\Common\Utils;
use Grav\Common\Grav;
use RocketTheme\Toolbox\Event\Event;
use RocketTheme\Toolbox\Event\EventDispatcher;
use RocketTheme\Toolbox\File\JsonFile;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
class Backups
{
protected const BACKUP_FILENAME_REGEXZ = "#(.*)--(\d*).zip#";
protected const BACKUP_DATE_FORMAT = 'YmdHis';
protected static $backup_dir;
protected static $backups = null;
public function init()
{
/** @var EventDispatcher $dispatcher */
$dispatcher = Grav::instance()['events'];
$dispatcher->addListener('onSchedulerInitialized', [$this, 'onSchedulerInitialized']);
Grav::instance()->fireEvent('onBackupsInitialized', new Event(['backups' => $this]));
}
public function setup()
{
if (null === static::$backup_dir) {
static::$backup_dir = Grav::instance()['locator']->findResource('backup://', true, true);
Folder::create(static::$backup_dir);
}
}
public function onSchedulerInitialized(Event $event)
{
/** @var Scheduler $scheduler */
$scheduler = $event['scheduler'];
/** @var Inflector $inflector */
$inflector = Grav::instance()['inflector'];
foreach (static::getBackupProfiles() as $id => $profile) {
$at = $profile['schedule_at'];
$name = $inflector::hyphenize($profile['name']);
$logs = 'logs/backup-' . $name . '.out';
/** @var Job $job */
$job = $scheduler->addFunction('Grav\Common\Backup\Backups::backup', [$id], $name );
$job->at($at);
$job->output($logs);
$job->backlink('/tools/backups');
}
}
public function getBackupDownloadUrl($backup, $base_url)
{
$param_sep = $param_sep = Grav::instance()['config']->get('system.param_sep', ':');
$download = urlencode(base64_encode($backup));
$url = rtrim(Grav::instance()['uri']->rootUrl(true), '/') . '/' . trim($base_url,
'/') . '/task' . $param_sep . 'backup/download' . $param_sep . $download . '/admin-nonce' . $param_sep . Utils::getNonce('admin-form');
return $url;
}
public static function getBackupProfiles()
{
return Grav::instance()['config']->get('backups.profiles');
}
public static function getPurgeConfig()
{
return Grav::instance()['config']->get('backups.purge');
}
public function getBackupNames()
{
return array_column(static::getBackupProfiles(), 'name');
}
public static function getTotalBackupsSize()
{
$backups = static::getAvailableBackups();
$size = array_sum(array_column($backups, 'size'));
return $size ?? 0;
}
public static function getAvailableBackups($force = false)
{
if ($force || null === static::$backups) {
static::$backups = [];
$backups_itr = new \GlobIterator(static::$backup_dir . '/*.zip', \FilesystemIterator::KEY_AS_FILENAME);
$inflector = Grav::instance()['inflector'];
$long_date_format = DATE_RFC2822;
/**
* @var string $name
* @var \SplFileInfo $file
*/
foreach ($backups_itr as $name => $file) {
if (preg_match(static::BACKUP_FILENAME_REGEXZ, $name, $matches)) {
$date = \DateTime::createFromFormat(static::BACKUP_DATE_FORMAT, $matches[2]);
$timestamp = $date->getTimestamp();
$backup = new \stdClass();
$backup->title = $inflector->titleize($matches[1]);
$backup->time = $date;
$backup->date = $date->format($long_date_format);
$backup->filename = $name;
$backup->path = $file->getPathname();
$backup->size = $file->getSize();
static::$backups[$timestamp] = $backup;
}
}
// Reverse Key Sort to get in reverse date order
krsort(static::$backups);
}
return static::$backups;
}
/**
* Backup
*
* @param int $id
* @param callable|null $status
*
* @return null|string
*/
public static function backup($id = 0, callable $status = null)
{
$profiles = static::getBackupProfiles();
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
if (isset($profiles[$id])) {
$backup = (object) $profiles[$id];
} else {
throw new \RuntimeException('No backups defined...');
}
$name = Grav::instance()['inflector']->underscorize($backup->name);
$date = date(static::BACKUP_DATE_FORMAT, time());
$filename = trim($name, '_') . '--' . $date . '.zip';
$destination = static::$backup_dir . DS . $filename;
$max_execution_time = ini_set('max_execution_time', 600);
$backup_root = $backup->root;
if ($locator->isStream($backup_root)) {
$backup_root = $locator->findResource($backup_root);
} else {
$backup_root = rtrim(GRAV_ROOT . $backup_root, '/');
}
if (!file_exists($backup_root)) {
throw new \RuntimeException("Backup location: {$backup_root} does not exist...");
}
$options = [
'exclude_files' => static::convertExclude($backup->exclude_files ?? ''),
'exclude_paths' => static::convertExclude($backup->exclude_paths ?? ''),
];
/** @var Archiver $archiver */
$archiver = Archiver::create('zip');
$archiver->setArchive($destination)->setOptions($options)->compress($backup_root, $status)->addEmptyFolders($options['exclude_paths'], $status);
$status && $status([
'type' => 'message',
'message' => 'Done...',
]);
$status && $status([
'type' => 'progress',
'complete' => true
]);
if ($max_execution_time !== false) {
ini_set('max_execution_time', $max_execution_time);
}
// Log the backup
Grav::instance()['log']->notice('Backup Created: ' . $destination);
// Fire Finished event
Grav::instance()->fireEvent('onBackupFinished', new Event(['backup' => $destination]));
// Purge anything required
static::purge();
// Log
$log = JsonFile::instance(Grav::instance()['locator']->findResource("log://backup.log", true, true));
$log->content([
'time' => time(),
'location' => $destination
]);
$log->save();
return $destination;
}
public static function purge()
{
$purge_config = static::getPurgeConfig();
$trigger = $purge_config['trigger'];
$backups = static::getAvailableBackups(true);
switch ($trigger)
{
case 'number':
$backups_count = count($backups);
if ($backups_count > $purge_config['max_backups_count']) {
$last = end($backups);
unlink ($last->path);
static::purge();
}
break;
case 'time':
$last = end($backups);
$now = new \DateTime();
$interval = $now->diff($last->time);
if ($interval->days > $purge_config['max_backups_time']) {
unlink($last->path);
static::purge();
}
break;
default:
$used_space = static::getTotalBackupsSize();
$max_space = $purge_config['max_backups_space'] * 1024 * 1024 * 1024;
if ($used_space > $max_space) {
$last = end($backups);
unlink($last->path);
static::purge();
}
break;
}
}
protected static function convertExclude($exclude)
{
$lines = preg_split("/[\s,]+/", $exclude);
return array_map('trim', $lines, array_fill(0, \count($lines), '/'));
}
}
-144
View File
@@ -1,144 +0,0 @@
<?php
/**
* @package Grav.Common.Backup
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Backup;
use Grav\Common\Grav;
use Grav\Common\Inflector;
class ZipBackup
{
protected static $ignorePaths = [
'backup',
'cache',
'images',
'logs',
'tmp'
];
protected static $ignoreFolders = [
'.git',
'.svn',
'.hg',
'.idea',
'node_modules'
];
/**
* Backup
*
* @param string|null $destination
* @param callable|null $messager
*
* @return null|string
*/
public static function backup($destination = null, callable $messager = null)
{
if (!$destination) {
$destination = Grav::instance()['locator']->findResource('backup://', true);
if (!$destination) {
throw new \RuntimeException('The backup folder is missing.');
}
}
$name = substr(strip_tags(Grav::instance()['config']->get('site.title', basename(GRAV_ROOT))), 0, 20);
$inflector = new Inflector();
if (is_dir($destination)) {
$date = date('YmdHis', time());
$filename = trim($inflector->hyphenize($name), '-') . '-' . $date . '.zip';
$destination = rtrim($destination, DS) . DS . $filename;
}
$messager && $messager([
'type' => 'message',
'level' => 'info',
'message' => 'Creating new Backup "' . $destination . '"'
]);
$messager && $messager([
'type' => 'message',
'level' => 'info',
'message' => ''
]);
$zip = new \ZipArchive();
$zip->open($destination, \ZipArchive::CREATE);
$max_execution_time = ini_set('max_execution_time', 600);
static::folderToZip(GRAV_ROOT, $zip, strlen(rtrim(GRAV_ROOT, DS) . DS), $messager);
$messager && $messager([
'type' => 'progress',
'percentage' => false,
'complete' => true
]);
$messager && $messager([
'type' => 'message',
'level' => 'info',
'message' => ''
]);
$messager && $messager([
'type' => 'message',
'level' => 'info',
'message' => 'Saving and compressing archive...'
]);
$zip->close();
if ($max_execution_time !== false) {
ini_set('max_execution_time', $max_execution_time);
}
return $destination;
}
/**
* @param $folder
* @param $zipFile
* @param $exclusiveLength
* @param $messager
*/
private static function folderToZip($folder, \ZipArchive $zipFile, $exclusiveLength, callable $messager = null)
{
$handle = opendir($folder);
while (false !== $f = readdir($handle)) {
if ($f !== '.' && $f !== '..') {
$filePath = "$folder/$f";
// Remove prefix from file path before add to zip.
$localPath = substr($filePath, $exclusiveLength);
if (in_array($f, static::$ignoreFolders)) {
continue;
}
if (in_array($localPath, static::$ignorePaths)) {
$zipFile->addEmptyDir($f);
continue;
}
if (is_file($filePath)) {
$zipFile->addFile($filePath, $localPath);
$messager && $messager([
'type' => 'progress',
'percentage' => false,
'complete' => false
]);
} elseif (is_dir($filePath)) {
// Add sub-directory.
$zipFile->addEmptyDir($localPath);
static::folderToZip($filePath, $zipFile, $exclusiveLength, $messager);
}
}
}
closedir($handle);
}
}
+15 -3
View File
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common
* @package Grav\Common
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -113,7 +114,7 @@ class Browser
{
$version = explode('.', $this->getLongVersion());
return intval($version[0]);
return (int)$version[0];
}
/**
@@ -134,4 +135,15 @@ class Browser
return true;
}
/**
* Determine if “Do Not Track” is set by browser
* @see https://www.w3.org/TR/tracking-dnt/
*
* @return bool
*/
public function isTrackable(): bool
{
return !(isset($_SERVER['HTTP_DNT']) && $_SERVER['HTTP_DNT'] === '1');
}
}
+209 -63
View File
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common
* @package Grav\Common
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -11,15 +12,16 @@ namespace Grav\Common;
use \Doctrine\Common\Cache as DoctrineCache;
use Grav\Common\Config\Config;
use Grav\Common\Filesystem\Folder;
use Grav\Common\Scheduler\Scheduler;
use Psr\SimpleCache\CacheInterface;
use RocketTheme\Toolbox\Event\Event;
use RocketTheme\Toolbox\Event\EventDispatcher;
/**
* The GravCache object is used throughout Grav to store and retrieve cached data.
* It uses DoctrineCache library and supports a variety of caching mechanisms. Those include:
*
* APCu
* APC
* XCache
* RedisCache
* MemCache
* MemCacheD
@@ -43,6 +45,11 @@ class Cache extends Getters
*/
protected $driver;
/**
* @var CacheInterface
*/
protected $simpleCache;
protected $driver_name;
protected $driver_setting;
@@ -117,37 +124,77 @@ class Cache extends Getters
$this->config = $grav['config'];
$this->now = time();
$this->cache_dir = $grav['locator']->findResource('cache://doctrine', true, true);
if (null === $this->enabled) {
$this->enabled = (bool)$this->config->get('system.cache.enabled');
}
/** @var Uri $uri */
$uri = $grav['uri'];
$prefix = $this->config->get('system.cache.prefix');
if (is_null($this->enabled)) {
$this->enabled = (bool)$this->config->get('system.cache.enabled');
}
$uniqueness = substr(md5($uri->rootUrl(true) . $this->config->key() . GRAV_VERSION), 2, 8);
// Cache key allows us to invalidate all cache on configuration changes.
$this->key = ($prefix ? $prefix : 'g') . '-' . substr(md5($uri->rootUrl(true) . $this->config->key() . GRAV_VERSION),
2, 8);
$this->key = ($prefix ? $prefix : 'g') . '-' . $uniqueness;
$this->cache_dir = $grav['locator']->findResource('cache://doctrine/' . $uniqueness, true, true);
$this->driver_setting = $this->config->get('system.cache.driver');
$this->driver = $this->getCacheDriver();
// Set the cache namespace to our unique key
$this->driver->setNamespace($this->key);
/** @var EventDispatcher $dispatcher */
$dispatcher = Grav::instance()['events'];
$dispatcher->addListener('onSchedulerInitialized', [$this, 'onSchedulerInitialized']);
}
/**
* @return CacheInterface
*/
public function getSimpleCache()
{
if (null === $this->simpleCache) {
$cache = new \Grav\Framework\Cache\Adapter\DoctrineCache($this->driver, '', $this->getLifetime());
// Disable cache key validation.
$cache->setValidation(false);
$this->simpleCache = $cache;
}
return $this->simpleCache;
}
/**
* Deletes the old out of date file-based caches
*
* @return int
*/
public function purgeOldCache()
{
$cache_dir = dirname($this->cache_dir);
$current = basename($this->cache_dir);
$count = 0;
foreach (new \DirectoryIterator($cache_dir) as $file) {
$dir = $file->getBasename();
if ($dir === $current || $file->isDot() || $file->isFile()) {
continue;
}
Folder::delete($file->getPathname());
$count++;
}
return $count;
}
/**
* Public accessor to set the enabled state of the cache
*
* @param $enabled
* @param bool|int $enabled
*/
public function setEnabled($enabled)
{
$this->enabled = (bool) $enabled;
$this->enabled = (bool)$enabled;
}
/**
@@ -184,19 +231,15 @@ class Cache extends Getters
// CLI compatibility requires a non-volatile cache driver
if ($this->config->get('system.cache.cli_compatibility') && (
$setting == 'auto' || $this->isVolatileDriver($setting))) {
$setting === 'auto' || $this->isVolatileDriver($setting))) {
$setting = $driver_name;
}
if (!$setting || $setting == 'auto') {
if (!$setting || $setting === 'auto') {
if (extension_loaded('apcu')) {
$driver_name = 'apcu';
} elseif (extension_loaded('apc')) {
$driver_name = 'apc';
} elseif (extension_loaded('wincache')) {
$driver_name = 'wincache';
} elseif (extension_loaded('xcache')) {
$driver_name = 'xcache';
}
} else {
$driver_name = $setting;
@@ -206,9 +249,6 @@ class Cache extends Getters
switch ($driver_name) {
case 'apc':
$driver = new DoctrineCache\ApcCache();
break;
case 'apcu':
$driver = new DoctrineCache\ApcuCache();
break;
@@ -217,45 +257,53 @@ class Cache extends Getters
$driver = new DoctrineCache\WinCacheCache();
break;
case 'xcache':
$driver = new DoctrineCache\XcacheCache();
break;
case 'memcache':
$memcache = new \Memcache();
$memcache->connect($this->config->get('system.cache.memcache.server', 'localhost'),
$this->config->get('system.cache.memcache.port', 11211));
$driver = new DoctrineCache\MemcacheCache();
$driver->setMemcache($memcache);
if (extension_loaded('memcache')) {
$memcache = new \Memcache();
$memcache->connect($this->config->get('system.cache.memcache.server', 'localhost'),
$this->config->get('system.cache.memcache.port', 11211));
$driver = new DoctrineCache\MemcacheCache();
$driver->setMemcache($memcache);
} else {
throw new \LogicException('Memcache PHP extension has not been installed');
}
break;
case 'memcached':
$memcached = new \Memcached();
$memcached->addServer($this->config->get('system.cache.memcached.server', 'localhost'),
$this->config->get('system.cache.memcached.port', 11211));
$driver = new DoctrineCache\MemcachedCache();
$driver->setMemcached($memcached);
if (extension_loaded('memcached')) {
$memcached = new \Memcached();
$memcached->addServer($this->config->get('system.cache.memcached.server', 'localhost'),
$this->config->get('system.cache.memcached.port', 11211));
$driver = new DoctrineCache\MemcachedCache();
$driver->setMemcached($memcached);
} else {
throw new \LogicException('Memcached PHP extension has not been installed');
}
break;
case 'redis':
$redis = new \Redis();
$socket = $this->config->get('system.cache.redis.socket', false);
$password = $this->config->get('system.cache.redis.password', false);
if (extension_loaded('redis')) {
$redis = new \Redis();
$socket = $this->config->get('system.cache.redis.socket', false);
$password = $this->config->get('system.cache.redis.password', false);
if ($socket) {
$redis->connect($socket);
if ($socket) {
$redis->connect($socket);
} else {
$redis->connect($this->config->get('system.cache.redis.server', 'localhost'),
$this->config->get('system.cache.redis.port', 6379));
}
// Authenticate with password if set
if ($password && !$redis->auth($password)) {
throw new \RedisException('Redis authentication failed');
}
$driver = new DoctrineCache\RedisCache();
$driver->setRedis($redis);
} else {
$redis->connect($this->config->get('system.cache.redis.server', 'localhost'),
$this->config->get('system.cache.redis.port', 6379));
throw new \LogicException('Redis PHP extension has not been installed');
}
// Authenticate with password if set
if ($password && !$redis->auth($password)) {
throw new \RedisException('Redis authentication failed');
}
$driver = new DoctrineCache\RedisCache();
$driver->setRedis($redis);
break;
default:
@@ -277,9 +325,9 @@ class Cache extends Getters
{
if ($this->enabled) {
return $this->driver->fetch($id);
} else {
return false;
}
return false;
}
/**
@@ -310,6 +358,21 @@ class Cache extends Getters
if ($this->enabled) {
return $this->driver->delete($id);
}
return false;
}
/**
* Deletes all cache
*
* @return bool
*/
public function deleteAll()
{
if ($this->enabled) {
return $this->driver->deleteAll();
}
return false;
}
@@ -324,6 +387,7 @@ class Cache extends Getters
if ($this->enabled) {
return $this->driver->contains(($id));
}
return false;
}
@@ -373,6 +437,9 @@ class Cache extends Getters
case 'tmp-only':
$remove_paths = self::$tmp_remove;
break;
case 'invalidate':
$remove_paths = [];
break;
default:
if (Grav::instance()['config']->get('system.cache.clear_images_by_default')) {
$remove_paths = self::$standard_remove;
@@ -382,6 +449,12 @@ class Cache extends Getters
}
// Delete entries in the doctrine cache if required
if (in_array($remove, ['all', 'standard'])) {
$cache = Grav::instance()['cache'];
$cache->driver->deleteAll();
}
// Clearing cache event to add paths to clear
Grav::instance()->fireEvent('onBeforeCacheClear', new Event(['remove' => $remove, 'paths' => &$remove_paths]));
@@ -422,7 +495,7 @@ class Cache extends Getters
$output[] = '';
if (($remove == 'all' || $remove == 'standard') && file_exists($user_config)) {
if (($remove === 'all' || $remove === 'standard') && file_exists($user_config)) {
touch($user_config);
$output[] = '<red>Touched: </red>' . $user_config;
@@ -440,6 +513,23 @@ class Cache extends Getters
return $output;
}
public static function invalidateCache()
{
$user_config = USER_DIR . 'config/system.yaml';
if (file_exists($user_config)) {
touch($user_config);
}
// Clear stat cache
@clearstatcache();
// Clear opcache
if (function_exists('opcache_reset')) {
@opcache_reset();
}
}
/**
* Set the cache lifetime programmatically
@@ -452,7 +542,7 @@ class Cache extends Getters
return;
}
$interval = $future - $this->now;
$interval = (int)($future - $this->now);
if ($interval > 0 && $interval < $this->getLifetime()) {
$this->lifetime = $interval;
}
@@ -467,7 +557,7 @@ class Cache extends Getters
public function getLifetime()
{
if ($this->lifetime === null) {
$this->lifetime = $this->config->get('system.cache.lifetime') ?: 604800; // 1 week default
$this->lifetime = (int)($this->config->get('system.cache.lifetime') ?: 604800); // 1 week default
}
return $this->lifetime;
@@ -496,15 +586,71 @@ class Cache extends Getters
/**
* is this driver a volatile driver in that it resides in PHP process memory
*
* @param $setting
* @param string $setting
* @return bool
*/
public function isVolatileDriver($setting)
{
if (in_array($setting, ['apc', 'apcu', 'xcache', 'wincache'])) {
return true;
} else {
return false;
}
return false;
}
/**
* Static function to call as a scheduled Job to purge old Doctrine files
*/
public static function purgeJob()
{
/** @var Cache $cache */
$cache = Grav::instance()['cache'];
$deleted_folders = $cache->purgeOldCache();
echo 'Purged ' . $deleted_folders . ' old cache folders...';
}
/**
* Static function to call as a scheduled Job to clear Grav cache
*
* @param string $type
*/
public static function clearJob($type)
{
$result = static::clearCache($type);
static::invalidateCache();
echo strip_tags(implode("\n", $result));
}
public function onSchedulerInitialized(Event $event)
{
/** @var Scheduler $scheduler */
$scheduler = $event['scheduler'];
$config = Grav::instance()['config'];
// File Cache Purge
$at = $config->get('system.cache.purge_at');
$name = 'cache-purge';
$logs = 'logs/' . $name . '.out';
$job = $scheduler->addFunction('Grav\Common\Cache::purgeJob', [], $name );
$job->at($at);
$job->output($logs);
$job->backlink('/config/system#caching');
// Cache Clear
$at = $config->get('system.cache.clear_at');
$clear_type = $config->get('system.cache.clear_job_type');
$name = 'cache-clear';
$logs = 'logs/' . $name . '.out';
$job = $scheduler->addFunction('Grav\Common\Cache::clearJob', [$clear_type], $name );
$job->at($at);
$job->output($logs);
$job->backlink('/config/system#caching');
}
}
+7 -6
View File
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common
* @package Grav\Common
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -11,7 +12,7 @@ namespace Grav\Common;
class Composer
{
/** @const Default composer location */
const DEFAULT_PATH = "bin/composer.phar";
const DEFAULT_PATH = 'bin/composer.phar';
/**
* Returns the location of composer.
@@ -20,12 +21,12 @@ class Composer
*/
public static function getComposerLocation()
{
if (!function_exists('shell_exec') || strtolower(substr(PHP_OS, 0, 3)) === 'win') {
if (!\function_exists('shell_exec') || stripos(PHP_OS, 'win') === 0) {
return self::DEFAULT_PATH;
}
// check for global composer install
$path = trim(shell_exec("command -v composer"));
$path = trim(shell_exec('command -v composer'));
// fall back to grav bundled composer
if (!$path || !preg_match('/(composer|composer\.phar)$/', $path)) {
@@ -46,7 +47,7 @@ class Composer
$composer = static::getComposerLocation();
if ($composer !== static::DEFAULT_PATH && is_executable($composer)) {
$file = fopen($composer, 'r');
$file = fopen($composer, 'rb');
$firstLine = fgets($file);
fclose($file);
+9 -10
View File
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.Config
* @package Grav\Common\Config
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -128,7 +129,7 @@ abstract class CompiledBase
*/
public function checksum()
{
if (!isset($this->checksum)) {
if (null === $this->checksum) {
$this->checksum = md5(json_encode($this->files) . $this->version);
}
@@ -197,11 +198,9 @@ abstract class CompiledBase
$cache = include $filename;
if (
!is_array($cache)
|| !isset($cache['checksum'])
|| !isset($cache['data'])
|| !isset($cache['@class'])
|| $cache['@class'] != get_class($this)
!\is_array($cache)
|| !isset($cache['checksum'], $cache['data'], $cache['@class'])
|| $cache['@class'] !== \get_class($this)
) {
return false;
}
@@ -212,7 +211,7 @@ abstract class CompiledBase
}
$this->createObject($cache['data']);
$this->timestamp = isset($cache['timestamp']) ? $cache['timestamp'] : 0;
$this->timestamp = $cache['timestamp'] ?? 0;
$this->finalizeObject();
@@ -243,7 +242,7 @@ abstract class CompiledBase
}
$cache = [
'@class' => get_class($this),
'@class' => \get_class($this),
'timestamp' => time(),
'checksum' => $this->checksum(),
'files' => $this->files,
@@ -1,27 +1,30 @@
<?php
/**
* @package Grav.Common.Config
* @package Grav\Common\Config
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Config;
use Grav\Common\Data\Blueprint;
use Grav\Common\Data\BlueprintSchema;
use Grav\Common\Grav;
/**
* Class CompiledBlueprints
* @package Grav\Common\Config
*/
class CompiledBlueprints extends CompiledBase
{
/**
* @var int Version number for the compiled file.
*/
public $version = 2;
public function __construct($cacheFolder, array $files, $path)
{
parent::__construct($cacheFolder, $files, $path);
/**
* @var BlueprintSchema Blueprints object.
*/
protected $object;
$this->version = 2;
}
/**
* Returns checksum from the configuration files.
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.Config
* @package Grav\Common\Config
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -12,16 +13,6 @@ use Grav\Common\File\CompiledYamlFile;
class CompiledConfig extends CompiledBase
{
/**
* @var int Version number for the compiled file.
*/
public $version = 1;
/**
* @var Config Configuration object.
*/
protected $object;
/**
* @var callable Blueprints loader.
*/
@@ -32,6 +23,13 @@ class CompiledConfig extends CompiledBase
*/
protected $withDefaults;
public function __construct($cacheFolder, array $files, $path)
{
parent::__construct($cacheFolder, $files, $path);
$this->version = 1;
}
/**
* Set blueprints for the configuration.
*
@@ -63,7 +61,7 @@ class CompiledConfig extends CompiledBase
*/
protected function createObject(array $data = [])
{
if ($this->withDefaults && empty($data) && is_callable($this->callable)) {
if ($this->withDefaults && empty($data) && \is_callable($this->callable)) {
$blueprints = $this->callable;
$data = $blueprints()->getDefaults();
}
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.Config
* @package Grav\Common\Config
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -12,15 +13,12 @@ use Grav\Common\File\CompiledYamlFile;
class CompiledLanguages extends CompiledBase
{
/**
* @var int Version number for the compiled file.
*/
public $version = 1;
public function __construct($cacheFolder, array $files, $path)
{
parent::__construct($cacheFolder, $files, $path);
/**
* @var Languages Configuration object.
*/
protected $object;
$this->version = 1;
}
/**
* Create configuration object.
+19 -9
View File
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.Config
* @package Grav\Common\Config
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -16,14 +17,24 @@ use Grav\Common\Utils;
class Config extends Data
{
public $environment;
/** @var string */
protected $key;
/** @var string */
protected $checksum;
protected $modified = false;
/** @var int */
protected $timestamp = 0;
/** @var bool */
protected $modified = false;
public function key()
{
return $this->checksum();
if (null === $this->key) {
$this->key = md5($this->checksum . $this->timestamp);
}
return $this->key;
}
public function checksum($checksum = null)
@@ -90,7 +101,7 @@ class Config extends Data
{
$setup = Grav::instance()['setup']->toArray();
foreach ($setup as $key => $value) {
if ($key === 'streams' || !is_array($value)) {
if ($key === 'streams' || !\is_array($value)) {
// Optimized as streams and simple values are fully defined in setup.
$this->items[$key] = $value;
} else {
@@ -98,14 +109,13 @@ class Config extends Data
}
}
// Override the media.upload_limit based on PHP values
$upload_limit = Utils::getUploadLimit();
$this->items['system']['media']['upload_limit'] = $upload_limit > 0 ? $upload_limit : 1024*1024*1024;
// Legacy value - Override the media.upload_limit based on PHP values
$this->items['system']['media']['upload_limit'] = Utils::getUploadLimit();
}
/**
* @return mixed
* @deprecated
* @deprecated 1.5 Use Grav::instance()['languages'] instead.
*/
public function getLanguages()
{
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.Config
* @package Grav\Common\Config
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
+30 -2
View File
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.Config
* @package Grav\Common\Config
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -13,6 +14,22 @@ use Grav\Common\Utils;
class Languages extends Data
{
/**
* @var string|null
*/
protected $checksum;
/**
* @var string|null
*/
protected $modified;
/**
* @var string|null
*/
protected $timestamp;
public function checksum($checksum = null)
{
if ($checksum !== null) {
@@ -52,4 +69,15 @@ class Languages extends Data
{
$this->items = Utils::arrayMergeRecursiveUnique($this->items, $data);
}
public function flattenByLang($lang)
{
$language = $this->items[$lang];
return Utils::arrayFlattenDotNotation($language);
}
public function unflatten($array)
{
return Utils::arrayUnflattenDotNotation($array);
}
}
+57 -19
View File
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.Config
* @package Grav\Common\Config
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -12,11 +13,23 @@ use Grav\Common\File\CompiledYamlFile;
use Grav\Common\Data\Data;
use Grav\Common\Utils;
use Pimple\Container;
use RocketTheme\Toolbox\File\YamlFile;
use Psr\Http\Message\ServerRequestInterface;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
class Setup extends Data
{
/**
* @var array Environment aliases normalized to lower case.
*/
public static $environments = [
'' => 'unknown',
'127.0.0.1' => 'localhost',
'::1' => 'localhost'
];
/**
* @var string Current environment normalized to lower case.
*/
public static $environment;
protected $streams = [
@@ -38,7 +51,7 @@ class Setup extends Data
// If not defined, environment will be set up in the constructor.
],
'asset' => [
'type' => 'ReadOnlyStream',
'type' => 'Stream',
'prefixes' => [
'' => ['assets'],
]
@@ -109,7 +122,7 @@ class Setup extends Data
]
],
'image' => [
'type' => 'ReadOnlyStream',
'type' => 'Stream',
'prefixes' => [
'' => ['user://images', 'system://images']
]
@@ -120,6 +133,13 @@ class Setup extends Data
'' => ['user://pages']
]
],
'user-data' => [
'type' => 'Stream',
'force' => true,
'prefixes' => [
'' => ['user://data']
]
],
'account' => [
'type' => 'ReadOnlyStream',
'prefixes' => [
@@ -133,12 +153,26 @@ class Setup extends Data
*/
public function __construct($container)
{
$environment = null !== static::$environment ? static::$environment : ($container['uri']->environment() ?: 'localhost');
// If no environment is set, make sure we get one (CLI or hostname).
if (!static::$environment) {
if (\defined('GRAV_CLI')) {
static::$environment = 'cli';
} else {
/** @var ServerRequestInterface $request */
$request = $container['request'];
$host = $request->getUri()->getHost();
static::$environment = Utils::substrToString($host, ':');
}
}
// Resolve server aliases to the proper environment.
$environment = $this->environments[static::$environment] ?? static::$environment;
// Pre-load setup.php which contains our initial configuration.
// Configuration may contain dynamic parts, which is why we need to always load it.
// If "GRAVE_SETUP_PATH" has been defined, use it, otherwise use defaults.
$file = defined('GRAV_SETUP_PATH') ? GRAV_SETUP_PATH : GRAV_ROOT . '/setup.php';
// If "GRAV_SETUP_PATH" has been defined, use it, otherwise use defaults.
$file = \defined('GRAV_SETUP_PATH') ? GRAV_SETUP_PATH : GRAV_ROOT . '/setup.php';
$setup = is_file($file) ? (array) include $file : [];
// Add default streams defined in beginning of the class.
@@ -151,8 +185,8 @@ class Setup extends Data
parent::__construct($setup);
// Set up environment.
$this->def('environment', $environment ?: 'cli');
$this->def('streams.schemes.environment.prefixes', ['' => $environment ? ["user://{$this->environment}"] : []]);
$this->def('environment', $environment);
$this->def('streams.schemes.environment.prefixes', ['' => ["user://{$this->get('environment')}"]]);
}
/**
@@ -212,8 +246,8 @@ class Setup extends Data
$locator->addPath($scheme, '', $config['paths']);
}
$override = isset($config['override']) ? $config['override'] : false;
$force = isset($config['force']) ? $config['force'] : false;
$override = $config['override'] ?? false;
$force = $config['force'] ?? false;
if (isset($config['prefixes'])) {
foreach ((array)$config['prefixes'] as $prefix => $paths) {
@@ -232,7 +266,7 @@ class Setup extends Data
{
$schemes = [];
foreach ((array) $this->get('streams.schemes') as $scheme => $config) {
$type = !empty($config['type']) ? $config['type'] : 'ReadOnlyStream';
$type = $config['type'] ?? 'ReadOnlyStream';
if ($type[0] !== '\\') {
$type = '\\RocketTheme\\Toolbox\\StreamWrapper\\' . $type;
}
@@ -251,8 +285,8 @@ class Setup extends Data
*/
protected function check(UniformResourceLocator $locator)
{
$streams = isset($this->items['streams']['schemes']) ? $this->items['streams']['schemes'] : null;
if (!is_array($streams)) {
$streams = $this->items['streams']['schemes'] ?? null;
if (!\is_array($streams)) {
throw new \InvalidArgumentException('Configuration is missing streams.schemes!');
}
$diff = array_keys(array_diff_key($this->streams, $streams));
@@ -271,10 +305,14 @@ class Setup extends Data
// Create security.yaml if it doesn't exist.
$filename = $locator->findResource('config://security.yaml', true, true);
$file = YamlFile::instance($filename);
if (!$file->exists()) {
$file->save(['salt' => Utils::generateRandomString(14)]);
$file->free();
$security_file = CompiledYamlFile::instance($filename);
$security_content = (array)$security_file->content();
if (!isset($security_content['salt'])) {
$security_content = array_merge($security_content, ['salt' => Utils::generateRandomString(14)]);
$security_file->content($security_content);
$security_file->save();
$security_file->free();
}
} catch (\RuntimeException $e) {
throw new \RuntimeException(sprintf('Grav failed to initialize: %s', $e->getMessage()), 500, $e);
+200 -26
View File
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.Data
* @package Grav\Common\Data
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -10,21 +11,37 @@ namespace Grav\Common\Data;
use Grav\Common\File\CompiledYamlFile;
use Grav\Common\Grav;
use Grav\Common\User\Interfaces\UserInterface;
use RocketTheme\Toolbox\Blueprints\BlueprintForm;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
class Blueprint extends BlueprintForm
{
/**
* @var string
*/
/** @var string */
protected $context = 'blueprints://';
/**
* @var BlueprintSchema
*/
protected $scope;
/** @var BlueprintSchema */
protected $blueprintSchema;
/** @var array */
protected $defaults;
protected $handlers = [];
public function __clone()
{
if ($this->blueprintSchema) {
$this->blueprintSchema = clone $this->blueprintSchema;
}
}
public function setScope($scope)
{
$this->scope = $scope;
}
/**
* Set default values for field types.
*
@@ -51,7 +68,60 @@ class Blueprint extends BlueprintForm
{
$this->initInternals();
return $this->blueprintSchema->getDefaults();
if (null === $this->defaults) {
$this->defaults = $this->blueprintSchema->getDefaults();
}
return $this->defaults;
}
/**
* Initialize blueprints with its dynamic fields.
*
* @return $this
*/
public function init()
{
foreach ($this->dynamic as $key => $data) {
// Locate field.
$path = explode('/', $key);
$current = &$this->items;
foreach ($path as $field) {
if (\is_object($current)) {
// Handle objects.
if (!isset($current->{$field})) {
$current->{$field} = [];
}
$current = &$current->{$field};
} else {
// Handle arrays and scalars.
if (!\is_array($current)) {
$current = [$field => []];
} elseif (!isset($current[$field])) {
$current[$field] = [];
}
$current = &$current[$field];
}
}
// Set dynamic property.
foreach ($data as $property => $call) {
$action = $call['action'];
$method = 'dynamic' . ucfirst($action);
if (isset($this->handlers[$action])) {
$callable = $this->handlers[$action];
$callable($current, $property, $call);
} elseif (method_exists($this, $method)) {
$this->{$method}($current, $property, $call);
}
}
}
return $this;
}
/**
@@ -70,6 +140,20 @@ class Blueprint extends BlueprintForm
return $this->blueprintSchema->mergeData($data1, $data2, $name, $separator);
}
/**
* Process data coming from a form.
*
* @param array $data
* @param array $toggles
* @return array
*/
public function processForm(array $data, array $toggles = [])
{
$this->initInternals();
return $this->blueprintSchema->processForm($data, $toggles);
}
/**
* Return data fields that do not exist in blueprints.
*
@@ -101,15 +185,32 @@ class Blueprint extends BlueprintForm
* Filter data by using blueprints.
*
* @param array $data
* @param bool $missingValuesAsNull
* @param bool $keepEmptyValues
* @return array
*/
public function filter(array $data)
public function filter(array $data, bool $missingValuesAsNull = false, bool $keepEmptyValues = false)
{
$this->initInternals();
return $this->blueprintSchema->filter($data);
return $this->blueprintSchema->filter($data, $missingValuesAsNull, $keepEmptyValues);
}
/**
* Flatten data by using blueprints.
*
* @param array $data
* @return array
*/
public function flattenData(array $data)
{
$this->initInternals();
return $this->blueprintSchema->flattenData($data);
}
/**
* Return blueprint data schema.
*
@@ -122,20 +223,28 @@ class Blueprint extends BlueprintForm
return $this->blueprintSchema;
}
public function addDynamicHandler(string $name, callable $callable): void
{
$this->handlers[$name] = $callable;
}
/**
* Initialize validator.
*/
protected function initInternals()
{
if (!isset($this->blueprintSchema)) {
if (null === $this->blueprintSchema) {
$types = Grav::instance()['plugins']->formFieldTypes;
$this->blueprintSchema = new BlueprintSchema;
if ($types) {
$this->blueprintSchema->setTypes($types);
}
$this->blueprintSchema->embed('', $this->items);
$this->blueprintSchema->init();
$this->defaults = null;
}
}
@@ -162,17 +271,19 @@ class Blueprint extends BlueprintForm
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
if (is_string($path) && !$locator->isStream($path)) {
if (\is_string($path) && !$locator->isStream($path)) {
// Find path overrides.
$paths = isset($this->overrides[$path]) ? (array) $this->overrides[$path] : [];
$paths = (array) ($this->overrides[$path] ?? null);
// Add path pointing to default context.
if ($context === null) {
$context = $this->context;
}
if ($context && $context[strlen($context)-1] !== '/') {
if ($context && $context[\strlen($context)-1] !== '/') {
$context .= '/';
}
$path = $context . $path;
if (!preg_match('/\.yaml$/', $path)) {
@@ -186,7 +297,7 @@ class Blueprint extends BlueprintForm
$files = [];
foreach ($paths as $lookup) {
if (is_string($lookup) && strpos($lookup, '://')) {
if (\is_string($lookup) && strpos($lookup, '://')) {
$files = array_merge($files, $locator->findResources($lookup));
} else {
$files[] = $lookup;
@@ -205,27 +316,29 @@ class Blueprint extends BlueprintForm
{
$params = $call['params'];
if (is_array($params)) {
if (\is_array($params)) {
$function = array_shift($params);
} else {
$function = $params;
$params = [];
}
list($o, $f) = preg_split('/::/', $function, 2);
[$o, $f] = explode('::', $function, 2);
$data = null;
if (!$f) {
if (function_exists($o)) {
$data = call_user_func_array($o, $params);
if (\function_exists($o)) {
$data = \call_user_func_array($o, $params);
}
} else {
if (method_exists($o, $f)) {
$data = call_user_func_array(array($o, $f), $params);
$data = \call_user_func_array([$o, $f], $params);
}
}
// If function returns a value,
if (isset($data)) {
if (isset($field[$property]) && is_array($field[$property]) && is_array($data)) {
if (null !== $data) {
if (\is_array($data) && isset($field[$property]) && \is_array($field[$property])) {
// Combine field and @data-field together.
$field[$property] += $data;
} else {
@@ -243,12 +356,73 @@ class Blueprint extends BlueprintForm
protected function dynamicConfig(array &$field, $property, array &$call)
{
$value = $call['params'];
$default = isset($field[$property]) ? $field[$property] : null;
$default = $field[$property] ?? null;
$config = Grav::instance()['config']->get($value, $default);
if (!is_null($config)) {
if (null !== $config) {
$field[$property] = $config;
}
}
/**
* @param array $field
* @param string $property
* @param array $call
*/
protected function dynamicSecurity(array &$field, $property, array &$call)
{
if ($property || !empty($field['validate']['ignore'])) {
return;
}
$grav = Grav::instance();
$actions = (array)$call['params'];
/** @var UserInterface|null $user */
$user = $grav['user'] ?? null;
foreach ($actions as $action) {
if (!$user || !$user->authorize($action)) {
$this->addPropertyRecursive($field, 'validate', ['ignore' => true]);
return;
}
}
}
/**
* @param array $field
* @param string $property
* @param array $call
*/
protected function dynamicScope(array &$field, $property, array &$call)
{
if ($property && $property !== 'ignore') {
return;
}
$scopes = (array)$call['params'];
$matches = \in_array($this->scope, $scopes, true);
if ($this->scope && $property !== 'ignore') {
$matches = !$matches;
}
if ($matches) {
$this->addPropertyRecursive($field, 'validate', ['ignore' => true]);
return;
}
}
protected function addPropertyRecursive(array &$field, $property, $value)
{
if (\is_array($value) && isset($field[$property]) && \is_array($field[$property])) {
$field[$property] = array_merge_recursive($field[$property], $value);
} else {
$field[$property] = $value;
}
if (!empty($field['fields'])) {
foreach ($field['fields'] as $key => &$child) {
$this->addPropertyRecursive($child, $property, $value);
}
}
}
}
+174 -26
View File
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.Data
* @package Grav\Common\Data
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -26,6 +27,23 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
'fields' => true
];
/**
* @return array
*/
public function getTypes()
{
return $this->types;
}
/**
* @param string $name
* @return array
*/
public function getType($name)
{
return $this->types[$name] ?? [];
}
/**
* Validate data against blueprints.
*
@@ -47,35 +65,90 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
}
/**
* Filter data by using blueprints.
*
* @param array $data
* @param array $data
* @param array $toggles
* @return array
*/
public function filter(array $data)
public function processForm(array $data, array $toggles = [])
{
return $this->filterArray($data, $this->nested);
return $this->processFormRecursive($data, $toggles, $this->nested);
}
/**
* Filter data by using blueprints.
*
* @param array $data Incoming data, for example from a form.
* @param bool $missingValuesAsNull Include missing values as nulls.
* @param bool $keepEmptyValues Include empty values.
* @return array
*/
public function filter(array $data, $missingValuesAsNull = false, $keepEmptyValues = false)
{
return $this->filterArray($data, $this->nested, $missingValuesAsNull, $keepEmptyValues);
}
/**
* Flatten data by using blueprints.
*
* @param array $data Data to be flattened.
* @return array
*/
public function flattenData(array $data)
{
return $this->flattenArray($data, $this->nested, '');
}
/**
* @param array $data
* @param array $rules
* @returns array
* @param string $prefix
* @return array
*/
protected function flattenArray(array $data, array $rules, string $prefix)
{
$array = [];
foreach ($data as $key => $field) {
$val = $rules[$key] ?? $rules['*'] ?? null;
$rule = is_string($val) ? $this->items[$val] : null;
if ($rule || isset($val['*'])) {
// Item has been defined in blueprints.
$array[$prefix.$key] = $field;
} elseif (is_array($field) && is_array($val)) {
// Array has been defined in blueprints.
$array += $this->flattenArray($field, $val, $prefix . $key . '.');
} else {
// Undefined/extra item.
$array[$prefix.$key] = $field;
}
}
return $array;
}
/**
* @param array $data
* @param array $rules
* @return array
* @throws \RuntimeException
* @internal
*/
protected function validateArray(array $data, array $rules)
{
$messages = $this->checkRequired($data, $rules);
foreach ($data as $key => $field) {
$val = isset($rules[$key]) ? $rules[$key] : (isset($rules['*']) ? $rules['*'] : null);
$rule = is_string($val) ? $this->items[$val] : null;
$val = $rules[$key] ?? $rules['*'] ?? null;
$rule = \is_string($val) ? $this->items[$val] : null;
if ($rule) {
// Item has been defined in blueprints.
if (!empty($rule['disabled']) || !empty($rule['validate']['ignore'])) {
// Skip validation in the ignored field.
continue;
}
$messages += Validation::validate($field, $rule);
} elseif (is_array($field) && is_array($val)) {
} elseif (\is_array($field) && \is_array($val)) {
// Array has been defined in blueprints.
$messages += $this->validateArray($field, $val);
} elseif (isset($rules['validation']) && $rules['validation'] === 'strict') {
@@ -90,32 +163,99 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
/**
* @param array $data
* @param array $rules
* @param bool $missingValuesAsNull
* @param bool $keepEmptyValues
* @return array
* @internal
*/
protected function filterArray(array $data, array $rules)
protected function filterArray(array $data, array $rules, $missingValuesAsNull, $keepEmptyValues)
{
$results = array();
$results = [];
if ($missingValuesAsNull) {
// First pass is to fill up all the fields with null. This is done to lock the ordering of the fields.
foreach ($rules as $key => $rule) {
if ($key && !isset($results[$key])) {
$val = $rules[$key] ?? $rules['*'] ?? null;
$rule = \is_string($val) ? $this->items[$val] : null;
if (empty($rule['disabled']) && empty($rule['validate']['ignore'])) {
continue;
}
}
}
}
foreach ($data as $key => $field) {
$val = isset($rules[$key]) ? $rules[$key] : (isset($rules['*']) ? $rules['*'] : null);
$rule = is_string($val) ? $this->items[$val] : null;
$val = $rules[$key] ?? $rules['*'] ?? null;
$rule = \is_string($val) ? $this->items[$val] : null;
if ($rule) {
// Item has been defined in blueprints.
if (!empty($rule['disabled']) || !empty($rule['validate']['ignore'])) {
// Skip any data in the ignored field.
unset($results[$key]);
continue;
}
$field = Validation::filter($field, $rule);
} elseif (is_array($field) && is_array($val)) {
} elseif (\is_array($field) && \is_array($val)) {
// Array has been defined in blueprints.
$field = $this->filterArray($field, $val);
$field = $this->filterArray($field, $val, $missingValuesAsNull, $keepEmptyValues);
} elseif (isset($rules['validation']) && $rules['validation'] === 'strict') {
$field = null;
// Skip any extra data.
continue;
}
if (isset($field) && (!is_array($field) || !empty($field))) {
if ($keepEmptyValues || (null !== $field && (!\is_array($field) || !empty($field)))) {
$results[$key] = $field;
}
}
return $results;
return $results ?: null;
}
/**
* @param array|null $data
* @param array $toggles
* @param array $nested
* @return array|null
*/
protected function processFormRecursive(?array $data, array $toggles, array $nested)
{
foreach ($nested as $key => $value) {
if ($key === '') {
continue;
}
if ($key === '*') {
// TODO: Add support to collections.
continue;
}
if (is_array($value)) {
// Recursively fetch the items.
$data[$key] = $this->processFormRecursive($data[$key] ?? null, $toggles[$key] ?? [], $value);
} else {
$field = $this->get($value);
// Do not add the field if:
if (
// Not an input field
!$field
// Field has been disabled
|| !empty($field['disabled'])
// Field validation is set to be ignored
|| !empty($field['validate']['ignore'])
// Field is toggleable and the toggle is turned off
|| (!empty($field['toggleable']) && empty($toggles[$key]))
) {
continue;
}
if (!isset($data[$key])) {
$data[$key] = null;
}
}
}
return $data;
}
/**
@@ -128,10 +268,18 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
$messages = [];
foreach ($fields as $name => $field) {
if (!is_string($field)) {
if (!\is_string($field)) {
continue;
}
$field = $this->items[$field];
// Skip ignored field, it will not be required.
if (!empty($field['disabled']) || !empty($field['validate']['ignore'])) {
continue;
}
// Check if required.
if (isset($field['validate']['required'])
&& $field['validate']['required'] === true) {
@@ -142,9 +290,9 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
continue;
}
$value = isset($field['label']) ? $field['label'] : $field['name'];
$value = $field['label'] ?? $field['name'];
$language = Grav::instance()['language'];
$message = sprintf($language->translate('FORM.MISSING_REQUIRED_FIELD', null, true) . ' %s', $language->translate($value));
$message = sprintf($language->translate('GRAV.FORM.MISSING_REQUIRED_FIELD', null, true) . ' %s', $language->translate($value));
$messages[$field['name']][] = $message;
}
}
@@ -161,7 +309,7 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
{
$value = $call['params'];
$default = isset($field[$property]) ? $field[$property] : null;
$default = $field[$property] ?? null;
$config = Grav::instance()['config']->get($value, $default);
if (null !== $config) {
+20 -6
View File
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.Data
* @package Grav\Common\Data
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -13,8 +14,11 @@ use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
class Blueprints
{
/** @var array|string */
protected $search;
/** @var array */
protected $types;
/** @var array */
protected $instances = [];
/**
@@ -35,7 +39,8 @@ class Blueprints
public function get($type)
{
if (!isset($this->instances[$type])) {
$this->instances[$type] = $this->loadFile($type);
$blueprint = $this->loadFile($type);
$this->instances[$type] = $blueprint;
}
return $this->instances[$type];
@@ -49,7 +54,7 @@ class Blueprints
public function types()
{
if ($this->types === null) {
$this->types = array();
$this->types = [];
$grav = Grav::instance();
@@ -87,7 +92,7 @@ class Blueprints
{
$blueprint = new Blueprint($name);
if (is_array($this->search) || is_object($this->search)) {
if (\is_array($this->search) || \is_object($this->search)) {
// Page types.
$blueprint->setOverrides($this->search);
$blueprint->setContext('blueprints://pages');
@@ -95,6 +100,15 @@ class Blueprints
$blueprint->setContext($this->search);
}
return $blueprint->load()->init();
try {
$blueprint->load()->init();
} catch (\RuntimeException $e) {
$log = Grav::instance()['log'];
$log->error(sprintf('Blueprint %s cannot be loaded: %s', $name, $e->getMessage()));
throw $e;
}
return $blueprint;
}
}
+33 -21
View File
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.Data
* @package Grav\Common\Data
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -15,28 +16,27 @@ use RocketTheme\Toolbox\ArrayTraits\NestedArrayAccessWithGetters;
use RocketTheme\Toolbox\File\File;
use RocketTheme\Toolbox\File\FileInterface;
class Data implements DataInterface, \ArrayAccess, \Countable, ExportInterface
class Data implements DataInterface, \ArrayAccess, \Countable, \JsonSerializable, ExportInterface
{
use NestedArrayAccessWithGetters, Countable, Export;
/** @var string */
protected $gettersVariable = 'items';
/** @var array */
protected $items;
/**
* @var Blueprints
*/
/** @var Blueprint */
protected $blueprints;
/**
* @var File
*/
/** @var File */
protected $storage;
/**
* @param array $items
* @param Blueprint|callable $blueprints
*/
public function __construct(array $items = array(), $blueprints = null)
public function __construct(array $items = [], $blueprints = null)
{
$this->items = $items;
$this->blueprints = $blueprints;
@@ -70,14 +70,16 @@ class Data implements DataInterface, \ArrayAccess, \Countable, ExportInterface
{
$old = $this->get($name, null, $separator);
if ($old !== null) {
if (!is_array($old)) {
if (!\is_array($old)) {
throw new \RuntimeException('Value ' . $old);
}
if (is_object($value)) {
if (\is_object($value)) {
$value = (array) $value;
} elseif (!is_array($value)) {
} elseif (!\is_array($value)) {
throw new \RuntimeException('Value ' . $value);
}
$value = $this->blueprints()->mergeData($old, $value, $name, $separator);
}
@@ -108,9 +110,10 @@ class Data implements DataInterface, \ArrayAccess, \Countable, ExportInterface
*/
public function joinDefaults($name, $value, $separator = '.')
{
if (is_object($value)) {
if (\is_object($value)) {
$value = (array) $value;
}
$old = $this->get($name, null, $separator);
if ($old !== null) {
$value = $this->blueprints()->mergeData($value, $old, $name, $separator);
@@ -125,16 +128,16 @@ class Data implements DataInterface, \ArrayAccess, \Countable, ExportInterface
* Get value from the configuration and join it with given data.
*
* @param string $name Dot separated path to the requested value.
* @param array $value Value to be joined.
* @param array|object $value Value to be joined.
* @param string $separator Separator, defaults to '.'
* @return array
* @throws \RuntimeException
*/
public function getJoined($name, $value, $separator = '.')
{
if (is_object($value)) {
if (\is_object($value)) {
$value = (array) $value;
} elseif (!is_array($value)) {
} elseif (!\is_array($value)) {
throw new \RuntimeException('Value ' . $value);
}
@@ -145,7 +148,7 @@ class Data implements DataInterface, \ArrayAccess, \Countable, ExportInterface
return $value;
}
if (!is_array($old)) {
if (!\is_array($old)) {
throw new \RuntimeException('Value ' . $old);
}
@@ -195,11 +198,14 @@ class Data implements DataInterface, \ArrayAccess, \Countable, ExportInterface
/**
* @return $this
* Filter all items by using blueprints.
*/
public function filter()
{
$this->items = $this->blueprints()->filter($this->items);
$args = func_get_args();
$missingValuesAsNull = (bool)(array_shift($args) ?: false);
$keepEmptyValues = (bool)(array_shift($args) ?: false);
$this->items = $this->blueprints()->filter($this->items, $missingValuesAsNull, $keepEmptyValues);
return $this;
}
@@ -223,7 +229,7 @@ class Data implements DataInterface, \ArrayAccess, \Countable, ExportInterface
{
if (!$this->blueprints){
$this->blueprints = new Blueprint;
} elseif (is_callable($this->blueprints)) {
} elseif (\is_callable($this->blueprints)) {
// Lazy load blueprints.
$blueprints = $this->blueprints;
$this->blueprints = $blueprints();
@@ -282,6 +288,12 @@ class Data implements DataInterface, \ArrayAccess, \Countable, ExportInterface
if ($storage) {
$this->storage = $storage;
}
return $this->storage;
}
public function jsonSerialize()
{
return $this->items;
}
}
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.Data
* @package Grav\Common\Data
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
+143 -104
View File
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.Data
* @package Grav\Common\Data
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -11,62 +12,59 @@ namespace Grav\Common\Data;
use Grav\Common\Grav;
use Grav\Common\Utils;
use Grav\Common\Yaml;
use RocketTheme\Toolbox\Compat\Yaml\Yaml as FallbackYaml;
class Validation
{
/**
* Validate value against a blueprint field definition.
*
* @param $value
* @param mixed $value
* @param array $field
* @return array
*/
public static function validate($value, array $field)
{
$messages = [];
$validate = isset($field['validate']) ? (array) $field['validate'] : [];
// Validate type with fallback type text.
$type = (string) isset($validate['type']) ? $validate['type'] : $field['type'];
$method = 'type'.strtr($type, '-', '_');
// If value isn't required, we will stop validation if empty value is given.
if ((empty($validate['required']) || (isset($validate['required']) && $validate['required'] !== true)) && ($value === null || $value === '' || (($field['type'] === 'checkbox' || $field['type'] === 'switch') && $value == false))) {
return $messages;
}
if (!isset($field['type'])) {
$field['type'] = 'text';
}
$validate = (array)($field['validate'] ?? null);
$type = $validate['type'] ?? $field['type'];
$required = $validate['required'] ?? false;
// If value isn't required, we will stop validation if empty value is given.
if ($required !== true && ($value === null || $value === '' || (($field['type'] === 'checkbox' || $field['type'] === 'switch') && $value == false))
) {
return [];
}
// Get language class.
$language = Grav::instance()['language'];
$name = ucfirst(isset($field['label']) ? $field['label'] : $field['name']);
$name = ucfirst($field['label'] ?? $field['name']);
$message = (string) isset($field['validate']['message'])
? $language->translate($field['validate']['message'])
: $language->translate('FORM.INVALID_INPUT', null, true) . ' "' . $language->translate($name) . '"';
: $language->translate('GRAV.FORM.INVALID_INPUT', null, true) . ' "' . $language->translate($name) . '"';
// Validate type with fallback type text.
$method = 'type' . str_replace('-', '_', $type);
// If this is a YAML field validate/filter as such
if ($type != 'yaml' && isset($field['yaml']) && $field['yaml'] === true) {
if (isset($field['yaml']) && $field['yaml'] === true) {
$method = 'typeYaml';
}
if (method_exists(__CLASS__, $method)) {
$success = self::$method($value, $validate, $field);
} else {
$success = true;
}
$messages = [];
$success = method_exists(__CLASS__, $method) ? self::$method($value, $validate, $field) : true;
if (!$success) {
$messages[$field['name']][] = $message;
}
// Check individual rules.
foreach ($validate as $rule => $params) {
$method = 'validate' . ucfirst(strtr($rule, '-', '_'));
$method = 'validate' . ucfirst(str_replace('-', '_', $rule));
if (method_exists(__CLASS__, $method)) {
$success = self::$method($value, $params);
@@ -89,29 +87,27 @@ class Validation
*/
public static function filter($value, array $field)
{
$validate = isset($field['validate']) ? (array) $field['validate'] : [];
$validate = (array)($field['filter'] ?? $field['validate'] ?? null);
// If value isn't required, we will return null if empty value is given.
if (empty($validate['required']) && ($value === null || $value === '')) {
if (($value === null || $value === '') && empty($validate['required'])) {
return null;
}
if (!isset($field['type'])) {
$field['type'] = 'text';
}
$type = $field['filter']['type'] ?? $field['validate']['type'] ?? $field['type'];
// Validate type with fallback type text.
$type = (string) isset($field['validate']['type']) ? $field['validate']['type'] : $field['type'];
$method = 'filter' . ucfirst(strtr($type, '-', '_'));
$method = 'filter' . ucfirst(str_replace('-', '_', $type));
// If this is a YAML field validate/filter as such
if ($type !== 'yaml' && isset($field['yaml']) && $field['yaml'] === true) {
if (isset($field['yaml']) && $field['yaml'] === true) {
$method = 'filterYaml';
}
if (!method_exists(__CLASS__, $method)) {
$method = 'filterText';
$method = isset($field['array']) && $field['array'] === true ? 'filterArray' : 'filterText';
}
return self::$method($value, $validate, $field);
@@ -127,22 +123,26 @@ class Validation
*/
public static function typeText($value, array $params, array $field)
{
if (!is_string($value) && !is_numeric($value)) {
if (!\is_string($value) && !is_numeric($value)) {
return false;
}
$value = (string)$value;
if (isset($params['min']) && strlen($value) < $params['min']) {
if (!empty($params['trim'])) {
$value = trim($value);
}
if (isset($params['min']) && \strlen($value) < $params['min']) {
return false;
}
if (isset($params['max']) && strlen($value) > $params['max']) {
if (isset($params['max']) && \strlen($value) > $params['max']) {
return false;
}
$min = isset($params['min']) ? $params['min'] : 0;
if (isset($params['step']) && (strlen($value) - $min) % $params['step'] == 0) {
$min = $params['min'] ?? 0;
if (isset($params['step']) && (\strlen($value) - $min) % $params['step'] === 0) {
return false;
}
@@ -155,17 +155,30 @@ class Validation
protected static function filterText($value, array $params, array $field)
{
if (!\is_string($value) && !is_numeric($value)) {
return '';
}
if (!empty($params['trim'])) {
$value = trim($value);
}
return (string) $value;
}
protected static function filterCheckbox($value, array $params, array $field)
{
return (bool) $value;
}
protected static function filterCommaList($value, array $params, array $field)
{
return is_array($value) ? $value : preg_split('/\s*,\s*/', $value, -1, PREG_SPLIT_NO_EMPTY);
return \is_array($value) ? $value : preg_split('/\s*,\s*/', $value, -1, PREG_SPLIT_NO_EMPTY);
}
public static function typeCommaList($value, array $params, array $field)
{
return is_array($value) ? true : self::typeText($value, $params, $field);
return \is_array($value) ? true : self::typeText($value, $params, $field);
}
protected static function filterLower($value, array $params)
@@ -234,6 +247,7 @@ class Validation
{
// Set multiple: true so checkboxes can easily use min/max counts to control number of options required
$field['multiple'] = true;
return self::typeArray((array) $value, $params, $field);
}
@@ -252,16 +266,10 @@ class Validation
*/
public static function typeCheckbox($value, array $params, array $field)
{
$value = (string) $value;
$value = (string)$value;
$field_value = (string)($field['value'] ?? '1');
if (!isset($field['value'])) {
$field['value'] = 1;
}
if (isset($value) && $value != $field['value']) {
return false;
}
return true;
return $value === $field_value;
}
/**
@@ -287,6 +295,10 @@ class Validation
*/
public static function typeToggle($value, array $params, array $field)
{
if (\is_bool($value)) {
$value = (int)$value;
}
return self::typeArray((array) $value, $params, $field);
}
@@ -300,12 +312,12 @@ class Validation
*/
public static function typeFile($value, array $params, array $field)
{
return self::typeArray((array) $value, $params, $field);
return self::typeArray((array)$value, $params, $field);
}
protected static function filterFile($value, array $params, array $field)
{
return (array) $value;
return (array)$value;
}
/**
@@ -343,12 +355,9 @@ class Validation
return false;
}
$min = isset($params['min']) ? $params['min'] : 0;
if (isset($params['step']) && fmod($value - $min, $params['step']) == 0) {
return false;
}
$min = $params['min'] ?? 0;
return true;
return !(isset($params['step']) && fmod($value - $min, $params['step']) === 0);
}
protected static function filterNumber($value, array $params, array $field)
@@ -408,10 +417,10 @@ class Validation
*/
public static function typeEmail($value, array $params, array $field)
{
$values = !is_array($value) ? explode(',', preg_replace('/\s+/', '', $value)) : $value;
$values = !\is_array($value) ? explode(',', preg_replace('/\s+/', '', $value)) : $value;
foreach ($values as $value) {
if (!(self::typeText($value, $params, $field) && filter_var($value, FILTER_VALIDATE_EMAIL))) {
foreach ($values as $val) {
if (!(self::typeText($val, $params, $field) && filter_var($val, FILTER_VALIDATE_EMAIL))) {
return false;
}
}
@@ -445,9 +454,11 @@ class Validation
{
if ($value instanceof \DateTime) {
return true;
} elseif (!is_string($value)) {
}
if (!\is_string($value)) {
return false;
} elseif (!isset($params['format'])) {
}
if (!isset($params['format'])) {
return false !== strtotime($value);
}
@@ -479,10 +490,10 @@ class Validation
*/
public static function typeDate($value, array $params, array $field)
{
$params = array($params);
if (!isset($params['format'])) {
$params['format'] = 'Y-m-d';
}
return self::typeDatetime($value, $params, $field);
}
@@ -496,10 +507,10 @@ class Validation
*/
public static function typeTime($value, array $params, array $field)
{
$params = array($params);
if (!isset($params['format'])) {
$params['format'] = 'H:i';
}
return self::typeDatetime($value, $params, $field);
}
@@ -513,10 +524,10 @@ class Validation
*/
public static function typeMonth($value, array $params, array $field)
{
$params = array($params);
if (!isset($params['format'])) {
$params['format'] = 'Y-m';
}
return self::typeDatetime($value, $params, $field);
}
@@ -533,6 +544,7 @@ class Validation
if (!isset($params['format']) && !preg_match('/^\d{4}-W\d{2}$/u', $value)) {
return false;
}
return self::typeDatetime($value, $params, $field);
}
@@ -546,72 +558,85 @@ class Validation
*/
public static function typeArray($value, array $params, array $field)
{
if (!is_array($value)) {
if (!\is_array($value)) {
return false;
}
if (isset($field['multiple'])) {
if (isset($params['min']) && count($value) < $params['min']) {
if (isset($params['min']) && \count($value) < $params['min']) {
return false;
}
if (isset($params['max']) && count($value) > $params['max']) {
if (isset($params['max']) && \count($value) > $params['max']) {
return false;
}
$min = isset($params['min']) ? $params['min'] : 0;
if (isset($params['step']) && (count($value) - $min) % $params['step'] == 0) {
$min = $params['min'] ?? 0;
if (isset($params['step']) && (\count($value) - $min) % $params['step'] === 0) {
return false;
}
}
$options = isset($field['options']) ? array_keys($field['options']) : array();
$values = isset($field['use']) && $field['use'] == 'keys' ? array_keys($value) : $value;
if ($options && array_diff($values, $options)) {
return false;
// If creating new values is allowed, no further checks are needed.
if (!empty($field['selectize']['create'])) {
return true;
}
return true;
$options = $field['options'] ?? [];
$use = $field['use'] ?? 'values';
if (empty($field['selectize']) || empty($field['multiple'])) {
$options = array_keys($options);
}
if ($use === 'keys') {
$value = array_keys($value);
}
return !($options && array_diff($value, $options));
}
protected static function filterArray($value, $params, $field)
{
$values = (array) $value;
$options = isset($field['options']) ? array_keys($field['options']) : array();
$multi = isset($field['multiple']) ? $field['multiple'] : false;
$options = isset($field['options']) ? array_keys($field['options']) : [];
$multi = $field['multiple'] ?? false;
if (count($values) == 1 && isset($values[0]) && $values[0] == '') {
if (\count($values) === 1 && isset($values[0]) && $values[0] === '') {
return null;
}
if ($options) {
$useKey = isset($field['use']) && $field['use'] == 'keys';
foreach ($values as $key => $value) {
$values[$key] = $useKey ? (bool) $value : $value;
$useKey = isset($field['use']) && $field['use'] === 'keys';
foreach ($values as $key => $val) {
$values[$key] = $useKey ? (bool) $val : $val;
}
}
if ($multi) {
foreach ($values as $key => $value) {
if (is_array($value)) {
$value = implode(',', $value);
$values[$key] = array_map('trim', explode(',', $value));
foreach ($values as $key => $val) {
if (\is_array($val)) {
$val = implode(',', $val);
$values[$key] = array_map('trim', explode(',', $val));
} else {
$values[$key] = trim($value);
$values[$key] = trim($val);
}
}
}
if (isset($field['ignore_empty']) && Utils::isPositive($field['ignore_empty'])) {
foreach ($values as $key => $value) {
foreach ($value as $inner_key => $inner_value) {
if ($inner_value == '') {
unset($value[$inner_key]);
foreach ($values as $key => $val) {
if ($val === '') {
unset($values[$key]);
} elseif (\is_array($val)) {
foreach ($val as $inner_key => $inner_value) {
if ($inner_value === '') {
unset($val[$inner_key]);
}
}
}
$values[$key] = $value;
$values[$key] = $val;
}
}
@@ -620,7 +645,7 @@ class Validation
public static function typeList($value, array $params, array $field)
{
if (!is_array($value)) {
if (!\is_array($value)) {
return false;
}
@@ -628,7 +653,7 @@ class Validation
foreach ($value as $key => $item) {
foreach ($field['fields'] as $subKey => $subField) {
$subKey = trim($subKey, '.');
$subValue = isset($item[$subKey]) ? $item[$subKey] : null;
$subValue = $item[$subKey] ?? null;
self::validate($subValue, $subField);
}
}
@@ -644,7 +669,7 @@ class Validation
public static function filterYaml($value, $params)
{
if (!is_string($value)) {
if (!\is_string($value)) {
return $value;
}
@@ -670,6 +695,23 @@ class Validation
return $value;
}
/**
* Input value which can be ignored.
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeUnset($value, array $params, array $field)
{
return true;
}
public static function filterUnset($value, array $params, array $field)
{
return null;
}
// HTML5 attributes (min, max and range are handled inside the types)
@@ -677,9 +719,9 @@ class Validation
{
if (is_scalar($value)) {
return (bool) $params !== true || $value !== '';
} else {
return (bool) $params !== true || !empty($value);
}
return (bool) $params !== true || !empty($value);
}
public static function validatePattern($value, $params)
@@ -702,12 +744,12 @@ class Validation
public static function typeBool($value, $params)
{
return is_bool($value) || $value == 1 || $value == 0;
return \is_bool($value) || $value == 1 || $value == 0;
}
public static function validateBool($value, $params)
{
return is_bool($value) || $value == 1 || $value == 0;
return \is_bool($value) || $value == 1 || $value == 0;
}
protected static function filterBool($value, $params)
@@ -722,7 +764,7 @@ class Validation
public static function validateFloat($value, $params)
{
return is_float(filter_var($value, FILTER_VALIDATE_FLOAT));
return \is_float(filter_var($value, FILTER_VALIDATE_FLOAT));
}
protected static function filterFloat($value, $params)
@@ -737,20 +779,17 @@ class Validation
public static function validateInt($value, $params)
{
return is_numeric($value) && (int) $value == $value;
return is_numeric($value) && (int)$value == $value;
}
protected static function filterInt($value, $params)
{
return (int) $value;
return (int)$value;
}
public static function validateArray($value, $params)
{
return is_array($value)
|| ($value instanceof \ArrayAccess
&& $value instanceof \Traversable
&& $value instanceof \Countable);
return \is_array($value) || ($value instanceof \ArrayAccess && $value instanceof \Traversable && $value instanceof \Countable);
}
public static function filterItem_List($value, $params)
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.Data
* @package Grav\Common\Data
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -18,7 +19,7 @@ class ValidationException extends \RuntimeException
$this->messages = $messages;
$language = Grav::instance()['language'];
$this->message = $language->translate('FORM.VALIDATION_FAIL', null, true) . ' ' . $this->message;
$this->message = $language->translate('GRAV.FORM.VALIDATION_FAIL', null, true) . ' ' . $this->message;
foreach ($messages as $variable => &$list) {
$list = array_unique($list);
+239 -76
View File
@@ -1,18 +1,29 @@
<?php
/**
* @package Grav.Common
* @package Grav\Common
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common;
use DebugBar\DataCollector\ConfigCollector;
use DebugBar\DataCollector\DataCollectorInterface;
use DebugBar\DataCollector\ExceptionsCollector;
use DebugBar\DataCollector\MemoryCollector;
use DebugBar\DataCollector\MessagesCollector;
use DebugBar\DataCollector\PhpInfoCollector;
use DebugBar\DataCollector\RequestDataCollector;
use DebugBar\DataCollector\TimeDataCollector;
use DebugBar\DebugBar;
use DebugBar\JavascriptRenderer;
use DebugBar\StandardDebugBar;
use Grav\Common\Config\Config;
use Grav\Common\Processors\ProcessorInterface;
use Twig\Template;
use Twig\TemplateWrapper;
class Debugger
{
@@ -28,13 +39,18 @@ class Debugger
/** @var StandardDebugBar $debugbar */
protected $debugbar;
/** @var bool */
protected $enabled;
protected $initialized = false;
/** @var array */
protected $timers = [];
/** @var string[] $deprecations */
/** @var array $deprecations */
protected $deprecations = [];
/** @var callable */
protected $errorHandler;
/**
@@ -42,11 +58,26 @@ class Debugger
*/
public function __construct()
{
$currentTime = microtime(true);
if (!\defined('GRAV_REQUEST_TIME')) {
\define('GRAV_REQUEST_TIME', $currentTime);
}
// Enable debugger until $this->init() gets called.
$this->enabled = true;
$this->debugbar = new StandardDebugBar();
$this->debugbar['time']->addMeasure('Loading', $this->debugbar['time']->getRequestStartTime(), microtime(true));
$debugbar = new DebugBar();
$debugbar->addCollector(new PhpInfoCollector());
$debugbar->addCollector(new MessagesCollector());
$debugbar->addCollector(new RequestDataCollector());
$debugbar->addCollector(new TimeDataCollector($_SERVER['REQUEST_TIME_FLOAT'] ?? GRAV_REQUEST_TIME));
$debugbar['time']->addMeasure('Server', $debugbar['time']->getRequestStartTime(), GRAV_REQUEST_TIME);
$debugbar['time']->addMeasure('Loading', GRAV_REQUEST_TIME, $currentTime);
$debugbar['time']->addMeasure('Debugger', $currentTime, microtime(true));
$this->debugbar = $debugbar;
// Set deprecation collector.
$this->setErrorHandler();
@@ -60,21 +91,28 @@ class Debugger
*/
public function init()
{
if ($this->initialized) {
return $this;
}
$this->grav = Grav::instance();
$this->config = $this->grav['config'];
// Enable/disable debugger based on configuration.
$this->enabled = $this->config->get('system.debugger.enabled');
$this->enabled = (bool)$this->config->get('system.debugger.enabled');
if ($this->enabled()) {
$this->initialized = true;
$plugins_config = (array)$this->config->get('plugins');
ksort($plugins_config);
$this->debugbar->addCollector(new ConfigCollector((array)$this->config->get('system'), 'Config'));
$this->debugbar->addCollector(new ConfigCollector($plugins_config, 'Plugins'));
$debugbar = $this->debugbar;
$debugbar->addCollector(new MemoryCollector());
$debugbar->addCollector(new ExceptionsCollector());
$debugbar->addCollector(new ConfigCollector((array)$this->config->get('system'), 'Config'));
$debugbar->addCollector(new ConfigCollector($plugins_config, 'Plugins'));
$this->addMessage('Grav v' . GRAV_VERSION);
}
@@ -86,12 +124,12 @@ class Debugger
*
* @param bool $state If null, the method returns the enabled value. If set, the method sets the enabled state
*
* @return null
* @return bool
*/
public function enabled($state = null)
{
if ($state !== null) {
$this->enabled = $state;
$this->enabled = (bool)$state;
}
return $this->enabled;
@@ -147,7 +185,7 @@ class Debugger
/**
* Adds a data collector
*
* @param $collector
* @param DataCollectorInterface $collector
*
* @return $this
* @throws \DebugBar\DebugBarException
@@ -162,9 +200,9 @@ class Debugger
/**
* Returns a data collector
*
* @param $collector
* @param DataCollectorInterface $collector
*
* @return \DebugBar\DataCollector\DataCollectorInterface
* @return DataCollectorInterface
* @throws \DebugBar\DebugBarException
*/
public function getCollector($collector)
@@ -229,14 +267,14 @@ class Debugger
/**
* Start a timer with an associated name and description
*
* @param $name
* @param string $name
* @param string|null $description
*
* @return $this
*/
public function startTimer($name, $description = null)
{
if ($name[0] === '_' || $this->enabled()) {
if (strpos($name, '_') === 0 || $this->enabled()) {
$this->debugbar['time']->startMeasure($name, $description);
$this->timers[] = $name;
}
@@ -253,7 +291,7 @@ class Debugger
*/
public function stopTimer($name)
{
if (in_array($name, $this->timers, true) && ($name[0] === '_' || $this->enabled())) {
if (\in_array($name, $this->timers, true) && (strpos($name, '_') === 0 || $this->enabled())) {
$this->debugbar['time']->stopMeasure($name);
}
@@ -263,7 +301,7 @@ class Debugger
/**
* Dump variables into the Messages tab of the Debug Bar
*
* @param $message
* @param mixed $message
* @param string $label
* @param bool $isString
*
@@ -286,7 +324,7 @@ class Debugger
*/
public function addException(\Exception $e)
{
if ($this->enabled()) {
if ($this->initialized && $this->enabled()) {
$this->debugbar['exceptions']->addException($e);
}
@@ -321,57 +359,183 @@ class Debugger
return true;
}
$backtrace = debug_backtrace(false);
// Figure out error scope from the error.
$scope = 'unknown';
if (stripos($errstr, 'grav') !== false) {
$scope = 'grav';
} elseif (strpos($errfile, '/twig/') !== false) {
$scope = 'twig';
} elseif (stripos($errfile, '/yaml/') !== false) {
$scope = 'yaml';
} elseif (strpos($errfile, '/vendor/') !== false) {
$scope = 'vendor';
}
// Clean up backtrace to make it more useful.
$backtrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT);
// Skip current call.
array_shift($backtrace);
// Find yaml file where the error happened.
if ($scope === 'yaml') {
foreach ($backtrace as $current) {
if (isset($current['args'])) {
foreach ($current['args'] as $arg) {
if ($arg instanceof \SplFileInfo) {
$arg = $arg->getPathname();
}
if (\is_string($arg) && preg_match('/.+\.(yaml|md)$/i', $arg)) {
$errfile = $arg;
$errline = 0;
break 2;
}
}
}
}
}
// Filter arguments.
$cut = 0;
$previous = null;
foreach ($backtrace as $i => &$current) {
if (isset($current['args'])) {
$args = [];
foreach ($current['args'] as $arg) {
if (\is_string($arg)) {
$arg = "'" . $arg . "'";
if (mb_strlen($arg) > 100) {
$arg = 'string';
}
} elseif (\is_bool($arg)) {
$arg = $arg ? 'true' : 'false';
} elseif (\is_scalar($arg)) {
$arg = $arg;
} elseif (\is_object($arg)) {
$arg = get_class($arg) . ' $object';
} elseif (\is_array($arg)) {
$arg = '$array';
} else {
$arg = '$object';
}
$args[] = $arg;
}
$current['args'] = $args;
}
$object = $current['object'] ?? null;
unset($current['object']);
$reflection = null;
if ($object instanceof TemplateWrapper) {
$reflection = new \ReflectionObject($object);
$property = $reflection->getProperty('template');
$property->setAccessible(true);
$object = $property->getValue($object);
}
if ($object instanceof Template) {
$file = $current['file'] ?? null;
if (preg_match('`(Template.php|TemplateWrapper.php)$`', $file)) {
$current = null;
continue;
}
$debugInfo = $object->getDebugInfo();
$line = 1;
if (!$reflection) {
foreach ($debugInfo as $codeLine => $templateLine) {
if ($codeLine <= $current['line']) {
$line = $templateLine;
break;
}
}
}
$src = $object->getSourceContext();
//$code = preg_split('/\r\n|\r|\n/', $src->getCode());
//$current['twig']['twig'] = trim($code[$line - 1]);
$current['twig']['file'] = $src->getPath();
$current['twig']['line'] = $line;
$prevFile = $previous['file'] ?? null;
if ($prevFile && $file === $prevFile) {
$prevLine = $previous['line'];
$line = 1;
foreach ($debugInfo as $codeLine => $templateLine) {
if ($codeLine <= $prevLine) {
$line = $templateLine;
break;
}
}
//$previous['twig']['twig'] = trim($code[$line - 1]);
$previous['twig']['file'] = $src->getPath();
$previous['twig']['line'] = $line;
}
$cut = $i;
} elseif ($object instanceof ProcessorInterface) {
$cut = $cut ?: $i;
break;
}
$previous = &$backtrace[$i];
}
unset($current);
if ($cut) {
$backtrace = array_slice($backtrace, 0, $cut + 1);
}
$backtrace = array_values(array_filter($backtrace));
// Skip vendor libraries and the method where error was triggered.
while ($current = array_shift($backtrace)) {
if (isset($current['file']) && strpos($current['file'], 'vendor') !== false) {
foreach ($backtrace as $i => $current) {
if (!isset($current['file'])) {
continue;
}
if (strpos($current['file'], '/vendor/') !== false) {
$cut = $i + 1;
continue;
}
if (isset($current['function']) && ($current['function'] === 'user_error' || $current['function'] === 'trigger_error')) {
$current = array_shift($backtrace);
$cut = $i + 1;
continue;
}
break;
}
// Add back last call.
array_unshift($backtrace, $current);
// Filter arguments.
foreach ($backtrace as &$current) {
if (isset($current['args'])) {
$args = [];
foreach ($current['args'] as $arg) {
if (\is_string($arg)) {
$args[] = "'" . $arg . "'";
} elseif (\is_bool($arg)) {
$args[] = $arg ? 'true' : 'false';
} elseif (\is_scalar($arg)) {
$args[] = $arg;
} elseif (\is_object($arg)) {
$args[] = get_class($arg) . ' $object';
} elseif (\is_array($arg)) {
$args[] = '$array';
} else {
$args[] = '$object';
}
}
$current['args'] = $args;
}
if ($cut) {
$backtrace = array_slice($backtrace, $cut);
}
unset($current);
$backtrace = array_values(array_filter($backtrace));
$this->deprecations[] = [
$current = reset($backtrace);
// If the issue happened inside twig file, change the file and line to match that file.
$file = $current['twig']['file'] ?? '';
if ($file) {
$errfile = $file;
$errline = $current['twig']['line'] ?? 0;
}
$deprecation = [
'scope' => $scope,
'message' => $errstr,
'file' => $errfile,
'line' => $errline,
'trace' => $backtrace,
'count' => 1
];
$this->deprecations[] = $deprecation;
// Do not pass forward.
return true;
}
@@ -396,38 +560,37 @@ class Debugger
protected function getDepracatedMessage($deprecated)
{
$scope = 'unknown';
if (stripos($deprecated['message'], 'grav') !== false) {
$scope = 'grav';
} elseif (!isset($deprecated['file'])) {
$scope = 'unknown';
} elseif (stripos($deprecated['file'], 'twig') !== false) {
$scope = 'twig';
} elseif (stripos($deprecated['file'], 'yaml') !== false) {
$scope = 'yaml';
} elseif (stripos($deprecated['file'], 'vendor') !== false) {
$scope = 'vendor';
}
$scope = $deprecated['scope'];
$trace = [];
foreach ($deprecated['trace'] as $current) {
$class = isset($current['class']) ? $current['class'] : '';
$type = isset($current['type']) ? $current['type'] : '';
$function = $this->getFunction($current);
if (isset($current['file'])) {
$current['file'] = str_replace(GRAV_ROOT . '/', '', $current['file']);
if (isset($deprecated['trace'])) {
foreach ($deprecated['trace'] as $current) {
$class = $current['class'] ?? '';
$type = $current['type'] ?? '';
$function = $this->getFunction($current);
if (isset($current['file'])) {
$current['file'] = str_replace(GRAV_ROOT . '/', '', $current['file']);
}
unset($current['class'], $current['type'], $current['function'], $current['args']);
if (isset($current['twig'])) {
$trace[] = $current['twig'];
} else {
$trace[] = ['call' => $class . $type . $function] + $current;
}
}
unset($current['class'], $current['type'], $current['function'], $current['args']);
$trace[] = ['call' => $class . $type . $function] + $current;
}
$array = [
'message' => $deprecated['message'],
'file' => $deprecated['file'],
'line' => $deprecated['line'],
'trace' => $trace
];
return [
[
'message' => $deprecated['message'],
'trace' => $trace
],
array_filter($array),
$scope
];
}
@@ -438,6 +601,6 @@ class Debugger
return '';
}
return $trace['function'] . '(' . implode(', ', $trace['args']) . ')';
return $trace['function'] . '(' . implode(', ', $trace['args'] ?? []) . ')';
}
}
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.Errors
* @package Grav\Common\Errors
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
+8 -16
View File
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.Errors
* @package Grav\Common\Errors
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -17,11 +18,11 @@ class Errors
{
$grav = Grav::instance();
$config = $grav['config']->get('system.errors');
$jsonRequest = $_SERVER && isset($_SERVER['HTTP_ACCEPT']) && $_SERVER['HTTP_ACCEPT'] == 'application/json';
$jsonRequest = $_SERVER && isset($_SERVER['HTTP_ACCEPT']) && $_SERVER['HTTP_ACCEPT'] === 'application/json';
// Setup Whoops-based error handler
$system = new SystemFacade;
$whoops = new \Whoops\Run($system);
$whoops = new Whoops\Run($system);
$verbosity = 1;
@@ -49,17 +50,8 @@ class Errors
break;
}
if (method_exists('Whoops\Util\Misc', 'isAjaxRequest')) { //Whoops 2.0
if (Whoops\Util\Misc::isAjaxRequest() || $jsonRequest) {
$whoops->pushHandler(new Whoops\Handler\JsonResponseHandler);
}
} elseif (function_exists('Whoops\isAjaxRequest')) { //Whoops 2.0.0-alpha
if (Whoops\isAjaxRequest() || $jsonRequest) {
$whoops->pushHandler(new Whoops\Handler\JsonResponseHandler);
}
} else { //Whoops 1.x
$json_page = new Whoops\Handler\JsonResponseHandler;
$json_page->onlyForAjaxRequests(true);
if (Whoops\Util\Misc::isAjaxRequest() || $jsonRequest) {
$whoops->pushHandler(new Whoops\Handler\JsonResponseHandler);
}
if (isset($config['log']) && $config['log']) {
@@ -70,7 +62,7 @@ class Errors
} catch (\Exception $e) {
echo $e;
}
}, 'log');
});
}
$whoops->register();
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.Errors
* @package Grav\Common\Errors
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -20,7 +21,7 @@ class SimplePageHandler extends Handler
public function __construct()
{
// Add the default, local resource search path:
$this->searchPaths[] = __DIR__ . "/Resources";
$this->searchPaths[] = __DIR__ . '/Resources';
}
/**
@@ -31,8 +32,8 @@ class SimplePageHandler extends Handler
$inspector = $this->getInspector();
$helper = new TemplateHelper();
$templateFile = $this->getResource("layout.html.php");
$cssFile = $this->getResource("error.css");
$templateFile = $this->getResource('layout.html.php');
$cssFile = $this->getResource('error.css');
$code = $inspector->getException()->getCode();
if ( ($code >= 400) && ($code < 600) )
@@ -46,9 +47,9 @@ class SimplePageHandler extends Handler
}
$vars = array(
"stylesheet" => file_get_contents($cssFile),
"code" => $code,
"message" => filter_var(rawurldecode($message), FILTER_SANITIZE_STRING),
'stylesheet' => file_get_contents($cssFile),
'code' => $code,
'message' => filter_var(rawurldecode($message), FILTER_SANITIZE_STRING),
);
$helper->setVariables($vars);
@@ -58,7 +59,7 @@ class SimplePageHandler extends Handler
}
/**
* @param $resource
* @param string $resource
*
* @return string
* @throws \RuntimeException
@@ -74,7 +75,7 @@ class SimplePageHandler extends Handler
// Search through available search paths, until we find the
// resource we're after:
foreach ($this->searchPaths as $path) {
$fullPath = $path . "/$resource";
$fullPath = "{$path}/{$resource}";
if (is_file($fullPath)) {
// Cache the result:
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.Errors
* @package Grav\Common\Errors
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
+3 -2
View File
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.File
* @package Grav\Common\File
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.File
* @package Grav\Common\File
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -23,6 +24,6 @@ class CompiledJsonFile extends JsonFile
*/
protected function decode($var, $assoc = true)
{
return (array) json_decode($var, $assoc);
return (array)json_decode($var, $assoc);
}
}
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.File
* @package Grav\Common\File
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.File
* @package Grav\Common\File
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -0,0 +1,66 @@
<?php
/**
* @package Grav\Common\Filesystem
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Filesystem;
use Grav\Common\Utils;
abstract class Archiver
{
protected $options = [
'exclude_files' => ['.DS_Store'],
'exclude_paths' => []
];
protected $archive_file;
public static function create($compression)
{
if ($compression === 'zip') {
return new ZipArchiver();
}
return new ZipArchiver();
}
public function setArchive($archive_file)
{
$this->archive_file = $archive_file;
return $this;
}
public function setOptions($options)
{
// Set infinite PHP execution time if possible.
if (function_exists('set_time_limit') && !Utils::isFunctionDisabled('set_time_limit')) {
set_time_limit(0);
}
$this->options = $options + $this->options;
return $this;
}
public abstract function compress($folder, callable $status = null);
public abstract function extract($destination, callable $status = null);
public abstract function addEmptyFolders($folders, callable $status = null);
protected function getArchiveFiles($rootPath)
{
$exclude_paths = $this->options['exclude_paths'];
$exclude_files = $this->options['exclude_files'];
$dirItr = new \RecursiveDirectoryIterator($rootPath, \RecursiveDirectoryIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS | \FilesystemIterator::UNIX_PATHS);
$filterItr = new RecursiveDirectoryFilterIterator($dirItr, $rootPath, $exclude_paths, $exclude_files);
$files = new \RecursiveIteratorIterator($filterItr, \RecursiveIteratorIterator::SELF_FIRST);
return $files;
}
}
+30 -24
View File
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.FileSystem
* @package Grav\Common\Filesystem
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -48,7 +49,7 @@ abstract class Folder
/**
* Recursively find the last modified time under given path by file.
*
* @param string $path
* @param string $path
* @param string $extensions which files to search for specifically
*
* @return int
@@ -86,7 +87,7 @@ abstract class Folder
/**
* Recursively md5 hash all files in a path
*
* @param $path
* @param string $path
* @return string
*/
public static function hashAllFiles($path)
@@ -141,6 +142,7 @@ abstract class Folder
*/
public static function getRelativePathDotDot($path, $base)
{
// Normalize paths.
$base = preg_replace('![\\\/]+!', '/', $base);
$path = preg_replace('![\\\/]+!', '/', $path);
@@ -148,8 +150,8 @@ abstract class Folder
return '';
}
$baseParts = explode('/', isset($base[0]) && '/' === $base[0] ? substr($base, 1) : $base);
$pathParts = explode('/', isset($path[0]) && '/' === $path[0] ? substr($path, 1) : $path);
$baseParts = explode('/', ltrim($base, '/'));
$pathParts = explode('/', ltrim($path, '/'));
array_pop($baseParts);
$lastPart = array_pop($pathParts);
@@ -164,7 +166,7 @@ abstract class Folder
$path = str_repeat('../', count($baseParts)) . implode('/', $pathParts);
return '' === $path
|| '/' === $path[0]
|| strpos($path, '/') === 0
|| false !== ($colonPos = strpos($path, ':')) && ($colonPos < ($slashPos = strpos($path, '/')) || false === $slashPos)
? "./$path" : $path;
}
@@ -199,14 +201,14 @@ abstract class Folder
}
$compare = isset($params['compare']) ? 'get' . $params['compare'] : null;
$pattern = isset($params['pattern']) ? $params['pattern'] : null;
$filters = isset($params['filters']) ? $params['filters'] : null;
$recursive = isset($params['recursive']) ? $params['recursive'] : true;
$levels = isset($params['levels']) ? $params['levels'] : -1;
$pattern = $params['pattern'] ?? null;
$filters = $params['filters'] ?? null;
$recursive = $params['recursive'] ?? true;
$levels = $params['levels'] ?? -1;
$key = isset($params['key']) ? 'get' . $params['key'] : null;
$value = isset($params['value']) ? 'get' . $params['value'] : ($recursive ? 'getSubPathname' : 'getFilename');
$folders = isset($params['folders']) ? $params['folders'] : true;
$files = isset($params['files']) ? $params['files'] : true;
$value = 'get' . ($params['value'] ?? ($recursive ? 'SubPathname' : 'Filename'));
$folders = $params['folders'] ?? true;
$files = $params['files'] ?? true;
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
@@ -233,7 +235,7 @@ abstract class Folder
/** @var \RecursiveDirectoryIterator $file */
foreach ($iterator as $file) {
// Ignore hidden files.
if ($file->getFilename()[0] === '.') {
if (strpos($file->getFilename(), '.') === 0) {
continue;
}
if (!$folders && $file->isDir()) {
@@ -255,7 +257,7 @@ abstract class Folder
if (isset($filters['value'])) {
$filter = $filters['value'];
if (is_callable($filter)) {
$filePath = call_user_func($filter, $file);
$filePath = $filter($file);
} else {
$filePath = preg_replace($filter, '', $filePath);
}
@@ -316,7 +318,7 @@ abstract class Folder
if (!$success) {
$error = error_get_last();
throw new \RuntimeException($error['message']);
throw new \RuntimeException($error['message'] ?? 'Unknown error');
}
// Make sure that the change will be detected when caching.
@@ -416,23 +418,27 @@ abstract class Folder
*/
public static function create($folder)
{
if (is_dir($folder)) {
// Silence error for open_basedir; should fail in mkdir instead.
if (@is_dir($folder)) {
return;
}
$success = @mkdir($folder, 0777, true);
if (!$success) {
$error = error_get_last();
throw new \RuntimeException($error['message']);
// Take yet another look, make sure that the folder doesn't exist.
clearstatcache(true, $folder);
if (!@is_dir($folder)) {
throw new \RuntimeException(sprintf('Unable to create directory: %s', $folder));
}
}
}
/**
* Recursive copy of one directory to another
*
* @param $src
* @param $dest
* @param string $src
* @param string $dest
*
* @return bool
* @throws \RuntimeException
@@ -448,7 +454,7 @@ abstract class Folder
// If the destination directory does not exist create it
if (!is_dir($dest)) {
static::mkdir($dest);
static::create($dest);
}
// Open the source directory to read in files
@@ -475,7 +481,7 @@ abstract class Folder
protected static function doDelete($folder, $include_target = true)
{
// Special case for symbolic links.
if (is_link($folder)) {
if ($include_target && is_link($folder)) {
return @unlink($folder);
}
@@ -0,0 +1,67 @@
<?php
/**
* @package Grav\Common\Filesystem
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Filesystem;
class RecursiveDirectoryFilterIterator extends \RecursiveFilterIterator
{
protected static $root;
protected static $ignore_folders;
protected static $ignore_files;
/**
* Create a RecursiveFilterIterator from a RecursiveIterator
*
* @param \RecursiveIterator $iterator
* @param string $root
* @param array $ignore_folders
* @param array $ignore_files
*/
public function __construct(\RecursiveIterator $iterator, $root, $ignore_folders, $ignore_files)
{
parent::__construct($iterator);
$this::$root = $root;
$this::$ignore_folders = $ignore_folders;
$this::$ignore_files = $ignore_files;
}
/**
* Check whether the current element of the iterator is acceptable
*
* @return bool true if the current element is acceptable, otherwise false.
*/
public function accept()
{
/** @var \SplFileInfo $file */
$file = $this->current();
$filename = $file->getFilename();
$relative_filename = str_replace($this::$root . '/', '', $file->getPathname());
if ($file->isDir()) {
if (in_array($relative_filename, $this::$ignore_folders, true)) {
return false;
}
if (!in_array($filename, $this::$ignore_files, true)) {
return true;
}
} elseif ($file->isFile() && !in_array($filename, $this::$ignore_files, true)) {
return true;
}
return false;
}
public function getChildren()
{
/** @var RecursiveDirectoryFilterIterator $iterator */
$iterator = $this->getInnerIterator();
return new self($iterator->getChildren(), $this::$root, $this::$ignore_folders, $this::$ignore_files);
}
}
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.FileSystem
* @package Grav\Common\Filesystem
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -12,19 +13,23 @@ use Grav\Common\Grav;
class RecursiveFolderFilterIterator extends \RecursiveFilterIterator
{
protected static $folder_ignores;
protected static $ignore_folders;
/**
* Create a RecursiveFilterIterator from a RecursiveIterator
*
* @param \RecursiveIterator $iterator
* @param array $ignore_folders
*/
public function __construct(\RecursiveIterator $iterator)
public function __construct(\RecursiveIterator $iterator, $ignore_folders = [])
{
parent::__construct($iterator);
if (empty($this::$folder_ignores)) {
$this::$folder_ignores = Grav::instance()['config']->get('system.pages.ignore_folders');
if (empty($ignore_folders)) {
$ignore_folders = Grav::instance()['config']->get('system.pages.ignore_folders');
}
$this::$ignore_folders = $ignore_folders;
}
/**
@@ -34,12 +39,9 @@ class RecursiveFolderFilterIterator extends \RecursiveFilterIterator
*/
public function accept()
{
/** @var $current \SplFileInfo */
/** @var \SplFileInfo $current */
$current = $this->current();
if ($current->isDir() && !in_array($current->getFilename(), $this::$folder_ignores, true)) {
return true;
}
return false;
return $current->isDir() && !in_array($current->getFilename(), $this::$ignore_folders, true);
}
}
@@ -0,0 +1,111 @@
<?php
/**
* @package Grav\Common\Filesystem
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Filesystem;
class ZipArchiver extends Archiver
{
public function extract($destination, callable $status = null)
{
$zip = new \ZipArchive();
$archive = $zip->open($this->archive_file);
if ($archive === true) {
Folder::create($destination);
if (!$zip->extractTo($destination)) {
throw new \RuntimeException('ZipArchiver: ZIP failed to extract ' . $this->archive_file . ' to ' . $destination);
}
$zip->close();
return $this;
}
throw new \RuntimeException('ZipArchiver: Failed to open ' . $this->archive_file);
}
public function compress($source, callable $status = null)
{
if (!extension_loaded('zip')) {
throw new \InvalidArgumentException('ZipArchiver: Zip PHP module not installed...');
}
if (!file_exists($source)) {
throw new \InvalidArgumentException('ZipArchiver: ' . $source . ' cannot be found...');
}
$zip = new \ZipArchive();
if (!$zip->open($this->archive_file, \ZipArchive::CREATE)) {
throw new \InvalidArgumentException('ZipArchiver:' . $this->archive_file . ' cannot be created...');
}
// Get real path for our folder
$rootPath = realpath($source);
$files = $this->getArchiveFiles($rootPath);
$status && $status([
'type' => 'count',
'steps' => iterator_count($files),
]);
foreach ($files as $file) {
$filePath = $file->getPathname();
$relativePath = ltrim(substr($filePath, strlen($rootPath)), '/');
if ($file->isDir()) {
$zip->addEmptyDir($relativePath);
} else {
$zip->addFile($filePath, $relativePath);
}
$status && $status([
'type' => 'progress',
]);
}
$status && $status([
'type' => 'message',
'message' => 'Compressing...'
]);
$zip->close();
return $this;
}
public function addEmptyFolders($folders, callable $status = null)
{
if (!extension_loaded('zip')) {
throw new \InvalidArgumentException('ZipArchiver: Zip PHP module not installed...');
}
$zip = new \ZipArchive();
if (!$zip->open($this->archive_file)) {
throw new \InvalidArgumentException('ZipArchiver: ' . $this->archive_file . ' cannot be opened...');
}
$status && $status([
'type' => 'message',
'message' => 'Adding empty folders...'
]);
foreach($folders as $folder) {
$zip->addEmptyDir($folder);
$status && $status([
'type' => 'progress',
]);
}
$zip->close();
return $this;
}
}
+101
View File
@@ -0,0 +1,101 @@
<?php
/**
* @package Grav\Common\Form
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Form;
use Grav\Common\Filesystem\Folder;
use Grav\Framework\Form\FormFlash as FrameworkFormFlash;
class FormFlash extends FrameworkFormFlash
{
/**
* @return array
* @deprecated 1.6 For backwards compatibility only, do not use
*/
public function getLegacyFiles(): array
{
$fields = [];
foreach ($this->files as $field => $files) {
if (strpos($field, '/')) {
continue;
}
foreach ($files as $file) {
if (\is_array($file)) {
$file['tmp_name'] = $this->getTmpDir() . '/' . $file['tmp_name'];
$fields[$field][$file['path'] ?? $file['name']] = $file;
}
}
}
return $fields;
}
/**
* @param string $field
* @param string $filename
* @param array $upload
* @return bool
* @deprecated 1.6 For backwards compatibility only, do not use
*/
public function uploadFile(string $field, string $filename, array $upload): bool
{
if (!$this->uniqueId) {
return false;
}
$tmp_dir = $this->getTmpDir();
Folder::create($tmp_dir);
$tmp_file = $upload['file']['tmp_name'];
$basename = basename($tmp_file);
if (!move_uploaded_file($tmp_file, $tmp_dir . '/' . $basename)) {
return false;
}
$upload['file']['tmp_name'] = $basename;
$upload['file']['name'] = $filename;
$this->addFileInternal($field, $filename, $upload['file']);
return true;
}
/**
* @param string $field
* @param string $filename
* @param array $upload
* @param array $crop
* @return bool
* @deprecated 1.6 For backwards compatibility only, do not use
*/
public function cropFile(string $field, string $filename, array $upload, array $crop): bool
{
if (!$this->uniqueId) {
return false;
}
$tmp_dir = $this->getTmpDir();
Folder::create($tmp_dir);
$tmp_file = $upload['file']['tmp_name'];
$basename = basename($tmp_file);
if (!move_uploaded_file($tmp_file, $tmp_dir . '/' . $basename)) {
return false;
}
$upload['file']['tmp_name'] = $basename;
$upload['file']['name'] = $filename;
$this->addFileInternal($field, $filename, $upload['file'], $crop);
return true;
}
}
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.GPM
* @package Grav\Common\GPM
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -14,13 +15,7 @@ abstract class AbstractCollection extends Iterator
{
public function toJson()
{
$items = [];
foreach ($this->items as $name => $package) {
$items[$name] = $package->toArray();
}
return json_encode($items);
return json_encode($this->toArray());
}
public function toArray()
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.GPM
* @package Grav\Common\GPM
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.GPM
* @package Grav\Common\GPM
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -10,18 +11,22 @@ namespace Grav\Common\GPM\Common;
use Grav\Common\Iterator;
class CachedCollection extends Iterator {
class CachedCollection extends Iterator
{
protected static $cache;
public function __construct($items)
{
parent::__construct();
$method = static::class . __METHOD__;
// local cache to speed things up
if (!isset(self::$cache[get_called_class().__METHOD__])) {
self::$cache[get_called_class().__METHOD__] = $items;
if (!isset(self::$cache[$method])) {
self::$cache[$method] = $items;
}
foreach (self::$cache[get_called_class().__METHOD__] as $name => $item) {
foreach (self::$cache[$method] as $name => $item) {
$this->append([$name => $item]);
}
}
+34 -13
View File
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.GPM
* @package Grav\Common\GPM
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -10,11 +11,15 @@ namespace Grav\Common\GPM\Common;
use Grav\Common\Data\Data;
class Package {
class Package
{
/**
* @var Data
*/
protected $data;
public function __construct(Data $package, $type = null) {
public function __construct(Data $package, $type = null)
{
$this->data = $package;
if ($type) {
@@ -22,28 +27,44 @@ class Package {
}
}
public function getData() {
/**
* @return Data
*/
public function getData()
{
return $this->data;
}
public function __get($key) {
public function __get($key)
{
return $this->data->get($key);
}
public function __isset($key) {
return isset($this->data->$key);
public function __set($key, $value)
{
return $this->data->set($key, $value);
}
public function __toString() {
public function __isset($key)
{
return isset($this->data->{$key});
}
public function __toString()
{
return $this->toJson();
}
public function toJson() {
public function toJson()
{
return $this->data->toJson();
}
public function toArray() {
/**
* @return array
*/
public function toArray()
{
return $this->data->toArray();
}
}
+104 -97
View File
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.GPM
* @package Grav\Common\GPM
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -36,7 +37,7 @@ class GPM extends Iterator
/**
* Internal cache
* @var
* @var array
*/
protected $cache;
@@ -48,11 +49,13 @@ class GPM extends Iterator
/**
* Creates a new GPM instance with Local and Remote packages available
* @param boolean $refresh Applies to Remote Packages only and forces a refetch of data
* @param bool $refresh Applies to Remote Packages only and forces a refetch of data
* @param callable $callback Either a function or callback in array notation
*/
public function __construct($refresh = false, $callback = null)
{
parent::__construct();
$this->cache = [];
$this->installed = new Local\Packages();
try {
$this->repository = new Remote\Packages($refresh, $callback);
@@ -94,7 +97,7 @@ class GPM extends Iterator
/**
* Returns the amount of locally installed packages
* @return integer Amount of installed packages
* @return int Amount of installed packages
*/
public function countInstalled()
{
@@ -144,7 +147,7 @@ class GPM extends Iterator
/**
* Checks if a Plugin is installed
* @param string $slug The slug of the Plugin
* @return boolean True if the Plugin has been installed. False otherwise
* @return bool True if the Plugin has been installed. False otherwise
*/
public function isPluginInstalled($slug)
{
@@ -178,7 +181,7 @@ class GPM extends Iterator
/**
* Checks if a Theme is installed
* @param string $slug The slug of the Theme
* @return boolean True if the Theme has been installed. False otherwise
* @return bool True if the Theme has been installed. False otherwise
*/
public function isThemeInstalled($slug)
{
@@ -187,7 +190,7 @@ class GPM extends Iterator
/**
* Returns the amount of updates available
* @return integer Amount of available updates
* @return int Amount of available updates
*/
public function countUpdates()
{
@@ -242,13 +245,12 @@ class GPM extends Iterator
continue;
}
$local_version = $plugin->version ? $plugin->version : 'Unknown';
$local_version = $plugin->version ?: 'Unknown';
$remote_version = $repository[$slug]->version;
if (version_compare($local_version, $remote_version) < 0) {
$repository[$slug]->available = $remote_version;
$repository[$slug]->version = $local_version;
$repository[$slug]->name = $repository[$slug]->name;
$repository[$slug]->type = $repository[$slug]->release_type;
$items[$slug] = $repository[$slug];
}
@@ -262,7 +264,7 @@ class GPM extends Iterator
/**
* Get the latest release of a package from the GPM
*
* @param $package_name
* @param string $package_name
*
* @return string|null
*/
@@ -285,7 +287,7 @@ class GPM extends Iterator
/**
* Check if a Plugin or Theme is updatable
* @param string $slug The slug of the package
* @return boolean True if updatable. False otherwise or if not found
* @return bool True if updatable. False otherwise or if not found
*/
public function isUpdatable($slug)
{
@@ -295,7 +297,7 @@ class GPM extends Iterator
/**
* Checks if a Plugin is updatable
* @param string $plugin The slug of the Plugin
* @return boolean True if the Plugin is updatable. False otherwise
* @return bool True if the Plugin is updatable. False otherwise
*/
public function isPluginUpdatable($plugin)
{
@@ -322,7 +324,7 @@ class GPM extends Iterator
continue;
}
$local_version = $plugin->version ? $plugin->version : 'Unknown';
$local_version = $plugin->version ?: 'Unknown';
$remote_version = $repository[$slug]->version;
if (version_compare($local_version, $remote_version) < 0) {
@@ -341,7 +343,7 @@ class GPM extends Iterator
/**
* Checks if a Theme is Updatable
* @param string $theme The slug of the Theme
* @return boolean True if the Theme is updatable. False otherwise
* @return bool True if the Theme is updatable. False otherwise
*/
public function isThemeUpdatable($theme)
{
@@ -351,7 +353,7 @@ class GPM extends Iterator
/**
* Get the release type of a package (stable / testing)
*
* @param $package_name
* @param string $package_name
*
* @return string|null
*/
@@ -374,9 +376,9 @@ class GPM extends Iterator
/**
* Returns true if the package latest release is stable
*
* @param $package_name
* @param string $package_name
*
* @return boolean
* @return bool
*/
public function isStableRelease($package_name)
{
@@ -386,9 +388,9 @@ class GPM extends Iterator
/**
* Returns true if the package latest release is testing
*
* @param $package_name
* @param string $package_name
*
* @return boolean
* @return bool
*/
public function isTestingRelease($package_name)
{
@@ -503,8 +505,8 @@ class GPM extends Iterator
/**
* Download the zip package via the URL
*
* @param $package_file
* @param $tmp
* @param string $package_file
* @param string $tmp
* @return null|string
*/
public static function downloadPackage($package_file, $tmp)
@@ -519,7 +521,7 @@ class GPM extends Iterator
$output = Response::get($package_file, []);
if ($output) {
Folder::mkdir($tmp);
Folder::create($tmp);
file_put_contents($tmp . DS . $filename, $output);
return $tmp . DS . $filename;
}
@@ -530,8 +532,8 @@ class GPM extends Iterator
/**
* Copy the local zip package to tmp
*
* @param $package_file
* @param $tmp
* @param string $package_file
* @param string $tmp
* @return null|string
*/
public static function copyPackage($package_file, $tmp)
@@ -540,7 +542,7 @@ class GPM extends Iterator
if (file_exists($package_file)) {
$filename = basename($package_file);
Folder::mkdir($tmp);
Folder::create($tmp);
copy(realpath($package_file), $tmp . DS . $filename);
return $tmp . DS . $filename;
}
@@ -551,7 +553,7 @@ class GPM extends Iterator
/**
* Try to guess the package type from the source files
*
* @param $source
* @param string $source
* @return bool|string
*/
public static function getPackageType($source)
@@ -564,44 +566,46 @@ class GPM extends Iterator
file_exists($source . 'system/config/system.yaml')
) {
return 'grav';
} else {
// must have a blueprint
if (!file_exists($source . 'blueprints.yaml')) {
return false;
}
}
// either theme or plugin
$name = basename($source);
if (Utils::contains($name, 'theme')) {
return 'theme';
} elseif (Utils::contains($name, 'plugin')) {
return 'plugin';
}
foreach (glob($source . "*.php") as $filename) {
$contents = file_get_contents($filename);
if (preg_match($theme_regex, $contents)) {
return 'theme';
} elseif (preg_match($plugin_regex, $contents)) {
return 'plugin';
}
}
// must have a blueprint
if (!file_exists($source . 'blueprints.yaml')) {
return false;
}
// Assume it's a theme
// either theme or plugin
$name = basename($source);
if (Utils::contains($name, 'theme')) {
return 'theme';
}
if (Utils::contains($name, 'plugin')) {
return 'plugin';
}
foreach (glob($source . '*.php') as $filename) {
$contents = file_get_contents($filename);
if (preg_match($theme_regex, $contents)) {
return 'theme';
}
if (preg_match($plugin_regex, $contents)) {
return 'plugin';
}
}
// Assume it's a theme
return 'theme';
}
/**
* Try to guess the package name from the source files
*
* @param $source
* @param string $source
* @return bool|string
*/
public static function getPackageName($source)
{
$ignore_yaml_files = ['blueprints', 'languages'];
foreach (glob($source . "*.yaml") as $filename) {
foreach (glob($source . '*.yaml') as $filename) {
$name = strtolower(basename($filename, '.yaml'));
if (in_array($name, $ignore_yaml_files)) {
continue;
@@ -614,7 +618,7 @@ class GPM extends Iterator
/**
* Find/Parse the blueprint file
*
* @param $source
* @param string $source
* @return array|bool
*/
public static function getBlueprints($source)
@@ -634,15 +638,15 @@ class GPM extends Iterator
/**
* Get the install path for a name and a particular type of package
*
* @param $type
* @param $name
* @param string $type
* @param string $name
* @return string
*/
public static function getInstallPath($type, $name)
{
$locator = Grav::instance()['locator'];
if ($type == 'theme') {
if ($type === 'theme') {
$install_path = $locator->findResource('themes://', false) . DS . $name;
} else {
$install_path = $locator->findResource('plugins://', false) . DS . $name;
@@ -692,7 +696,7 @@ class GPM extends Iterator
}
$not_found = new \stdClass();
$not_found->name = $inflector->camelize($search);
$not_found->name = $inflector::camelize($search);
$not_found->slug = $search;
$not_found->package_type = $type;
$not_found->install_path = str_replace('%name%', $search, $this->install_paths[$type]);
@@ -726,7 +730,7 @@ class GPM extends Iterator
$dependency = $dependency['name'];
}
if ($dependency == $slug) {
if ($dependency === $slug) {
$dependent_packages[] = $package_name;
}
}
@@ -740,8 +744,8 @@ class GPM extends Iterator
/**
* Get the required version of a dependency of a package
*
* @param $package_slug
* @param $dependency_slug
* @param string $package_slug
* @param string $dependency_slug
*
* @return mixed
*/
@@ -766,7 +770,7 @@ class GPM extends Iterator
* @param array $ignore_packages_list
*
* @return bool
* @throws \Exception
* @throws \RuntimeException
*/
public function checkNoOtherPackageNeedsThisDependencyInALowerVersion(
$slug,
@@ -789,8 +793,8 @@ class GPM extends Iterator
$compatible = $this->checkNextSignificantReleasesAreCompatible($version,
$other_dependency_version);
if (!$compatible) {
if (!in_array($dependent_package, $ignore_packages_list)) {
throw new \Exception("Package <cyan>$slug</cyan> is required in an older version by package <cyan>$dependent_package</cyan>. This package needs a newer version, and because of this it cannot be installed. The <cyan>$dependent_package</cyan> package must be updated to use a newer release of <cyan>$slug</cyan>.",
if (!in_array($dependent_package, $ignore_packages_list, true)) {
throw new \RuntimeException("Package <cyan>$slug</cyan> is required in an older version by package <cyan>$dependent_package</cyan>. This package needs a newer version, and because of this it cannot be installed. The <cyan>$dependent_package</cyan> package must be updated to use a newer release of <cyan>$slug</cyan>.",
2);
}
}
@@ -804,7 +808,7 @@ class GPM extends Iterator
/**
* Check the passed packages list can be updated
*
* @param $packages_names_list
* @param array $packages_names_list
*
* @throws \Exception
*/
@@ -833,36 +837,36 @@ class GPM extends Iterator
{
$dependencies = $this->calculateMergedDependenciesOfPackages($packages);
foreach ($dependencies as $dependency_slug => $dependencyVersionWithOperator) {
if (in_array($dependency_slug, $packages)) {
if (\in_array($dependency_slug, $packages, true)) {
unset($dependencies[$dependency_slug]);
continue;
}
// Check PHP version
if ($dependency_slug == 'php') {
if ($dependency_slug === 'php') {
$current_php_version = phpversion();
if (version_compare($this->calculateVersionNumberFromDependencyVersion($dependencyVersionWithOperator),
$current_php_version) === 1
) {
//Needs a Grav update first
throw new \Exception("<red>One of the packages require PHP " . $dependencies['php'] . ". Please update PHP to resolve this");
} else {
unset($dependencies[$dependency_slug]);
continue;
throw new \RuntimeException("<red>One of the packages require PHP {$dependencies['php']}. Please update PHP to resolve this");
}
unset($dependencies[$dependency_slug]);
continue;
}
//First, check for Grav dependency. If a dependency requires Grav > the current version, abort and tell.
if ($dependency_slug == 'grav') {
if ($dependency_slug === 'grav') {
if (version_compare($this->calculateVersionNumberFromDependencyVersion($dependencyVersionWithOperator),
GRAV_VERSION) === 1
) {
//Needs a Grav update first
throw new \Exception("<red>One of the packages require Grav " . $dependencies['grav'] . ". Please update Grav to the latest release.");
} else {
unset($dependencies[$dependency_slug]);
continue;
throw new \RuntimeException("<red>One of the packages require Grav {$dependencies['grav']}. Please update Grav to the latest release.");
}
unset($dependencies[$dependency_slug]);
continue;
}
if ($this->isPluginInstalled($dependency_slug)) {
@@ -888,7 +892,7 @@ class GPM extends Iterator
$currentlyInstalledVersion);
if (!$compatible) {
throw new \Exception('Dependency <cyan>' . $dependency_slug . '</cyan> is required in an older version than the one installed. This package must be updated. Please get in touch with its developer.',
throw new \RuntimeException('Dependency <cyan>' . $dependency_slug . '</cyan> is required in an older version than the one installed. This package must be updated. Please get in touch with its developer.',
2);
}
}
@@ -899,7 +903,7 @@ class GPM extends Iterator
if ($this->firstVersionIsLower($latestRelease, $dependencyVersion)) {
//throw an exception if a required version cannot be found in the GPM yet
throw new \Exception('Dependency <cyan>' . $package_yaml['name'] . '</cyan> is required in version <cyan>' . $dependencyVersion . '</cyan> which is higher than the latest release, <cyan>' . $latestRelease . '</cyan>. Try running `bin/gpm -f index` to force a refresh of the GPM cache',
throw new \RuntimeException('Dependency <cyan>' . $package_yaml['name'] . '</cyan> is required in version <cyan>' . $dependencyVersion . '</cyan> which is higher than the latest release, <cyan>' . $latestRelease . '</cyan>. Try running `bin/gpm -f index` to force a refresh of the GPM cache',
1);
}
@@ -950,7 +954,7 @@ class GPM extends Iterator
private function firstVersionIsLower($firstVersion, $secondVersion)
{
return version_compare($firstVersion, $secondVersion) == -1;
return version_compare($firstVersion, $secondVersion) === -1;
}
/**
@@ -1005,7 +1009,7 @@ class GPM extends Iterator
$current_package_version_number = $this->calculateVersionNumberFromDependencyVersion($current_package_version_information);
if (!$current_package_version_number) {
throw new \Exception('Bad format for version of dependency ' . $current_package_name . ' for package ' . $packageName,
throw new \RuntimeException('Bad format for version of dependency ' . $current_package_name . ' for package ' . $packageName,
1);
}
@@ -1021,7 +1025,7 @@ class GPM extends Iterator
if (!$currently_stored_version_is_in_next_significant_release_format && !$current_package_version_is_in_next_significant_release_format) {
//Comparing versions equals or higher, a simple version_compare is enough
if (version_compare($currently_stored_version_number,
$current_package_version_number) == -1
$current_package_version_number) === -1
) { //Current package version is higher
$dependencies[$current_package_name] = $current_package_version_information;
}
@@ -1029,7 +1033,7 @@ class GPM extends Iterator
$compatible = $this->checkNextSignificantReleasesAreCompatible($currently_stored_version_number,
$current_package_version_number);
if (!$compatible) {
throw new \Exception('Dependency ' . $current_package_name . ' is required in two incompatible versions',
throw new \RuntimeException('Dependency ' . $current_package_name . ' is required in two incompatible versions',
2);
}
}
@@ -1076,17 +1080,20 @@ class GPM extends Iterator
*/
public function calculateVersionNumberFromDependencyVersion($version)
{
if ($version == '*') {
if ($version === '*') {
return null;
} elseif ($version == '') {
return null;
} elseif ($this->versionFormatIsNextSignificantRelease($version)) {
return trim(substr($version, 1));
} elseif ($this->versionFormatIsEqualOrHigher($version)) {
return trim(substr($version, 2));
} else {
return $version;
}
if ($version === '') {
return null;
}
if ($this->versionFormatIsNextSignificantRelease($version)) {
return trim(substr($version, 1));
}
if ($this->versionFormatIsEqualOrHigher($version)) {
return trim(substr($version, 2));
}
return $version;
}
/**
@@ -1094,13 +1101,13 @@ class GPM extends Iterator
*
* Example: returns true for $version: '~2.0'
*
* @param $version
* @param string $version
*
* @return bool
*/
public function versionFormatIsNextSignificantRelease($version)
public function versionFormatIsNextSignificantRelease($version): bool
{
return substr($version, 0, 1) == '~';
return strpos($version, '~') === 0;
}
/**
@@ -1108,13 +1115,13 @@ class GPM extends Iterator
*
* Example: returns true for $version: '>=2.0'
*
* @param $version
* @param string $version
*
* @return bool
*/
public function versionFormatIsEqualOrHigher($version)
public function versionFormatIsEqualOrHigher($version): bool
{
return substr($version, 0, 2) == '>=';
return strpos($version, '>=') === 0;
}
/**
@@ -1130,17 +1137,17 @@ class GPM extends Iterator
*
* @return bool
*/
public function checkNextSignificantReleasesAreCompatible($version1, $version2)
public function checkNextSignificantReleasesAreCompatible($version1, $version2): bool
{
$version1array = explode('.', $version1);
$version2array = explode('.', $version2);
if (count($version1array) > count($version2array)) {
if (\count($version1array) > \count($version2array)) {
list($version1array, $version2array) = [$version2array, $version1array];
}
$i = 0;
while ($i < count($version1array) - 1) {
while ($i < \count($version1array) - 1) {
if ($version1array[$i] != $version2array[$i]) {
return false;
}
+59 -49
View File
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.GPM
* @package Grav\Common\GPM
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -14,23 +15,23 @@ use Grav\Common\Grav;
class Installer
{
/** @const No error */
const OK = 0;
public const OK = 0;
/** @const Target already exists */
const EXISTS = 1;
public const EXISTS = 1;
/** @const Target is a symbolic link */
const IS_LINK = 2;
public const IS_LINK = 2;
/** @const Target doesn't exist */
const NOT_FOUND = 4;
public const NOT_FOUND = 4;
/** @const Target is not a directory */
const NOT_DIRECTORY = 8;
public const NOT_DIRECTORY = 8;
/** @const Target is not a Grav instance */
const NOT_GRAV_ROOT = 16;
public const NOT_GRAV_ROOT = 16;
/** @const Error while trying to open the ZIP package */
const ZIP_OPEN_ERROR = 32;
public const ZIP_OPEN_ERROR = 32;
/** @const Error while trying to extract the ZIP package */
const ZIP_EXTRACT_ERROR = 64;
public const ZIP_EXTRACT_ERROR = 64;
/** @const Invalid source file */
const INVALID_SOURCE = 128;
public const INVALID_SOURCE = 128;
/**
* Destination folder on which validation checks are applied
@@ -39,12 +40,12 @@ class Installer
protected static $target;
/**
* @var integer Error Code
* @var int Error Code
*/
protected static $error = 0;
/**
* @var integer Zip Error Code
* @var int Zip Error Code
*/
protected static $error_zip = 0;
@@ -74,9 +75,10 @@ class Installer
* @param string $destination The local path to the Grav Instance
* @param array $options Options to use for installing. ie, ['install_path' => 'user/themes/antimatter']
* @param string $extracted The local path to the extacted ZIP package
* @param bool $keepExtracted True if you want to keep the original files
* @return bool True if everything went fine, False otherwise.
*/
public static function install($zip, $destination, $options = [], $extracted = null)
public static function install($zip, $destination, $options = [], $extracted = null, $keepExtracted = false)
{
$destination = rtrim($destination, DS);
$options = array_merge(self::$options, $options);
@@ -88,15 +90,15 @@ class Installer
return false;
}
if (self::lastErrorCode() == self::IS_LINK && $options['ignore_symlinks'] ||
self::lastErrorCode() == self::EXISTS && !$options['overwrite']
if ((self::lastErrorCode() === self::IS_LINK && $options['ignore_symlinks']) ||
(self::lastErrorCode() === self::EXISTS && !$options['overwrite'])
) {
return false;
}
// Create a tmp location
$tmp_dir = Grav::instance()['locator']->findResource('tmp://', true, true);
$tmp = $tmp_dir . '/Grav-' . uniqid();
$tmp = $tmp_dir . '/Grav-' . uniqid('', false);
if (!$extracted) {
$extracted = self::unZip($zip, $tmp);
@@ -111,7 +113,6 @@ class Installer
return false;
}
$is_install = true;
$installer = self::loadInstaller($extracted, $is_install);
@@ -140,7 +141,7 @@ class Installer
self::moveInstall($extracted, $install_path);
}
} else {
self::sophisticatedInstall($extracted, $install_path, $options['ignores']);
self::sophisticatedInstall($extracted, $install_path, $options['ignores'], $keepExtracted);
}
Folder::delete($tmp);
@@ -165,8 +166,8 @@ class Installer
/**
* Unzip a file to somewhere
*
* @param $zip_file
* @param $destination
* @param string $zip_file
* @param string $destination
* @return bool|string
*/
public static function unZip($zip_file, $destination)
@@ -175,7 +176,7 @@ class Installer
$archive = $zip->open($zip_file);
if ($archive === true) {
Folder::mkdir($destination);
Folder::create($destination);
$unzip = $zip->extractTo($destination);
@@ -189,9 +190,8 @@ class Installer
$package_folder_name = preg_replace('#\./$#', '', $zip->getNameIndex(0));
$zip->close();
$extracted_folder = $destination . '/' . $package_folder_name;
return $extracted_folder;
return $destination . '/' . $package_folder_name;
}
self::$error = self::ZIP_EXTRACT_ERROR;
@@ -216,7 +216,7 @@ class Installer
$install_file = $installer_file_folder . DS . 'install.php';
if (file_exists($install_file)) {
require_once($install_file);
require_once $install_file;
} else {
return null;
}
@@ -253,8 +253,8 @@ class Installer
}
/**
* @param $source_path
* @param $install_path
* @param string $source_path
* @param string $install_path
*
* @return bool
*/
@@ -270,8 +270,8 @@ class Installer
}
/**
* @param $source_path
* @param $install_path
* @param string $source_path
* @param string $install_path
*
* @return bool
*/
@@ -279,24 +279,26 @@ class Installer
{
if (empty($source_path)) {
throw new \RuntimeException("Directory $source_path is missing");
} else {
Folder::rcopy($source_path, $install_path);
}
Folder::rcopy($source_path, $install_path);
return true;
}
/**
* @param $source_path
* @param $install_path
* @param string $source_path
* @param string $install_path
* @param array $ignores
* @param bool $keep_source
*
* @return bool
*/
public static function sophisticatedInstall($source_path, $install_path, $ignores = [])
public static function sophisticatedInstall($source_path, $install_path, $ignores = [], $keep_source = false)
{
foreach (new \DirectoryIterator($source_path) as $file) {
if ($file->isLink() || $file->isDot() || in_array($file->getFilename(), $ignores)) {
if ($file->isLink() || $file->isDot() || \in_array($file->getFilename(), $ignores, true)) {
continue;
}
@@ -304,7 +306,11 @@ class Installer
if ($file->isDir()) {
Folder::delete($path);
Folder::move($file->getPathname(), $path);
if ($keep_source) {
Folder::copy($file->getPathname(), $path);
} else {
Folder::move($file->getPathname(), $path);
}
if ($file->getFilename() === 'bin') {
foreach (glob($path . DS . '*') as $bin_file) {
@@ -326,7 +332,7 @@ class Installer
* @param string $path The slug of the package(s)
* @param array $options Options to use for uninstalling
*
* @return boolean True if everything went fine, False otherwise.
* @return bool True if everything went fine, False otherwise.
*/
public static function uninstall($path, $options = [])
{
@@ -368,7 +374,7 @@ class Installer
* @param string $destination The directory to run validations at
* @param array $exclude An array of constants to exclude from the validation
*
* @return boolean True if validation passed. False otherwise
* @return bool True if validation passed. False otherwise
*/
public static function isValidDestination($destination, $exclude = [])
{
@@ -385,11 +391,11 @@ class Installer
self::$error = self::NOT_DIRECTORY;
}
if (count($exclude) && in_array(self::$error, $exclude)) {
if (\count($exclude) && \in_array(self::$error, $exclude, true)) {
return true;
}
return !(self::$error);
return !self::$error;
}
/**
@@ -397,7 +403,7 @@ class Installer
*
* @param string $target The local path to the Grav Instance
*
* @return boolean True if is a Grav Instance. False otherwise
* @return bool True if is a Grav Instance. False otherwise
*/
public static function isGravInstance($target)
{
@@ -469,23 +475,23 @@ class Installer
if (self::$error_zip) {
switch(self::$error_zip) {
case \ZipArchive::ER_EXISTS:
$msg .= "File already exists.";
$msg .= 'File already exists.';
break;
case \ZipArchive::ER_INCONS:
$msg .= "Zip archive inconsistent.";
$msg .= 'Zip archive inconsistent.';
break;
case \ZipArchive::ER_MEMORY:
$msg .= "Malloc failure.";
$msg .= 'Memory allocation failure.';
break;
case \ZipArchive::ER_NOENT:
$msg .= "No such file.";
$msg .= 'No such file.';
break;
case \ZipArchive::ER_NOZIP:
$msg .= "Not a zip archive.";
$msg .= 'Not a zip archive.';
break;
case \ZipArchive::ER_OPEN:
@@ -493,16 +499,20 @@ class Installer
break;
case \ZipArchive::ER_READ:
$msg .= "Read error.";
$msg .= 'Read error.';
break;
case \ZipArchive::ER_SEEK:
$msg .= "Seek error.";
$msg .= 'Seek error.';
break;
}
}
break;
case self::INVALID_SOURCE:
$msg = 'Invalid source file';
break;
default:
$msg = 'Unknown Error';
break;
@@ -513,7 +523,7 @@ class Installer
/**
* Returns the last error code of the occurred error
* @return integer The code of the last error
* @return int|string The code of the last error
*/
public static function lastErrorCode()
{
+16 -19
View File
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.GPM
* @package Grav\Common\GPM
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -32,22 +33,22 @@ class Licenses
/**
* Returns the license for a Premium package
*
* @param $slug
* @param $license
* @param string $slug
* @param string $license
*
* @return boolean
* @return bool
*/
public static function set($slug, $license)
{
$licenses = self::getLicenseFile();
$data = $licenses->content();
$data = (array)$licenses->content();
$slug = strtolower($slug);
if ($license && !self::validate($license)) {
return false;
}
if (!is_string($license)) {
if (!\is_string($license)) {
if (isset($data['licenses'][$slug])) {
unset($data['licenses'][$slug]);
} else {
@@ -66,33 +67,29 @@ class Licenses
/**
* Returns the license for a Premium package
*
* @param $slug
* @param string $slug
*
* @return string
* @return array|string
*/
public static function get($slug = null)
{
$licenses = self::getLicenseFile();
$data = $licenses->content();
$data = (array)$licenses->content();
$licenses->free();
$slug = strtolower($slug);
if (!$slug) {
return isset($data['licenses']) ? $data['licenses'] : [];
return $data['licenses'] ?? [];
}
if (!isset($data['licenses']) || !isset($data['licenses'][$slug])) {
return '';
}
return $data['licenses'][$slug];
return $data['licenses'][$slug] ?? '';
}
/**
* Validates the License format
*
* @param $license
* @param string $license
*
* @return bool
*/
@@ -106,7 +103,7 @@ class Licenses
}
/**
* Get's the License File object
* Get the License File object
*
* @return \RocketTheme\Toolbox\File\FileInterface
*/
@@ -114,7 +111,7 @@ class Licenses
{
if (!isset(self::$file)) {
$path = Grav::instance()['locator']->findResource('user://data') . '/licenses.yaml';
$path = Grav::instance()['locator']->findResource('user-data://') . '/licenses.yaml';
if (!file_exists($path)) {
touch($path);
}
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.GPM
* @package Grav\Common\GPM
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -14,6 +15,8 @@ abstract class AbstractPackageCollection extends BaseCollection
{
public function __construct($items)
{
parent::__construct();
foreach ($items as $name => $data) {
$data->set('slug', $name);
$this->items[$name] = new Package($data, $this->type);
+6 -5
View File
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.GPM
* @package Grav\Common\GPM
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -22,11 +23,11 @@ class Package extends BasePackage
$this->settings = $package->toArray();
$html_description = \Parsedown::instance()->line($this->description);
$this->data->set('slug', $package->slug);
$html_description = \Parsedown::instance()->line($this->__get('description'));
$this->data->set('slug', $package->__get('slug'));
$this->data->set('description_html', $html_description);
$this->data->set('description_plain', strip_tags($html_description));
$this->data->set('symlink', is_link(USER_DIR . $package_type . DS . $this->slug));
$this->data->set('symlink', is_link(USER_DIR . $package_type . DS . $this->__get('slug')));
}
/**
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.GPM
* @package Grav\Common\GPM
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
+4 -2
View File
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.GPM
* @package Grav\Common\GPM
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -24,6 +25,7 @@ class Plugins extends AbstractPackageCollection
{
/** @var \Grav\Common\Plugins $plugins */
$plugins = Grav::instance()['plugins'];
parent::__construct($plugins->all());
}
}
+3 -2
View File
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.GPM
* @package Grav\Common\GPM
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.GPM
* @package Grav\Common\GPM
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -23,7 +24,7 @@ class AbstractPackageCollection extends BaseCollection
/**
* The lifetime to store the entry in seconds
* @var integer
* @var int
*/
private $lifetime = 86400;
@@ -40,8 +41,9 @@ class AbstractPackageCollection extends BaseCollection
*/
public function __construct($repository = null, $refresh = false, $callback = null)
{
parent::__construct();
if ($repository === null) {
throw new \RuntimeException("A repository is required to indicate the origin of the remote collection");
throw new \RuntimeException('A repository is required to indicate the origin of the remote collection');
}
$channel = Grav::instance()['config']->get('system.gpm.releases', 'stable');
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.GPM
* @package Grav\Common\GPM
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -36,9 +37,9 @@ class GravCore extends AbstractPackageCollection
$this->fetch($refresh, $callback);
$this->data = json_decode($this->raw, true);
$this->version = isset($this->data['version']) ? $this->data['version'] : '-';
$this->date = isset($this->data['date']) ? $this->data['date'] : '-';
$this->min_php = isset($this->data['min_php']) ? $this->data['min_php'] : null;
$this->version = $this->data['version'] ?? '-';
$this->date = $this->data['date'] ?? '-';
$this->min_php = $this->data['min_php'] ?? null;
if (isset($this->data['assets'])) {
foreach ((array)$this->data['assets'] as $slug => $data) {
@@ -72,7 +73,7 @@ class GravCore extends AbstractPackageCollection
$diffLog = [];
foreach ((array)$this->data['changelog'] as $version => $changelog) {
preg_match("/[\w-\.]+/", $version, $cleanVersion);
preg_match("/[\w\-\.]+/", $version, $cleanVersion);
if (!$cleanVersion || version_compare($diff, $cleanVersion[0], '>=')) {
continue;
@@ -122,7 +123,7 @@ class GravCore extends AbstractPackageCollection
public function getMinPHPVersion()
{
// If non min set, assume current PHP version
if (is_null($this->min_php)) {
if (null === $this->min_php) {
$this->min_php = phpversion();
}
return $this->min_php;
+12 -4
View File
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.GPM
* @package Grav\Common\GPM
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -11,9 +12,16 @@ namespace Grav\Common\GPM\Remote;
use Grav\Common\Data\Data;
use Grav\Common\GPM\Common\Package as BasePackage;
class Package extends BasePackage {
public function __construct($package, $package_type = null) {
class Package extends BasePackage implements \JsonSerializable
{
public function __construct($package, $package_type = null)
{
$data = new Data($package);
parent::__construct($data, $package_type);
}
public function jsonSerialize()
{
return $this->data;
}
}
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.GPM
* @package Grav\Common\GPM
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.GPM
* @package Grav\Common\GPM
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
+3 -2
View File
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.GPM
* @package Grav\Common\GPM
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
+29 -27
View File
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.GPM
* @package Grav\Common\GPM
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -77,7 +78,7 @@ class Response
*/
public static function setMethod($method = 'auto')
{
if (!in_array($method, ['auto', 'curl', 'fopen'])) {
if (!\in_array($method, ['auto', 'curl', 'fopen'], true)) {
$method = 'auto';
}
@@ -103,7 +104,7 @@ class Response
// check if this function is available, if so use it to stop any timeouts
try {
if (!Utils::isFunctionDisabled('set_time_limit') && !ini_get('safe_mode') && function_exists('set_time_limit')) {
if (function_exists('set_time_limit') && !Utils::isFunctionDisabled('set_time_limit')) {
set_time_limit(0);
}
} catch (\Exception $e) {
@@ -165,7 +166,7 @@ class Response
$overrides['curl'][CURLOPT_PROXYPORT] = $proxy['port'];
}
if (isset($proxy['user']) && isset($proxy['pass'])) {
if (isset($proxy['user'], $proxy['pass'])) {
$fopen_auth = $auth = base64_encode($proxy['user'] . ':' . $proxy['pass']);
$overrides['curl'][CURLOPT_PROXYUSERPWD] = $proxy['user'] . ':' . $proxy['pass'];
$overrides['fopen']['header'] = "Proxy-Authorization: Basic $fopen_auth";
@@ -182,7 +183,7 @@ class Response
/**
* Checks if cURL is available
*
* @return boolean
* @return bool
*/
public static function isCurlAvailable()
{
@@ -192,7 +193,7 @@ class Response
/**
* Checks if the remote fopen request is enabled in PHP
*
* @return boolean
* @return bool
*/
public static function isFopenAvailable()
{
@@ -202,7 +203,7 @@ class Response
/**
* Is this a remote file or not
*
* @param $file
* @param string $file
* @return bool
*/
public static function isRemote($file)
@@ -219,7 +220,7 @@ class Response
static $filesize = null;
$args = func_get_args();
$isCurlResource = is_resource($args[0]) && get_resource_type($args[0]) == 'curl';
$isCurlResource = is_resource($args[0]) && get_resource_type($args[0]) === 'curl';
$notification_code = !$isCurlResource ? $args[0] : false;
$bytes_transferred = $isCurlResource ? $args[2] : $args[4];
@@ -241,7 +242,7 @@ class Response
];
if (self::$callback !== null) {
call_user_func_array(self::$callback, [$progress]);
call_user_func(self::$callback, $progress);
}
}
}
@@ -272,13 +273,13 @@ class Response
*/
private static function getFopen()
{
if (count($args = func_get_args()) == 1) {
if (\count($args = func_get_args()) === 1) {
$args = $args[0];
}
$uri = $args[0];
$options = $args[1];
$callback = $args[2];
$options = $args[1] ?? [];
$callback = $args[2] ?? null;
if ($callback) {
$options['fopen']['notification'] = ['self', 'progress'];
@@ -301,17 +302,18 @@ class Response
if ($content === false) {
$code = null;
// Function file_get_contents() creates local variable $http_response_header, check it
if (isset($http_response_header)) {
$code = explode(' ', $http_response_header[0])[1];
$code = explode(' ', $http_response_header[0] ?? '')[1] ?? null;
}
switch ($code) {
case '404':
throw new \RuntimeException("Page not found");
throw new \RuntimeException('Page not found');
case '401':
throw new \RuntimeException("Invalid LICENSE");
throw new \RuntimeException('Invalid LICENSE');
default:
throw new \RuntimeException("Error while trying to download (code: $code): $uri \n");
throw new \RuntimeException("Error while trying to download (code: {$code}): {$uri}\n");
}
}
@@ -329,8 +331,8 @@ class Response
$args = count($args) > 1 ? $args : array_shift($args);
$uri = $args[0];
$options = $args[1];
$callback = $args[2];
$options = $args[1] ?? [];
$callback = $args[2] ?? null;
$ch = curl_init($uri);
@@ -343,9 +345,9 @@ class Response
switch ($code) {
case '404':
throw new \RuntimeException("Page not found");
throw new \RuntimeException('Page not found');
case '401':
throw new \RuntimeException("Invalid LICENSE");
throw new \RuntimeException('Invalid LICENSE');
default:
throw new \RuntimeException("Error while trying to download (code: $code): $uri \nMessage: $error_message");
}
@@ -357,9 +359,9 @@ class Response
}
/**
* @param $ch
* @param $options
* @param $callback
* @param resource $ch
* @param array $options
* @param bool $callback
*
* @return bool|mixed
*/
@@ -381,7 +383,7 @@ class Response
return curl_exec($ch);
}
$max_redirects = isset($options['curl'][CURLOPT_MAXREDIRS]) ? $options['curl'][CURLOPT_MAXREDIRS] : 5;
$max_redirects = $options['curl'][CURLOPT_MAXREDIRS] ?? 5;
$options['curl'][CURLOPT_FOLLOWLOCATION] = false;
// open_basedir set but no redirects to follow, we can disable followlocation and proceed normally
@@ -405,8 +407,8 @@ class Response
if (curl_errno($rch)) {
$code = 0;
} else {
$code = curl_getinfo($rch, CURLINFO_HTTP_CODE);
if ($code == 301 || $code == 302 || $code == 303) {
$code = (int)curl_getinfo($rch, CURLINFO_HTTP_CODE);
if ($code === 301 || $code === 302 || $code === 303) {
preg_match('/Location:(.*?)\n/', $header, $matches);
$uri = trim(array_pop($matches));
} else {
+5 -4
View File
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.GPM
* @package Grav\Common\GPM
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -112,7 +113,7 @@ class Upgrader
*/
public function minPHPVersion()
{
if (is_null($this->min_php)) {
if (null === $this->min_php) {
$this->min_php = $this->remote->getMinPHPVersion();
}
return $this->min_php;
@@ -125,7 +126,7 @@ class Upgrader
*/
public function isUpgradable()
{
return version_compare($this->getLocalVersion(), $this->getRemoteVersion(), "<");
return version_compare($this->getLocalVersion(), $this->getRemoteVersion(), '<');
}
/**
+21 -20
View File
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common
* @package Grav\Common
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -73,9 +74,9 @@ abstract class Getters implements \ArrayAccess, \Countable
$var = $this->gettersVariable;
return isset($this->{$var}[$offset]);
} else {
return isset($this->{$offset});
}
return isset($this->{$offset});
}
/**
@@ -88,10 +89,10 @@ abstract class Getters implements \ArrayAccess, \Countable
if ($this->gettersVariable) {
$var = $this->gettersVariable;
return isset($this->{$var}[$offset]) ? $this->{$var}[$offset] : null;
} else {
return isset($this->{$offset}) ? $this->{$offset} : null;
return $this->{$var}[$offset] ?? null;
}
return $this->{$offset} ?? null;
}
/**
@@ -128,10 +129,10 @@ abstract class Getters implements \ArrayAccess, \Countable
{
if ($this->gettersVariable) {
$var = $this->gettersVariable;
count($this->{$var});
} else {
count($this->toArray());
return \count($this->{$var});
}
return \count($this->toArray());
}
/**
@@ -145,16 +146,16 @@ abstract class Getters implements \ArrayAccess, \Countable
$var = $this->gettersVariable;
return $this->{$var};
} else {
$properties = (array)$this;
$list = [];
foreach ($properties as $property => $value) {
if ($property[0] != "\0") {
$list[$property] = $value;
}
}
return $list;
}
$properties = (array)$this;
$list = [];
foreach ($properties as $property => $value) {
if ($property[0] !== "\0") {
$list[$property] = $value;
}
}
return $list;
}
}
+216 -142
View File
@@ -1,21 +1,48 @@
<?php
/**
* @package Grav.Common
* @package Grav\Common
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common;
use Grav\Common\Config\Config;
use Grav\Common\Config\Setup;
use Grav\Common\Page\Interfaces\PageInterface;
use Grav\Common\Page\Medium\ImageMedium;
use Grav\Common\Page\Medium\Medium;
use Grav\Common\Page\Page;
use RocketTheme\Toolbox\DI\Container;
use Grav\Common\Processors\AssetsProcessor;
use Grav\Common\Processors\BackupsProcessor;
use Grav\Common\Processors\ConfigurationProcessor;
use Grav\Common\Processors\DebuggerAssetsProcessor;
use Grav\Common\Processors\DebuggerProcessor;
use Grav\Common\Processors\ErrorsProcessor;
use Grav\Common\Processors\InitializeProcessor;
use Grav\Common\Processors\LoggerProcessor;
use Grav\Common\Processors\PagesProcessor;
use Grav\Common\Processors\PluginsProcessor;
use Grav\Common\Processors\RenderProcessor;
use Grav\Common\Processors\RequestProcessor;
use Grav\Common\Processors\SchedulerProcessor;
use Grav\Common\Processors\TasksProcessor;
use Grav\Common\Processors\ThemesProcessor;
use Grav\Common\Processors\TwigProcessor;
use Grav\Framework\DI\Container;
use Grav\Framework\Psr7\Response;
use Grav\Framework\RequestHandler\RequestHandler;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use RocketTheme\Toolbox\Event\Event;
use RocketTheme\Toolbox\Event\EventDispatcher;
/**
* Grav container is the heart of Grav.
*
* @package Grav\Common
*/
class Grav extends Container
{
/**
@@ -33,54 +60,47 @@ class Grav extends Container
* to the dependency injection container.
*/
protected static $diMap = [
'Grav\Common\Service\LoggerServiceProvider',
'Grav\Common\Service\ErrorServiceProvider',
'uri' => 'Grav\Common\Uri',
'events' => 'RocketTheme\Toolbox\Event\EventDispatcher',
'cache' => 'Grav\Common\Cache',
'Grav\Common\Service\SessionServiceProvider',
'plugins' => 'Grav\Common\Plugins',
'themes' => 'Grav\Common\Themes',
'twig' => 'Grav\Common\Twig\Twig',
'taxonomy' => 'Grav\Common\Taxonomy',
'language' => 'Grav\Common\Language\Language',
'pages' => 'Grav\Common\Page\Pages',
'Grav\Common\Service\TaskServiceProvider',
'Grav\Common\Service\AccountsServiceProvider',
'Grav\Common\Service\AssetsServiceProvider',
'Grav\Common\Service\PageServiceProvider',
'Grav\Common\Service\OutputServiceProvider',
'browser' => 'Grav\Common\Browser',
'exif' => 'Grav\Common\Helpers\Exif',
'Grav\Common\Service\StreamsServiceProvider',
'Grav\Common\Service\BackupsServiceProvider',
'Grav\Common\Service\ConfigServiceProvider',
'inflector' => 'Grav\Common\Inflector',
'siteSetupProcessor' => 'Grav\Common\Processors\SiteSetupProcessor',
'configurationProcessor' => 'Grav\Common\Processors\ConfigurationProcessor',
'errorsProcessor' => 'Grav\Common\Processors\ErrorsProcessor',
'debuggerInitProcessor' => 'Grav\Common\Processors\DebuggerInitProcessor',
'initializeProcessor' => 'Grav\Common\Processors\InitializeProcessor',
'pluginsProcessor' => 'Grav\Common\Processors\PluginsProcessor',
'themesProcessor' => 'Grav\Common\Processors\ThemesProcessor',
'tasksProcessor' => 'Grav\Common\Processors\TasksProcessor',
'assetsProcessor' => 'Grav\Common\Processors\AssetsProcessor',
'twigProcessor' => 'Grav\Common\Processors\TwigProcessor',
'pagesProcessor' => 'Grav\Common\Processors\PagesProcessor',
'debuggerAssetsProcessor' => 'Grav\Common\Processors\DebuggerAssetsProcessor',
'renderProcessor' => 'Grav\Common\Processors\RenderProcessor',
'Grav\Common\Service\ErrorServiceProvider',
'Grav\Common\Service\FilesystemServiceProvider',
'Grav\Common\Service\InflectorServiceProvider',
'Grav\Common\Service\LoggerServiceProvider',
'Grav\Common\Service\OutputServiceProvider',
'Grav\Common\Service\PagesServiceProvider',
'Grav\Common\Service\RequestServiceProvider',
'Grav\Common\Service\SessionServiceProvider',
'Grav\Common\Service\StreamsServiceProvider',
'Grav\Common\Service\TaskServiceProvider',
'browser' => 'Grav\Common\Browser',
'cache' => 'Grav\Common\Cache',
'events' => 'RocketTheme\Toolbox\Event\EventDispatcher',
'exif' => 'Grav\Common\Helpers\Exif',
'plugins' => 'Grav\Common\Plugins',
'scheduler' => 'Grav\Common\Scheduler\Scheduler',
'taxonomy' => 'Grav\Common\Taxonomy',
'themes' => 'Grav\Common\Themes',
'twig' => 'Grav\Common\Twig\Twig',
'uri' => 'Grav\Common\Uri',
];
/**
* @var array All processors that are processed in $this->process()
* @var array All middleware processors that are processed in $this->process()
*/
protected $processors = [
'siteSetupProcessor',
protected $middleware = [
'configurationProcessor',
'loggerProcessor',
'errorsProcessor',
'debuggerInitProcessor',
'debuggerProcessor',
'initializeProcessor',
'pluginsProcessor',
'themesProcessor',
'requestProcessor',
'tasksProcessor',
'backupsProcessor',
'schedulerProcessor',
'assetsProcessor',
'twigProcessor',
'pagesProcessor',
@@ -88,6 +108,8 @@ class Grav extends Container
'renderProcessor',
];
protected $initialized = [];
/**
* Reset the Grav instance.
*/
@@ -119,21 +141,116 @@ class Grav extends Container
return self::$instance;
}
/**
* Setup Grav instance using specific environment.
*
* Initializes Grav streams by
*
* @param string|null $environment
* @return $this
*/
public function setup(string $environment = null)
{
if (isset($this->initialized['setup'])) {
return $this;
}
$this->initialized['setup'] = true;
$this->measureTime('_setup', 'Site Setup', function () use ($environment) {
// Force environment if passed to the method.
if ($environment) {
Setup::$environment = $environment;
}
$this['setup'];
$this['streams'];
});
return $this;
}
/**
* Process a request
*/
public function process()
{
// process all processors (e.g. config, initialize, assets, ..., render)
foreach ($this->processors as $processor) {
$processor = $this[$processor];
$this->measureTime($processor->id, $processor->title, function () use ($processor) {
$processor->process();
});
if (isset($this->initialized['process'])) {
return;
}
// Initialize Grav if needed.
$this->setup();
$this->initialized['process'] = true;
$container = new Container(
[
'configurationProcessor' => function () {
return new ConfigurationProcessor($this);
},
'loggerProcessor' => function () {
return new LoggerProcessor($this);
},
'errorsProcessor' => function () {
return new ErrorsProcessor($this);
},
'debuggerProcessor' => function () {
return new DebuggerProcessor($this);
},
'initializeProcessor' => function () {
return new InitializeProcessor($this);
},
'backupsProcessor' => function () {
return new BackupsProcessor($this);
},
'pluginsProcessor' => function () {
return new PluginsProcessor($this);
},
'themesProcessor' => function () {
return new ThemesProcessor($this);
},
'schedulerProcessor' => function () {
return new SchedulerProcessor($this);
},
'requestProcessor' => function () {
return new RequestProcessor($this);
},
'tasksProcessor' => function () {
return new TasksProcessor($this);
},
'assetsProcessor' => function () {
return new AssetsProcessor($this);
},
'twigProcessor' => function () {
return new TwigProcessor($this);
},
'pagesProcessor' => function () {
return new PagesProcessor($this);
},
'debuggerAssetsProcessor' => function () {
return new DebuggerAssetsProcessor($this);
},
'renderProcessor' => function () {
return new RenderProcessor($this);
},
]
);
$default = function (ServerRequestInterface $request) {
return new Response(404);
};
/** @var Debugger $debugger */
$debugger = $this['debugger'];
$collection = new RequestHandler($this->middleware, $default, $container);
$response = $collection->handle($this['request']);
$this->header($response);
echo $response->getBody();
$debugger->render();
register_shutdown_function([$this, 'shutdown']);
@@ -147,7 +264,7 @@ class Grav extends Container
// Initialize Locale if set and configured.
if ($this['language']->enabled() && $this['config']->get('system.languages.override_locale')) {
$language = $this['language']->getLanguage();
setlocale(LC_ALL, strlen($language) < 3 ? ($language . '_' . strtoupper($language)) : $language);
setlocale(LC_ALL, \strlen($language) < 3 ? ($language . '_' . strtoupper($language)) : $language);
} elseif ($this['config']->get('system.default_locale')) {
setlocale(LC_ALL, $this['config']->get('system.default_locale'));
}
@@ -213,53 +330,22 @@ class Grav extends Container
/**
* Set response header.
*
* @param ResponseInterface|null $response
*/
public function header()
public function header(ResponseInterface $response = null)
{
/** @var Page $page */
$page = $this['page'];
if (null === $response) {
/** @var PageInterface $page */
$page = $this['page'];
$response = new Response($page->httpResponseCode(), $page->httpHeaders(), '');
}
$format = $page->templateFormat();
header('Content-type: ' . Utils::getMimeByExtension($format, 'text/html'));
$cache_control = $page->cacheControl();
// Calculate Expires Headers if set to > 0
$expires = $page->expires();
if ($expires > 0) {
$expires_date = gmdate('D, d M Y H:i:s', time() + $expires) . ' GMT';
if (!$cache_control) {
header('Cache-Control: max-age=' . $expires);
header("HTTP/{$response->getProtocolVersion()} {$response->getStatusCode()} {$response->getReasonPhrase()}");
foreach ($response->getHeaders() as $key => $values) {
foreach ($values as $i => $value) {
header($key . ': ' . $value, $i === 0);
}
header('Expires: ' . $expires_date);
}
// Set cache-control header
if ($cache_control) {
header('Cache-Control: ' . strtolower($cache_control));
}
// Set the last modified time
if ($page->lastModified()) {
$last_modified_date = gmdate('D, d M Y H:i:s', $page->modified()) . ' GMT';
header('Last-Modified: ' . $last_modified_date);
}
// Calculate a Hash based on the raw file
if ($page->eTag()) {
header('ETag: "' . md5($page->raw() . $page->modified()).'"');
}
// Set HTTP response code
if (isset($this['page']->header()->http_response_code)) {
http_response_code($this['page']->header()->http_response_code);
}
// Vary: Accept-Encoding
if ($this['config']->get('system.pages.vary_accept_encoding', false)) {
header('Vary: Accept-Encoding');
}
}
@@ -286,7 +372,7 @@ class Grav extends Container
public function shutdown()
{
// Prevent user abort allowing onShutdown event to run without interruptions.
if (function_exists('ignore_user_abort')) {
if (\function_exists('ignore_user_abort')) {
@ignore_user_abort(true);
}
@@ -300,7 +386,7 @@ class Grav extends Container
// the connection to the client open. This will make page loads to feel much faster.
// FastCGI allows us to flush all response data to the client and finish the request.
$success = function_exists('fastcgi_finish_request') ? @fastcgi_finish_request() : false;
$success = \function_exists('fastcgi_finish_request') ? @fastcgi_finish_request() : false;
if (!$success) {
// Unfortunately without FastCGI there is no way to force close the connection.
@@ -323,7 +409,7 @@ class Grav extends Container
// Get length and close the connection.
header('Content-Length: ' . ob_get_length());
header("Connection: close");
header('Connection: close');
ob_end_flush();
@ob_flush();
@@ -337,13 +423,33 @@ class Grav extends Container
/**
* Magic Catch All Function
* Used to call closures like measureTime on the instance.
*
* Used to call closures.
*
* Source: http://stackoverflow.com/questions/419804/closures-as-class-members
*/
public function __call($method, $args)
{
$closure = $this->$method;
call_user_func_array($closure, $args);
$closure = $this->{$method};
\call_user_func_array($closure, $args);
}
/**
* Measure how long it takes to do an action.
*
* @param string $timerId
* @param string $timerTitle
* @param callable $callback
* @return mixed Returns value returned by the callable.
*/
public function measureTime(string $timerId, string $timerTitle, callable $callback)
{
$debugger = $this['debugger'];
$debugger->startTimer($timerId, $timerTitle);
$result = $callback();
$debugger->stopTimer($timerId);
return $result;
}
/**
@@ -357,22 +463,15 @@ class Grav extends Container
{
$container = new static($values);
$container['grav'] = $container;
$container['debugger'] = new Debugger();
$debugger = $container['debugger'];
$container['grav'] = function (Container $container) {
user_error('Calling $grav[\'grav\'] or {{ grav.grav }} is deprecated since Grav 1.6, just use $grav or {{ grav }}', E_USER_DEPRECATED);
// closure that measures time by wrapping a function into startTimer and stopTimer
// The debugger can be passed to the closure. Should be more performant
// then to get it from the container all time.
$container->measureTime = function ($timerId, $timerTitle, $callback) use ($debugger) {
$debugger->startTimer($timerId, $timerTitle);
$callback();
$debugger->stopTimer($timerId);
return $container;
};
$container->measureTime('_services', 'Services', function () use ($container) {
$container->registerServices($container);
$container->registerServices();
});
return $container;
@@ -389,45 +488,20 @@ class Grav extends Container
protected function registerServices()
{
foreach (self::$diMap as $serviceKey => $serviceClass) {
if (is_int($serviceKey)) {
$this->registerServiceProvider($serviceClass);
if (\is_int($serviceKey)) {
$this->register(new $serviceClass);
} else {
$this->registerService($serviceKey, $serviceClass);
$this[$serviceKey] = function ($c) use ($serviceClass) {
return new $serviceClass($c);
};
}
}
}
/**
* Register a service provider with the container.
*
* @param string $serviceClass
*
* @return void
*/
protected function registerServiceProvider($serviceClass)
{
$this->register(new $serviceClass);
}
/**
* Register a service with the container.
*
* @param string $serviceKey
* @param string $serviceClass
*
* @return void
*/
protected function registerService($serviceKey, $serviceClass)
{
$this[$serviceKey] = function ($c) use ($serviceClass) {
return new $serviceClass($c);
};
}
/**
* This attempts to find media, other files, and download them
*
* @param $path
* @param string $path
*/
public function fallbackUrl($path)
{
@@ -453,7 +527,7 @@ class Grav extends Container
$path_parts = pathinfo($path);
/** @var Page $page */
/** @var PageInterface $page */
$page = $this['pages']->dispatch($path_parts['dirname'], true);
if ($page) {
@@ -466,8 +540,8 @@ class Grav extends Container
/** @var Medium $medium */
$medium = $media[$media_file];
foreach ($uri->query(null, true) as $action => $params) {
if (in_array($action, ImageMedium::$magic_actions)) {
call_user_func_array([&$medium, $action], explode(',', $params));
if (\in_array($action, ImageMedium::$magic_actions, true)) {
\call_user_func_array([&$medium, $action], explode(',', $params));
}
}
Utils::download($medium->path(), false);
@@ -486,7 +560,7 @@ class Grav extends Container
if ($extension) {
$download = true;
if (in_array(ltrim($extension, '.'), $config->get('system.media.unsupported_inline_types', []))) {
if (\in_array(ltrim($extension, '.'), $config->get('system.media.unsupported_inline_types', []), true)) {
$download = false;
}
Utils::download($page->path() . DIRECTORY_SEPARATOR . $uri->basename(), $download);
+7 -5
View File
@@ -1,15 +1,16 @@
<?php
/**
* @package Grav.Common
* @package Grav\Common
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common;
/**
* @deprecated 1.4 Use Grav::instance() instead
* @deprecated 1.4 Use Grav::instance() instead.
*/
trait GravTrait
{
@@ -17,15 +18,16 @@ trait GravTrait
/**
* @return Grav
* @deprecated 1.4 Use Grav::instance() instead.
*/
public static function getGrav()
{
user_error(__TRAIT__ . ' is deprecated since Grav 1.4, use Grav::instance() instead', E_USER_DEPRECATED);
if (!self::$grav) {
self::$grav = Grav::instance();
}
user_error(__TRAIT__ . ' is deprecated since Grav 1.4, use Grav::instance() instead', E_USER_DEPRECATED);
return self::$grav;
}
}
+58 -32
View File
@@ -1,17 +1,18 @@
<?php
/**
* @package Grav.Common.Helpers
* @package Grav\Common\Helpers
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Helpers;
class Base32 {
protected static $base32Chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
protected static $base32Lookup = array(
class Base32
{
protected static $base32Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
protected static $base32Lookup = [
0xFF,0xFF,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F, // '0', '1', '2', '3', '4', '5', '6', '7'
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, // '8', '9', ':', ';', '<', '=', '>', '?'
0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06, // '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G'
@@ -22,27 +23,31 @@ class Base32 {
0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E, // 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o'
0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16, // 'p', 'q', 'r', 's', 't', 'u', 'v', 'w'
0x17,0x18,0x19,0xFF,0xFF,0xFF,0xFF,0xFF // 'x', 'y', 'z', '{', '|', '}', '~', 'DEL'
);
];
/**
* Encode in Base32
*
* @param $bytes
* @param string $bytes
* @return string
*/
public static function encode( $bytes ) {
$i = 0; $index = 0; $digit = 0;
public static function encode($bytes)
{
$i = 0; $index = 0;
$base32 = '';
$bytes_len = strlen($bytes);
while( $i < $bytes_len ) {
$currByte = ord($bytes{$i});
$bytesLen = \strlen($bytes);
while ($i < $bytesLen) {
$currByte = \ord($bytes[$i]);
/* Is the current digit going to span a byte boundary? */
if( $index > 3 ) {
if( ($i + 1) < $bytes_len ) {
$nextByte = ord($bytes{$i+1});
if ($index > 3) {
if (($i + 1) < $bytesLen) {
$nextByte = \ord($bytes[$i+1]);
} else {
$nextByte = 0;
}
$digit = $currByte & (0xFF >> $index);
$index = ($index + 5) % 8;
$digit <<= $index;
@@ -51,9 +56,12 @@ class Base32 {
} else {
$digit = ($currByte >> (8 - ($index + 5))) & 0x1F;
$index = ($index + 5) % 8;
if( $index === 0 ) $i++;
if ($index === 0) {
$i++;
}
}
$base32 .= self::$base32Chars{$digit};
$base32 .= self::$base32Chars[$digit];
}
return $base32;
}
@@ -61,30 +69,42 @@ class Base32 {
/**
* Decode in Base32
*
* @param $base32
* @param string $base32
* @return string
*/
public static function decode( $base32 ) {
$bytes = array();
$base32_len = strlen($base32);
for( $i=$base32_len*5/8-1; $i>=0; --$i ) {
public static function decode($base32)
{
$bytes = [];
$base32Len = \strlen($base32);
$base32LookupLen = \count(self::$base32Lookup);
for ($i = $base32Len * 5 / 8 - 1; $i >= 0; --$i) {
$bytes[] = 0;
}
for( $i = 0, $index = 0, $offset = 0; $i < $base32_len; $i++ ) {
$lookup = ord($base32{$i}) - ord('0');
for ($i = 0, $index = 0, $offset = 0; $i < $base32Len; $i++) {
$lookup = \ord($base32[$i]) - \ord('0');
/* Skip chars outside the lookup table */
if( $lookup < 0 || $lookup >= count(self::$base32Lookup) ) {
if ($lookup < 0 || $lookup >= $base32LookupLen) {
continue;
}
$digit = self::$base32Lookup[$lookup];
/* If this digit is not in the table, ignore it */
if( $digit == 0xFF ) continue;
if( $index <= 3 ) {
if ($digit === 0xFF) {
continue;
}
if ($index <= 3) {
$index = ($index + 5) % 8;
if( $index == 0) {
if ($index === 0) {
$bytes[$offset] |= $digit;
$offset++;
if( $offset >= count($bytes) ) break;
if ($offset >= \count($bytes)) {
break;
}
} else {
$bytes[$offset] |= $digit << (8 - $index);
}
@@ -92,12 +112,18 @@ class Base32 {
$index = ($index + 5) % 8;
$bytes[$offset] |= ($digit >> $index);
$offset++;
if ($offset >= count($bytes) ) break;
if ($offset >= \count($bytes)) {
break;
}
$bytes[$offset] |= $digit << (8 - $index);
}
}
$bites = '';
foreach( $bytes as $byte ) $bites .= chr($byte);
foreach ($bytes as $byte) {
$bites .= \chr($byte);
}
return $bites;
}
}
+27 -239
View File
@@ -1,31 +1,28 @@
<?php
/**
* @package Grav.Common.Helpers
* @package Grav\Common\Helpers
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Helpers;
use Grav\Common\Grav;
use Grav\Common\Page\Page;
use Grav\Common\Uri;
use Grav\Common\Page\Interfaces\PageInterface;
use Grav\Common\Page\Markdown\Excerpts as ExcerptsObject;
use Grav\Common\Page\Medium\Medium;
use Grav\Common\Utils;
use RocketTheme\Toolbox\Event\Event;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
class Excerpts
{
/**
* Process Grav image media URL from HTML tag
*
* @param string $html HTML tag e.g. `<img src="image.jpg" />`
* @param Page $page The current page object
* @return string Returns final HTML string
* @param string $html HTML tag e.g. `<img src="image.jpg" />`
* @param PageInterface|null $page Page, defaults to the current page object
* @return string Returns final HTML string
*/
public static function processImageHtml($html, Page $page)
public static function processImageHtml($html, PageInterface $page = null)
{
$excerpt = static::getExcerptFromHtml($html, 'img');
@@ -79,7 +76,7 @@ class Excerpts
/**
* Rebuild HTML tag from an excerpt array
*
* @param $excerpt
* @param array $excerpt
* @return string
*/
public static function getHtmlFromExcerpt($excerpt)
@@ -110,253 +107,44 @@ class Excerpts
/**
* Process a Link excerpt
*
* @param $excerpt
* @param Page $page
* @param array $excerpt
* @param PageInterface|null $page Page, defaults to the current page object
* @param string $type
* @return mixed
*/
public static function processLinkExcerpt($excerpt, Page $page, $type = 'link')
public static function processLinkExcerpt($excerpt, PageInterface $page = null, $type = 'link')
{
$url = htmlspecialchars_decode(rawurldecode($excerpt['element']['attributes']['href']));
$excerpts = new ExcerptsObject($page);
$url_parts = static::parseUrl($url);
// If there is a query, then parse it and build action calls.
if (isset($url_parts['query'])) {
$actions = array_reduce(explode('&', $url_parts['query']), function ($carry, $item) {
$parts = explode('=', $item, 2);
$value = isset($parts[1]) ? rawurldecode($parts[1]) : true;
$carry[$parts[0]] = $value;
return $carry;
}, []);
// Valid attributes supported.
$valid_attributes = ['rel', 'target', 'id', 'class', 'classes'];
// Unless told to not process, go through actions.
if (array_key_exists('noprocess', $actions)) {
unset($actions['noprocess']);
} else {
// Loop through actions for the image and call them.
foreach ($actions as $attrib => $value) {
$key = $attrib;
if (in_array($attrib, $valid_attributes, true)) {
// support both class and classes.
if ($attrib === 'classes') {
$attrib = 'class';
}
$excerpt['element']['attributes'][$attrib] = str_replace(',', ' ', $value);
unset($actions[$key]);
}
}
}
$url_parts['query'] = http_build_query($actions, null, '&', PHP_QUERY_RFC3986);
}
// If no query elements left, unset query.
if (empty($url_parts['query'])) {
unset ($url_parts['query']);
}
// Set path to / if not set.
if (empty($url_parts['path'])) {
$url_parts['path'] = '';
}
// If scheme isn't http(s)..
if (!empty($url_parts['scheme']) && !in_array($url_parts['scheme'], ['http', 'https'])) {
// Handle custom streams.
if ($type !== 'image' && !empty($url_parts['stream']) && !empty($url_parts['path'])) {
$url_parts['path'] = Grav::instance()['base_url_relative'] . '/' . static::resolveStream("{$url_parts['scheme']}://{$url_parts['path']}");
unset($url_parts['stream'], $url_parts['scheme']);
}
$excerpt['element']['attributes']['href'] = Uri::buildUrl($url_parts);
return $excerpt;
}
// Handle paths and such.
$url_parts = Uri::convertUrl($page, $url_parts, $type);
// Build the URL from the component parts and set it on the element.
$excerpt['element']['attributes']['href'] = Uri::buildUrl($url_parts);
return $excerpt;
return $excerpts->processLinkExcerpt($excerpt, $type);
}
/**
* Process an image excerpt
*
* @param array $excerpt
* @param Page $page
* @return mixed
* @param PageInterface|null $page Page, defaults to the current page object
* @return array
*/
public static function processImageExcerpt(array $excerpt, Page $page)
public static function processImageExcerpt(array $excerpt, PageInterface $page = null)
{
$url = htmlspecialchars_decode(urldecode($excerpt['element']['attributes']['src']));
$url_parts = static::parseUrl($url);
$excerpts = new ExcerptsObject($page);
$media = null;
$filename = null;
if (!empty($url_parts['stream'])) {
$filename = $url_parts['scheme'] . '://' . (isset($url_parts['path']) ? $url_parts['path'] : '');
$media = $page->media();
} else {
// File is also local if scheme is http(s) and host matches.
$local_file = isset($url_parts['path'])
&& (empty($url_parts['scheme']) || in_array($url_parts['scheme'], ['http', 'https'], true))
&& (empty($url_parts['host']) || $url_parts['host'] === Grav::instance()['uri']->host());
if ($local_file) {
$filename = basename($url_parts['path']);
$folder = dirname($url_parts['path']);
// Get the local path to page media if possible.
if ($folder === $page->url(false, false, false)) {
// Get the media objects for this page.
$media = $page->media();
} else {
// see if this is an external page to this one
$base_url = rtrim(Grav::instance()['base_url_relative'] . Grav::instance()['pages']->base(), '/');
$page_route = '/' . ltrim(str_replace($base_url, '', $folder), '/');
/** @var Page $ext_page */
$ext_page = Grav::instance()['pages']->dispatch($page_route, true);
if ($ext_page) {
$media = $ext_page->media();
} else {
Grav::instance()->fireEvent('onMediaLocate', new Event(['route' => $page_route, 'media' => &$media]));
}
}
}
}
// If there is a media file that matches the path referenced..
if ($media && $filename && isset($media[$filename])) {
// Get the medium object.
/** @var Medium $medium */
$medium = $media[$filename];
// Process operations
$medium = static::processMediaActions($medium, $url_parts);
$element_excerpt = $excerpt['element']['attributes'];
$alt = isset($element_excerpt['alt']) ? $element_excerpt['alt'] : '';
$title = isset($element_excerpt['title']) ? $element_excerpt['title'] : '';
$class = isset($element_excerpt['class']) ? $element_excerpt['class'] : '';
$id = isset($element_excerpt['id']) ? $element_excerpt['id'] : '';
$excerpt['element'] = $medium->parsedownElement($title, $alt, $class, $id, true);
} else {
// Not a current page media file, see if it needs converting to relative.
$excerpt['element']['attributes']['src'] = Uri::buildUrl($url_parts);
}
return $excerpt;
return $excerpts->processImageExcerpt($excerpt);
}
/**
* Process media actions
*
* @param $medium
* @param $url
* @return mixed
* @param Medium $medium
* @param string|array $url
* @param PageInterface|null $page Page, defaults to the current page object
* @return Medium
*/
public static function processMediaActions($medium, $url)
public static function processMediaActions($medium, $url, PageInterface $page = null)
{
if (!is_array($url)) {
$url_parts = parse_url($url);
} else {
$url_parts = $url;
}
$excerpts = new ExcerptsObject($page);
$actions = [];
// if there is a query, then parse it and build action calls
if (isset($url_parts['query'])) {
$actions = array_reduce(explode('&', $url_parts['query']), function ($carry, $item) {
$parts = explode('=', $item, 2);
$value = isset($parts[1]) ? $parts[1] : null;
$carry[] = ['method' => $parts[0], 'params' => $value];
return $carry;
}, []);
}
if (Grav::instance()['config']->get('system.images.auto_fix_orientation')) {
$actions[] = ['method' => 'fixOrientation', 'params' => ''];
}
$defaults = Grav::instance()['config']->get('system.images.defaults');
if (is_array($defaults) && count($defaults)) {
foreach ($defaults as $method => $params) {
$actions[] = [
'method' => $method,
'params' => $params,
];
}
}
// loop through actions for the image and call them
foreach ($actions as $action) {
$matches = [];
if (preg_match('/\[(.*)\]/', $action['params'], $matches)) {
$args = [explode(',', $matches[1])];
} else {
$args = explode(',', $action['params']);
}
$medium = call_user_func_array([$medium, $action['method']], $args);
}
if (isset($url_parts['fragment'])) {
$medium->urlHash($url_parts['fragment']);
}
return $medium;
}
/**
* Variation of parse_url() which works also with local streams.
*
* @param string $url
* @return array|bool
*/
protected static function parseUrl($url)
{
$url_parts = Utils::multibyteParseUrl($url);
if (isset($url_parts['scheme'])) {
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
// Special handling for the streams.
if ($locator->schemeExists($url_parts['scheme'])) {
if (isset($url_parts['host'])) {
// Merge host and path into a path.
$url_parts['path'] = $url_parts['host'] . (isset($url_parts['path']) ? '/' . $url_parts['path'] : '');
unset($url_parts['host']);
}
$url_parts['stream'] = true;
}
}
return $url_parts;
}
protected static function resolveStream($url)
{
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
return $locator->isStream($url) ? ($locator->findResource($url, false) ?: $locator->findResource($url, false, true)) : $url;
return $excerpts->processMediaActions($medium, $url);
}
}
+4 -4
View File
@@ -1,15 +1,15 @@
<?php
/**
* @package Grav.Common.Helpers
* @package Grav\Common\Helpers
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Helpers;
use Grav\Common\Grav;
use SebastianBergmann\GlobalState\RuntimeException;
class Exif
{
@@ -17,7 +17,7 @@ class Exif
/**
* Exif constructor.
* @throws RuntimeException
* @throws \RuntimeException
*/
public function __construct()
{
@@ -0,0 +1,149 @@
<?php
/**
* @package Grav\Common\Helpers
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Helpers;
class LogViewer
{
protected $pattern = '/\[(?P<date>.*)\] (?P<logger>\w+).(?P<level>\w+): (?P<message>.*[^ ]+) (?P<context>[^ ]+) (?P<extra>[^ ]+)/';
/**
* Get the objects of a tailed file
*
* @param string $filepath
* @param int $lines
* @param bool $desc
* @return array
*/
public function objectTail($filepath, $lines = 1, $desc = true)
{
$data = $this->tail($filepath, $lines);
$tailed_log = explode(PHP_EOL, $data);
$line_objects = [];
foreach ($tailed_log as $line) {
$line_objects[] = $this->parse($line);
}
return $desc ? $line_objects : array_reverse($line_objects);
}
/**
* Optimized way to get just the last few entries of a log file
*
* @param string $filepath
* @param int $lines
* @return bool|string
*/
public function tail($filepath, $lines = 1) {
$f = @fopen($filepath, "rb");
if ($f === false) return false;
else $buffer = ($lines < 2 ? 64 : ($lines < 10 ? 512 : 4096));
fseek($f, -1, SEEK_END);
if (fread($f, 1) != "\n") $lines -= 1;
// Start reading
$output = '';
$chunk = '';
// While we would like more
while (ftell($f) > 0 && $lines >= 0) {
// Figure out how far back we should jump
$seek = min(ftell($f), $buffer);
// Do the jump (backwards, relative to where we are)
fseek($f, -$seek, SEEK_CUR);
// Read a chunk and prepend it to our output
$output = ($chunk = fread($f, $seek)) . $output;
// Jump back to where we started reading
fseek($f, -mb_strlen($chunk, '8bit'), SEEK_CUR);
// Decrease our line counter
$lines -= substr_count($chunk, "\n");
}
// While we have too many lines
// (Because of buffer size we might have read too many)
while ($lines++ < 0) {
// Find first newline and remove all text before that
$output = substr($output, strpos($output, "\n") + 1);
}
// Close file and return
fclose($f);
return trim($output);
}
/**
* Helper class to get level color
*
* @param string $level
* @return mixed|string
*/
public static function levelColor($level)
{
$colors = [
'DEBUG' => 'green',
'INFO' => 'cyan',
'NOTICE' => 'yellow',
'WARNING' => 'yellow',
'ERROR' => 'red',
'CRITICAL' => 'red',
'ALERT' => 'red',
'EMERGENCY' => 'magenta'
];
return $colors[$level] ?? 'white';
}
/**
* Parse a monolog row into array bits
*
* @param string $line
* @return array
*/
public function parse($line)
{
if( !is_string($line) || strlen($line) === 0) {
return array();
}
preg_match($this->pattern, $line, $data);
if (!isset($data['date'])) {
return array();
}
preg_match('/(.*)- Trace:(.*)/', $data['message'], $matches);
if (is_array($matches) && isset($matches[1])) {
$data['message'] = trim($matches[1]);
$data['trace'] = trim($matches[2]);
}
return array(
'date' => \DateTime::createFromFormat('Y-m-d H:i:s', $data['date']),
'logger' => $data['logger'],
'level' => $data['level'],
'message' => $data['message'],
'trace' => isset($data['trace']) ? $this->parseTrace($data['trace']) : null,
'context' => json_decode($data['context'], true),
'extra' => json_decode($data['extra'], true)
);
}
/**
* Parse text of trace into an array of lines
*
* @param string $trace
* @param int $rows
* @return array
*/
public static function parseTrace($trace, $rows = 10)
{
$lines = array_filter(preg_split('/#\d*/m', $trace));
return array_slice($lines, 0, $rows);
}
}
+138 -37
View File
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.Helpers
* @package Grav\Common\Helpers
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -30,7 +31,7 @@ class Truncator {
/**
* Safely truncates HTML by a given number of words.
* @param string $html Input HTML.
* @param integer $limit Limit to how many words we preserve.
* @param int $limit Limit to how many words we preserve.
* @param string $ellipsis String to use as ellipsis (if any).
* @return string Safe truncated HTML.
*/
@@ -40,13 +41,12 @@ class Truncator {
return $html;
}
$dom = self::htmlToDomDocument($html);
// Grab the body of our DOM.
$body = $dom->getElementsByTagName("body")->item(0);
$doc = self::htmlToDomDocument($html);
$container = $doc->getElementsByTagName('div')->item(0);
$container = $container->parentNode->removeChild($container);
// Iterate over words.
$words = new DOMWordsIterator($body);
$words = new DOMWordsIterator($container);
$truncated = false;
foreach ($words as $word) {
@@ -65,7 +65,7 @@ class Truncator {
$words[$offset][1] + strlen($words[$offset][0])
);
self::removeProceedingNodes($curNode, $body);
self::removeProceedingNodes($curNode, $container);
if (!empty($ellipsis)) {
self::insertEllipsis($curNode, $ellipsis);
@@ -80,32 +80,31 @@ class Truncator {
// Return original HTML if not truncated.
if ($truncated) {
return self::innerHTML($body);
} else {
return $html;
$html = self::getCleanedHtml($doc, $container);
}
return $html;
}
/**
* Safely truncates HTML by a given number of letters.
* @param string $html Input HTML.
* @param integer $limit Limit to how many letters we preserve.
* @param int $limit Limit to how many letters we preserve.
* @param string $ellipsis String to use as ellipsis (if any).
* @return string Safe truncated HTML.
*/
public static function truncateLetters($html, $limit = 0, $ellipsis = "")
public static function truncateLetters($html, $limit = 0, $ellipsis = '')
{
if ($limit <= 0) {
return $html;
}
$dom = self::htmlToDomDocument($html);
// Grab the body of our DOM.
$body = $dom->getElementsByTagName('body')->item(0);
$doc = self::htmlToDomDocument($html);
$container = $doc->getElementsByTagName('div')->item(0);
$container = $container->parentNode->removeChild($container);
// Iterate over letters.
$letters = new DOMLettersIterator($body);
$letters = new DOMLettersIterator($container);
$truncated = false;
foreach ($letters as $letter) {
@@ -114,7 +113,7 @@ class Truncator {
$currentText = $letters->currentTextPosition();
$currentText[0]->nodeValue = mb_substr($currentText[0]->nodeValue, 0, $currentText[1] + 1);
self::removeProceedingNodes($currentText[0], $body);
self::removeProceedingNodes($currentText[0], $container);
if (!empty($ellipsis)) {
self::insertEllipsis($currentText[0], $ellipsis);
@@ -128,10 +127,10 @@ class Truncator {
// Return original HTML if not truncated.
if ($truncated) {
return self::innerHTML($body);
} else {
return $html;
$html = self::getCleanedHtml($doc, $container);
}
return $html;
}
/**
@@ -142,7 +141,7 @@ class Truncator {
public static function htmlToDomDocument($html)
{
if (!$html) {
$html = '<p></p>';
$html = '';
}
// Transform multibyte entities which otherwise display incorrectly.
@@ -154,7 +153,7 @@ class Truncator {
// Instantiate new DOMDocument object, and then load in UTF-8 HTML.
$dom = new DOMDocument();
$dom->encoding = 'UTF-8';
$dom->loadHTML($html);
$dom->loadHTML("<div>$html</div>");
return $dom;
}
@@ -187,6 +186,27 @@ class Truncator {
}
}
/**
* Clean extra code
*
* @param DOMDocument $doc
* @param $container
* @return string
*/
private static function getCleanedHTML(DOMDocument $doc, $container)
{
while ($doc->firstChild) {
$doc->removeChild($doc->firstChild);
}
while ($container->firstChild ) {
$doc->appendChild($container->firstChild);
}
$html = trim($doc->saveHTML());
return $html;
}
/**
* Inserts an ellipsis
* @param DOMNode|DOMElement $domNode Element to insert after.
@@ -214,21 +234,102 @@ class Truncator {
}
/**
* Returns the innerHTML of a particular DOMElement
*
* @param $element
* @return string
*/
private static function innerHTML($element) {
$innerHTML = '';
$children = $element->childNodes;
foreach ($children as $child)
{
$tmp_dom = new DOMDocument();
$tmp_dom->appendChild($tmp_dom->importNode($child, true));
$innerHTML.=trim($tmp_dom->saveHTML());
public function truncate(
$text,
$length = 100,
$ending = '...',
$exact = false,
$considerHtml = true
) {
if ($considerHtml) {
// if the plain text is shorter than the maximum length, return the whole text
if (strlen(preg_replace('/<.*?>/', '', $text)) <= $length) {
return $text;
}
// splits all html-tags to scanable lines
preg_match_all('/(<.+?>)?([^<>]*)/s', $text, $lines, PREG_SET_ORDER);
$total_length = strlen($ending);
$open_tags = array();
$truncate = '';
foreach ($lines as $line_matchings) {
// if there is any html-tag in this line, handle it and add it (uncounted) to the output
if (!empty($line_matchings[1])) {
// if it's an "empty element" with or without xhtml-conform closing slash
if (preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is', $line_matchings[1])) {
// do nothing
// if tag is a closing tag
} else if (preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $line_matchings[1], $tag_matchings)) {
// delete tag from $open_tags list
$pos = array_search($tag_matchings[1], $open_tags);
if ($pos !== false) {
unset($open_tags[$pos]);
}
// if tag is an opening tag
} else if (preg_match('/^<\s*([^\s>!]+).*?>$/s', $line_matchings[1], $tag_matchings)) {
// add tag to the beginning of $open_tags list
array_unshift($open_tags, strtolower($tag_matchings[1]));
}
// add html-tag to $truncate'd text
$truncate .= $line_matchings[1];
}
// calculate the length of the plain text part of the line; handle entities as one character
$content_length = strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|[0-9a-f]{1,6};/i', ' ', $line_matchings[2]));
if ($total_length+$content_length> $length) {
// the number of characters which are left
$left = $length - $total_length;
$entities_length = 0;
// search for html entities
if (preg_match_all('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|[0-9a-f]{1,6};/i', $line_matchings[2], $entities, PREG_OFFSET_CAPTURE)) {
// calculate the real length of all entities in the legal range
foreach ($entities[0] as $entity) {
if ($entity[1]+1-$entities_length <= $left) {
$left--;
$entities_length += strlen($entity[0]);
} else {
// no more characters left
break;
}
}
}
$truncate .= substr($line_matchings[2], 0, $left+$entities_length);
// maximum lenght is reached, so get off the loop
break;
} else {
$truncate .= $line_matchings[2];
$total_length += $content_length;
}
// if the maximum length is reached, get off the loop
if($total_length>= $length) {
break;
}
}
} else {
if (strlen($text) <= $length) {
return $text;
} else {
$truncate = substr($text, 0, $length - strlen($ending));
}
}
return $innerHTML;
// if the words shouldn't be cut in the middle...
if (!$exact) {
// ...search the last occurance of a space...
$spacepos = strrpos($truncate, ' ');
if (isset($spacepos)) {
// ...and cut the text in this position
$truncate = substr($truncate, 0, $spacepos);
}
}
// add the defined ending to the text
$truncate .= $ending;
if($considerHtml) {
// close all unclosed html-tags
foreach ($open_tags as $tag) {
$truncate .= '</' . $tag . '>';
}
}
return $truncate;
}
}
@@ -0,0 +1,89 @@
<?php
/**
* @package Grav\Common\Helpers
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Helpers;
use Grav\Common\Grav;
use RocketTheme\Toolbox\File\MarkdownFile;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
use Symfony\Component\Yaml\Yaml;
class YamlLinter
{
public static function lint()
{
$errors = static::lintConfig();
$errors = $errors + static::lintPages();
$errors = $errors + static::lintBlueprints();
return $errors;
}
public static function lintPages()
{
return static::recurseFolder('page://');
}
public static function lintConfig()
{
return static::recurseFolder('config://');
}
public static function lintBlueprints()
{
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
$current_theme = Grav::instance()['config']->get('system.pages.theme');
$theme_path = 'themes://' . $current_theme . '/blueprints';
$locator->addPath('blueprints', '', [$theme_path]);
return static::recurseFolder('blueprints://');
}
public static function recurseFolder($path, $extensions = 'md|yaml')
{
$lint_errors = [];
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
$flags = \RecursiveDirectoryIterator::SKIP_DOTS;
if ($locator->isStream($path)) {
$directory = $locator->getRecursiveIterator($path, $flags);
} else {
$directory = new \RecursiveDirectoryIterator($path, $flags);
}
$recursive = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::SELF_FIRST);
$iterator = new \RegexIterator($recursive, '/^.+\.'.$extensions.'$/i');
/** @var \RecursiveDirectoryIterator $file */
foreach ($iterator as $filepath => $file) {
try {
Yaml::parse(static::extractYaml($filepath));
} catch (\Exception $e) {
$lint_errors[str_replace(GRAV_ROOT, '', $filepath)] = $e->getMessage();
}
}
return $lint_errors;
}
protected static function extractYaml($path)
{
$extension = pathinfo($path, PATHINFO_EXTENSION);
if ($extension === 'md') {
$file = MarkdownFile::instance($path);
$contents = $file->frontmatter();
} else {
$contents = file_get_contents($path);
}
return $contents;
}
}
+61 -66
View File
@@ -1,36 +1,35 @@
<?php
/**
* @package Grav.Common
* @package Grav\Common
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common;
use Grav\Common\Grav;
/**
* This file was originally part of the Akelos Framework
*/
class Inflector
{
protected $plural;
protected $singular;
protected $uncountable;
protected $irregular;
protected $ordinals;
protected static $plural;
protected static $singular;
protected static $uncountable;
protected static $irregular;
protected static $ordinals;
public function init()
public static function init()
{
if (empty($this->plural)) {
if (empty(static::$plural)) {
$language = Grav::instance()['language'];
$this->plural = $language->translate('INFLECTOR_PLURALS', null, true) ?: [];
$this->singular = $language->translate('INFLECTOR_SINGULAR', null, true) ?: [];
$this->uncountable = $language->translate('INFLECTOR_UNCOUNTABLE', null, true) ?: [];
$this->irregular = $language->translate('INFLECTOR_IRREGULAR', null, true) ?: [];
$this->ordinals = $language->translate('INFLECTOR_ORDINALS', null, true) ?: [];
static::$plural = $language->translate('GRAV.INFLECTOR_PLURALS', null, true) ?: [];
static::$singular = $language->translate('GRAV.INFLECTOR_SINGULAR', null, true) ?: [];
static::$uncountable = $language->translate('GRAV.INFLECTOR_UNCOUNTABLE', null, true) ?: [];
static::$irregular = $language->translate('GRAV.INFLECTOR_IRREGULAR', null, true) ?: [];
static::$ordinals = $language->translate('GRAV.INFLECTOR_ORDINALS', null, true) ?: [];
}
}
@@ -42,29 +41,29 @@ class Inflector
*
* @return string Plural noun
*/
public function pluralize($word, $count = 2)
public static function pluralize($word, $count = 2)
{
$this->init();
static::init();
if ($count == 1) {
if ((int)$count === 1) {
return $word;
}
$lowercased_word = strtolower($word);
foreach ($this->uncountable as $_uncountable) {
if (substr($lowercased_word, (-1 * strlen($_uncountable))) == $_uncountable) {
foreach (static::$uncountable as $_uncountable) {
if (substr($lowercased_word, -1 * strlen($_uncountable)) === $_uncountable) {
return $word;
}
}
foreach ($this->irregular as $_plural => $_singular) {
foreach (static::$irregular as $_plural => $_singular) {
if (preg_match('/(' . $_plural . ')$/i', $word, $arr)) {
return preg_replace('/(' . $_plural . ')$/i', substr($arr[0], 0, 1) . substr($_singular, 1), $word);
}
}
foreach ($this->plural as $rule => $replacement) {
foreach (static::$plural as $rule => $replacement) {
if (preg_match($rule, $word)) {
return preg_replace($rule, $replacement, $word);
}
@@ -82,28 +81,28 @@ class Inflector
*
* @return string Singular noun.
*/
public function singularize($word, $count = 1)
public static function singularize($word, $count = 1)
{
$this->init();
static::init();
if ($count != 1) {
if ((int)$count !== 1) {
return $word;
}
$lowercased_word = strtolower($word);
foreach ($this->uncountable as $_uncountable) {
if (substr($lowercased_word, (-1 * strlen($_uncountable))) == $_uncountable) {
foreach (static::$uncountable as $_uncountable) {
if (substr($lowercased_word, -1 * strlen($_uncountable)) === $_uncountable) {
return $word;
}
}
foreach ($this->irregular as $_plural => $_singular) {
foreach (static::$irregular as $_plural => $_singular) {
if (preg_match('/(' . $_singular . ')$/i', $word, $arr)) {
return preg_replace('/(' . $_singular . ')$/i', substr($arr[0], 0, 1) . substr($_plural, 1), $word);
}
}
foreach ($this->singular as $rule => $replacement) {
foreach (static::$singular as $rule => $replacement) {
if (preg_match($rule, $word)) {
return preg_replace($rule, $replacement, $word);
}
@@ -129,11 +128,11 @@ class Inflector
*
* @return string Text formatted as title
*/
public function titleize($word, $uppercase = '')
public static function titleize($word, $uppercase = '')
{
$uppercase = $uppercase == 'first' ? 'ucfirst' : 'ucwords';
$uppercase = $uppercase === 'first' ? 'ucfirst' : 'ucwords';
return $uppercase($this->humanize($this->underscorize($word)));
return $uppercase(static::humanize(static::underscorize($word)));
}
/**
@@ -149,7 +148,7 @@ class Inflector
*
* @return string UpperCamelCasedWord
*/
public function camelize($word)
public static function camelize($word)
{
return str_replace(' ', '', ucwords(preg_replace('/[^A-Z^a-z^0-9]+/', ' ', $word)));
}
@@ -166,7 +165,7 @@ class Inflector
*
* @return string Underscored word
*/
public function underscorize($word)
public static function underscorize($word)
{
$regex1 = preg_replace('/([A-Z]+)([A-Z][a-z])/', '\1_\2', $word);
$regex2 = preg_replace('/([a-zd])([A-Z])/', '\1_\2', $regex1);
@@ -187,7 +186,7 @@ class Inflector
*
* @return string hyphenized word
*/
public function hyphenize($word)
public static function hyphenize($word)
{
$regex1 = preg_replace('/([A-Z]+)([A-Z][a-z])/', '\1-\2', $word);
$regex2 = preg_replace('/([a-z])([A-Z])/', '\1-\2', $regex1);
@@ -213,9 +212,9 @@ class Inflector
*
* @return string Human-readable word
*/
public function humanize($word, $uppercase = '')
public static function humanize($word, $uppercase = '')
{
$uppercase = $uppercase == 'all' ? 'ucwords' : 'ucfirst';
$uppercase = $uppercase === 'all' ? 'ucwords' : 'ucfirst';
return $uppercase(str_replace('_', ' ', preg_replace('/_id$/', '', $word)));
}
@@ -233,9 +232,9 @@ class Inflector
*
* @return string Returns a lowerCamelCasedWord
*/
public function variablize($word)
public static function variablize($word)
{
$word = $this->camelize($word);
$word = static::camelize($word);
return strtolower($word[0]) . substr($word, 1);
}
@@ -252,9 +251,9 @@ class Inflector
*
* @return string plural_table_name
*/
public function tableize($class_name)
public static function tableize($class_name)
{
return $this->pluralize($this->underscorize($class_name));
return static::pluralize(static::underscorize($class_name));
}
/**
@@ -269,9 +268,9 @@ class Inflector
*
* @return string SingularClassName
*/
public function classify($table_name)
public static function classify($table_name)
{
return $this->camelize($this->singularize($table_name));
return static::camelize(static::singularize($table_name));
}
/**
@@ -279,31 +278,27 @@ class Inflector
*
* This method converts 13 to 13th, 2 to 2nd ...
*
* @param integer $number Number to get its ordinal value
* @param int $number Number to get its ordinal value
*
* @return string Ordinal representation of given string.
*/
public function ordinalize($number)
public static function ordinalize($number)
{
$this->init();
static::init();
if (in_array(($number % 100), range(11, 13))) {
return $number . $this->ordinals['default'];
} else {
switch (($number % 10)) {
case 1:
return $number . $this->ordinals['first'];
break;
case 2:
return $number . $this->ordinals['second'];
break;
case 3:
return $number . $this->ordinals['third'];
break;
default:
return $number . $this->ordinals['default'];
break;
}
if (\in_array($number % 100, range(11, 13), true)) {
return $number . static::$ordinals['default'];
}
switch ($number % 10) {
case 1:
return $number . static::$ordinals['first'];
case 2:
return $number . static::$ordinals['second'];
case 3:
return $number . static::$ordinals['third'];
default:
return $number . static::$ordinals['default'];
}
}
@@ -314,7 +309,7 @@ class Inflector
*
* @return int
*/
public function monthize($days)
public static function monthize($days)
{
$now = new \DateTime();
$end = new \DateTime();
@@ -325,7 +320,7 @@ class Inflector
// handle years
if ($diff->y > 0) {
$diff->m = $diff->m + 12 * $diff->y;
$diff->m += 12 * $diff->y;
}
return $diff->m;
+15 -13
View File
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common
* @package Grav\Common
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -34,7 +35,7 @@ class Iterator implements \ArrayAccess, \Iterator, \Countable, \Serializable
*/
public function __call($key, $args)
{
return (isset($this->items[$key])) ? $this->items[$key] : null;
return $this->items[$key] ?? null;
}
/**
@@ -43,8 +44,8 @@ class Iterator implements \ArrayAccess, \Iterator, \Countable, \Serializable
public function __clone()
{
foreach ($this as $key => $value) {
if (is_object($value)) {
$this->$key = clone $this->$key;
if (\is_object($value)) {
$this->{$key} = clone $this->{$key};
}
}
}
@@ -62,7 +63,7 @@ class Iterator implements \ArrayAccess, \Iterator, \Countable, \Serializable
/**
* Remove item from the list.
*
* @param $key
* @param string $key
*/
public function remove($key)
{
@@ -90,7 +91,7 @@ class Iterator implements \ArrayAccess, \Iterator, \Countable, \Serializable
{
$items = array_keys($this->items);
return (isset($items[$key])) ? $this->offsetGet($items[$key]) : false;
return isset($items[$key]) ? $this->offsetGet($items[$key]) : false;
}
/**
@@ -175,7 +176,7 @@ class Iterator implements \ArrayAccess, \Iterator, \Countable, \Serializable
*/
public function slice($offset, $length = null)
{
$this->items = array_slice($this->items, $offset, $length);
$this->items = \array_slice($this->items, $offset, $length);
return $this;
}
@@ -189,8 +190,9 @@ class Iterator implements \ArrayAccess, \Iterator, \Countable, \Serializable
*/
public function random($num = 1)
{
if ($num > count($this->items)) {
$num = count($this->items);
$count = \count($this->items);
if ($num > $count) {
$num = $count;
}
$this->items = array_intersect_key($this->items, array_flip((array)array_rand($this->items, $num)));
@@ -227,8 +229,8 @@ class Iterator implements \ArrayAccess, \Iterator, \Countable, \Serializable
{
foreach ($this->items as $key => $value) {
if (
($callback && !call_user_func($callback, $value, $key)) ||
(!$callback && !(bool)$value)
(!$callback && !(bool)$value) ||
($callback && !$callback($value, $key))
) {
unset($this->items[$key]);
}
@@ -251,7 +253,7 @@ class Iterator implements \ArrayAccess, \Iterator, \Countable, \Serializable
*/
public function sort(callable $callback = null, $desc = false)
{
if (!$callback || !is_callable($callback)) {
if (!$callback || !\is_callable($callback)) {
return $this;
}
+95 -65
View File
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.Language
* @package Grav\Common\Language
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -10,11 +11,16 @@ namespace Grav\Common\Language;
use Grav\Common\Grav;
use Grav\Common\Config\Config;
use Negotiation\AcceptLanguage;
use Negotiation\LanguageNegotiator;
class Language
{
protected $grav;
protected $enabled = true;
/**
* @var array
*/
protected $languages = [];
protected $page_extensions = [];
protected $fallback_languages = [];
@@ -45,7 +51,14 @@ class Language
*/
public function init()
{
$this->default = reset($this->languages);
$default = $this->config->get('system.languages.default_lang');
if (isset($default) && $this->validate($default)) {
$this->default = $default;
} else {
$this->default = reset($this->languages);
}
$this->page_extensions = null;
if (empty($this->languages)) {
$this->enabled = false;
@@ -75,7 +88,7 @@ class Language
/**
* Sets the current supported languages manually
*
* @param $langs
* @param array $langs
*/
public function setLanguages($langs)
{
@@ -91,18 +104,24 @@ class Language
public function getAvailable()
{
$languagesArray = $this->languages; //Make local copy
$languagesArray = array_map(function($value) {
return preg_quote($value);
}, $languagesArray);
sort($languagesArray);
return implode('|', array_reverse($languagesArray));
}
/**
* Gets language, active if set, else default
*
* @return mixed
* @return string
*/
public function getLanguage()
{
return $this->active ? $this->active : $this->default;
return $this->active ?: $this->default;
}
/**
@@ -118,7 +137,7 @@ class Language
/**
* Sets default language manually
*
* @param $lang
* @param string $lang
*
* @return bool
*/
@@ -136,7 +155,7 @@ class Language
/**
* Gets current active language
*
* @return mixed
* @return string
*/
public function getActive()
{
@@ -146,9 +165,9 @@ class Language
/**
* Sets active language manually
*
* @param $lang
* @param string $lang
*
* @return bool
* @return string|bool
*/
public function setActive($lang)
{
@@ -164,9 +183,9 @@ class Language
/**
* Sets the active language based on the first part of the URL
*
* @param $uri
* @param string $uri
*
* @return mixed
* @return string
*/
public function setActiveFromUri($uri)
{
@@ -189,29 +208,24 @@ class Language
}
} else {
// Try getting language from the session, else no active.
if (isset($this->grav['session']) && $this->grav['session']->isStarted()
&& $this->config->get('system.languages.session_store_active', true)) {
if (isset($this->grav['session']) && $this->grav['session']->isStarted() &&
$this->config->get('system.languages.session_store_active', true)) {
$this->active = $this->grav['session']->active_language ?: null;
}
// if still null, try from http_accept_language header
if ($this->active === null && $this->config->get('system.languages.http_accept_language')) {
$preferred = $this->getBrowserLanguages();
foreach ($preferred as $lang) {
if ($this->validate($lang)) {
$this->active = $lang;
break;
}
if ($this->active === null &&
$this->config->get('system.languages.http_accept_language') &&
$accept = $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? false) {
$negotiator = new LanguageNegotiator();
$best_language = $negotiator->getBest($accept, $this->languages);
if ($best_language instanceof AcceptLanguage) {
$this->active = $best_language->getType();
} else {
$this->active = $this->getDefault();
}
// Repeat if not found, try base language only - fixes Safari sending the language code always
// with a locale (e.g. it-it or fr-fr).
foreach ($preferred as $lang) {
$lang = substr($lang, 0, 2);
if ($this->validate($lang)) {
$this->active = $lang;
break;
}
}
}
}
}
@@ -220,9 +234,9 @@ class Language
}
/**
* Get's a URL prefix based on configuration
* Get a URL prefix based on configuration
*
* @param null $lang
* @param string|null $lang
* @return string
*/
public function getLanguageURLPrefix($lang = null)
@@ -238,7 +252,7 @@ class Language
/**
* Test to see if language is default and language should be included in the URL
*
* @param null $lang
* @param string|null $lang
* @return bool
*/
public function isIncludeDefaultLanguage($lang = null)
@@ -248,11 +262,7 @@ class Language
$lang = $this->getLanguage();
}
if ($this->default == $lang && $this->config->get('system.languages.include_default_lang') === false) {
return false;
} else {
return true;
}
return !($this->default === $lang && $this->config->get('system.languages.include_default_lang') === false);
}
/**
@@ -276,7 +286,7 @@ class Language
public function getFallbackPageExtensions($file_ext = null)
{
if (empty($this->page_extensions)) {
if (empty($file_ext)) {
if (!$file_ext) {
$file_ext = CONTENT_EXT;
}
@@ -288,12 +298,19 @@ class Language
if ($this->active) {
$active_extension = '.' . $this->active . $file_ext;
$key = array_search($active_extension, $valid_lang_extensions);
unset($valid_lang_extensions[$key]);
array_unshift($valid_lang_extensions, $active_extension);
}
$key = \array_search($active_extension, $valid_lang_extensions, true);
$this->page_extensions = array_merge($valid_lang_extensions, (array)$file_ext);
// Default behavior is to find any language other than active
if ($this->config->get('system.languages.pages_fallback_only')) {
$slice = \array_slice($valid_lang_extensions, 0, $key+1);
$valid_lang_extensions = array_reverse($slice);
} else {
unset($valid_lang_extensions[$key]);
array_unshift($valid_lang_extensions, $active_extension);
}
}
$valid_lang_extensions[] = $file_ext;
$this->page_extensions = $valid_lang_extensions;
} else {
$this->page_extensions = (array)$file_ext;
}
@@ -313,7 +330,8 @@ class Language
* $this->grav['pages']->init();
* ```
*/
public function resetFallbackPageExtensions() {
public function resetFallbackPageExtensions()
{
$this->page_extensions = null;
}
@@ -330,7 +348,7 @@ class Language
if ($this->active) {
$active_extension = $this->active;
$key = array_search($active_extension, $fallback_languages);
$key = \array_search($active_extension, $fallback_languages, true);
unset($fallback_languages[$key]);
array_unshift($fallback_languages, $active_extension);
}
@@ -346,23 +364,19 @@ class Language
/**
* Ensures the language is valid and supported
*
* @param $lang
* @param string $lang
*
* @return bool
*/
public function validate($lang)
{
if (in_array($lang, $this->languages)) {
return true;
}
return false;
return \in_array($lang, $this->languages, true);
}
/**
* Translate a key and possibly arguments into a string using current lang and fallbacks
*
* @param mixed $args The first argument is the lookup key value
* @param string|array $args The first argument is the lookup key value
* Other arguments can be passed and replaced in the translation with sprintf syntax
* @param array $languages
* @param bool $array_support
@@ -372,7 +386,7 @@ class Language
*/
public function translate($args, array $languages = null, $array_support = false, $html_out = false)
{
if (is_array($args)) {
if (\is_array($args)) {
$lookup = array_shift($args);
} else {
$lookup = $args;
@@ -396,28 +410,28 @@ class Language
$translation = $this->getTranslation($lang, $lookup, $array_support);
if ($translation) {
if (count($args) >= 1) {
if (\count($args) >= 1) {
return vsprintf($translation, $args);
} else {
return $translation;
}
return $translation;
}
}
}
if ($html_out) {
return '<span class="untranslated">' . $lookup . '</span>';
} else {
return $lookup;
}
return $lookup;
}
/**
* Translate Array
*
* @param $key
* @param $index
* @param null $languages
* @param string $key
* @param string $index
* @param array|null $languages
* @param bool $html_out
*
* @return string
@@ -447,9 +461,9 @@ class Language
if ($html_out) {
return '<span class="untranslated">' . $key . '[' . $index . ']</span>';
} else {
return $key . '[' . $index . ']';
}
return $key . '[' . $index . ']';
}
/**
@@ -475,11 +489,13 @@ class Language
* Get the browser accepted languages
*
* @param array $accept_langs
*
* @return array
* @deprecated 1.6 No longer used - using content negotiation.
*/
public function getBrowserLanguages($accept_langs = [])
{
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, no longer used', E_USER_DEPRECATED);
if (empty($this->http_accept_language)) {
if (empty($accept_langs) && isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
$accept_langs = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
@@ -487,6 +503,8 @@ class Language
return $accept_langs;
}
$langs = [];
foreach (explode(',', $accept_langs) as $k => $pref) {
// split $pref again by ';q='
// and decorate the language entries by inverted position
@@ -504,4 +522,16 @@ class Language
return $this->http_accept_language;
}
/**
* Accessible wrapper to LanguageCodes
*
* @param string $code
* @param string $type
* @return bool
*/
public function getLanguageCode($code, $type = 'name')
{
return LanguageCodes::get($code, $type);
}
}
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.Language
* @package Grav\Common\Language
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -179,10 +180,7 @@ class LanguageCodes
public static function isRtl($code)
{
if (static::getOrientation($code) === 'rtl') {
return true;
}
return false;
return static::getOrientation($code) === 'rtl';
}
public static function getNames(array $keys)
@@ -196,7 +194,7 @@ class LanguageCodes
return $results;
}
protected static function get($code, $type)
public static function get($code, $type)
{
if (isset(static::$codes[$code][$type])) {
return static::$codes[$code][$type];
+19 -6
View File
@@ -1,13 +1,17 @@
<?php
/**
* @package Grav.Common.Markdown
* @package Grav\Common\Markdown
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Markdown;
use Grav\Common\Page\Interfaces\PageInterface;
use Grav\Common\Page\Markdown\Excerpts;
class Parsedown extends \Parsedown
{
use ParsedownGravTrait;
@@ -15,12 +19,21 @@ class Parsedown extends \Parsedown
/**
* Parsedown constructor.
*
* @param $page
* @param $defaults
* @param Excerpts|null $excerpts
* @param array|null $defaults
*/
public function __construct($page, $defaults)
public function __construct($excerpts = null, $defaults = null)
{
$this->init($page, $defaults);
if (!$excerpts || $excerpts instanceof PageInterface || null !== $defaults) {
// Deprecated in Grav 1.6.10
if ($defaults) {
$defaults = ['markdown' => $defaults];
}
$excerpts = new Excerpts($excerpts, $defaults);
user_error(__CLASS__ . '::' . __FUNCTION__ . '($page, $defaults) is deprecated since Grav 1.6.10, use new ' . __CLASS__ . '(new ' . Excerpts::class . '($page, [\'markdown\' => $defaults])) instead.', E_USER_DEPRECATED);
}
$this->init($excerpts, $defaults);
}
}
@@ -1,13 +1,17 @@
<?php
/**
* @package Grav.Common.Markdown
* @package Grav\Common\Markdown
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Markdown;
use Grav\Common\Page\Interfaces\PageInterface;
use Grav\Common\Page\Markdown\Excerpts;
class ParsedownExtra extends \ParsedownExtra
{
use ParsedownGravTrait;
@@ -15,14 +19,23 @@ class ParsedownExtra extends \ParsedownExtra
/**
* ParsedownExtra constructor.
*
* @param $page
* @param $defaults
* @param Excerpts|null $excerpts
* @param array|null $defaults
* @throws \Exception
*/
public function __construct($page, $defaults)
public function __construct($excerpts = null, $defaults = null)
{
if (!$excerpts || $excerpts instanceof PageInterface || null !== $defaults) {
// Deprecated in Grav 1.6.10
if ($defaults) {
$defaults = ['markdown' => $defaults];
}
$excerpts = new Excerpts($excerpts, $defaults);
user_error(__CLASS__ . '::' . __FUNCTION__ . '($page, $defaults) is deprecated since Grav 1.6.10, use new ' . __CLASS__ . '(new ' . Excerpts::class . '($page, [\'markdown\' => $defaults])) instead.', E_USER_DEPRECATED);
}
parent::__construct();
$this->init($page, $defaults);
$this->init($excerpts, $defaults);
}
}
@@ -1,22 +1,21 @@
<?php
/**
* @package Grav.Common.Markdown
* @package Grav\Common\Markdown
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Markdown;
use Grav\Common\Grav;
use Grav\Common\Helpers\Excerpts;
use Grav\Common\Page\Page;
use RocketTheme\Toolbox\Event\Event;
use Grav\Common\Page\Markdown\Excerpts;
use Grav\Common\Page\Interfaces\PageInterface;
trait ParsedownGravTrait
{
/** @var Page $page */
protected $page;
/** @var Excerpts */
protected $excerpts;
protected $special_chars;
protected $twig_link_regex = '/\!*\[(?:.*)\]\((\{([\{%#])\s*(.*?)\s*(?:\2|\})\})\)/';
@@ -27,38 +26,59 @@ trait ParsedownGravTrait
/**
* Initialization function to setup key variables needed by the MarkdownGravLinkTrait
*
* @param $page
* @param $defaults
* @param PageInterface|Excerpts|null $excerpts
* @param array|null $defaults
*/
protected function init($page, $defaults)
protected function init($excerpts = null, $defaults = null)
{
$grav = Grav::instance();
$this->page = $page;
$this->BlockTypes['{'] [] = 'TwigTag';
$this->special_chars = ['>' => 'gt', '<' => 'lt', '"' => 'quot'];
if ($defaults === null) {
$defaults = Grav::instance()['config']->get('system.pages.markdown');
if (!$excerpts || $excerpts instanceof PageInterface) {
// Deprecated in Grav 1.6.10
if ($defaults) {
$defaults = ['markdown' => $defaults];
}
$this->excerpts = new Excerpts($excerpts, $defaults);
user_error(__CLASS__ . '::' . __FUNCTION__ . '($page, $defaults) is deprecated since Grav 1.6.10, use ->init(new ' . Excerpts::class . '($page, [\'markdown\' => $defaults])) instead.', E_USER_DEPRECATED);
} else {
$this->excerpts = $excerpts;
}
$this->setBreaksEnabled($defaults['auto_line_breaks']);
$this->setUrlsLinked($defaults['auto_url_links']);
$this->setMarkupEscaped($defaults['escape_markup']);
$this->setSpecialChars($defaults['special_chars']);
$this->BlockTypes['{'][] = 'TwigTag';
$this->special_chars = ['>' => 'gt', '<' => 'lt', '"' => 'quot'];
$grav->fireEvent('onMarkdownInitialized', new Event(['markdown' => $this]));
$defaults = $this->excerpts->getConfig();
if (isset($defaults['markdown']['auto_line_breaks'])) {
$this->setBreaksEnabled($defaults['markdown']['auto_line_breaks']);
}
if (isset($defaults['markdown']['auto_url_links'])) {
$this->setUrlsLinked($defaults['markdown']['auto_url_links']);
}
if (isset($defaults['markdown']['escape_markup'])) {
$this->setMarkupEscaped($defaults['markdown']['escape_markup']);
}
if (isset($defaults['markdown']['special_chars'])) {
$this->setSpecialChars($defaults['markdown']['special_chars']);
}
$this->excerpts->fireInitializedEvent($this);
}
/**
* @return Excerpts
*/
public function getExcerpts()
{
return $this->excerpts;
}
/**
* Be able to define a new Block type or override an existing one
*
* @param $type
* @param $tag
* @param string $type
* @param string $tag
* @param bool $continuable
* @param bool $completable
* @param $index
* @param int|null $index
*/
public function addBlockType($type, $tag, $continuable = false, $completable = false, $index = null)
{
@@ -87,9 +107,9 @@ trait ParsedownGravTrait
/**
* Be able to define a new Inline type or override an existing one
*
* @param $type
* @param $tag
* @param $index
* @param string $type
* @param string $tag
* @param int|null $index
*/
public function addInlineType($type, $tag, $index = null)
{
@@ -107,13 +127,14 @@ trait ParsedownGravTrait
/**
* Overrides the default behavior to allow for plugin-provided blocks to be continuable
*
* @param $Type
* @param string $Type
*
* @return bool
*/
protected function isBlockContinuable($Type)
{
$continuable = \in_array($Type, $this->continuable_blocks) || method_exists($this, 'block' . $Type . 'Continue');
$continuable = \in_array($Type, $this->continuable_blocks, true)
|| method_exists($this, 'block' . $Type . 'Continue');
return $continuable;
}
@@ -121,13 +142,14 @@ trait ParsedownGravTrait
/**
* Overrides the default behavior to allow for plugin-provided blocks to be completable
*
* @param $Type
* @param string $Type
*
* @return bool
*/
protected function isBlockCompletable($Type)
{
$completable = \in_array($Type, $this->completable_blocks) || method_exists($this, 'block' . $Type . 'Complete');
$completable = \in_array($Type, $this->completable_blocks, true)
|| method_exists($this, 'block' . $Type . 'Complete');
return $completable;
}
@@ -148,7 +170,7 @@ trait ParsedownGravTrait
/**
* Setter for special chars
*
* @param $special_chars
* @param array $special_chars
*
* @return $this
*/
@@ -209,7 +231,7 @@ trait ParsedownGravTrait
// if this is an image process it
if (isset($excerpt['element']['attributes']['src'])) {
$excerpt = Excerpts::processImageExcerpt($excerpt, $this->page);
$excerpt = $this->excerpts->processImageExcerpt($excerpt);
}
return $excerpt;
@@ -217,11 +239,7 @@ trait ParsedownGravTrait
protected function inlineLink($excerpt)
{
if (isset($excerpt['type'])) {
$type = $excerpt['type'];
} else {
$type = 'link';
}
$type = $excerpt['type'] ?? 'link';
// do some trickery to get around Parsedown requirement for valid URL if its Twig in there
if (preg_match($this->twig_link_regex, $excerpt['text'], $matches)) {
@@ -237,13 +255,15 @@ trait ParsedownGravTrait
// if this is a link
if (isset($excerpt['element']['attributes']['href'])) {
$excerpt = Excerpts::processLinkExcerpt($excerpt, $this->page, $type);
$excerpt = $this->excerpts->processLinkExcerpt($excerpt, $type);
}
return $excerpt;
}
// For extending this class via plugins
/**
* For extending this class via plugins
*/
public function __call($method, $args)
{
if (isset($this->{$method}) === true) {
@@ -1,9 +1,23 @@
<?php
/**
* @package Grav\Common\Media
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Media\Interfaces;
/**
* Class implements media collection interface.
*/
interface MediaCollectionInterface
interface MediaCollectionInterface extends \Grav\Framework\Media\Interfaces\MediaCollectionInterface
{
/**
* Return media path.
*
* @return string|null
*/
public function getPath();
}
@@ -1,29 +1,17 @@
<?php
/**
* @package Grav\Common\Media
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Media\Interfaces;
/**
* Class implements media interface.
*/
interface MediaInterface
interface MediaInterface extends \Grav\Framework\Media\Interfaces\MediaInterface
{
/**
* Gets the associated media collection.
*
* @return MediaCollectionInterface Collection of associated media.
*/
public function getMedia();
/**
* Get filesystem path to the associated media.
*
* @return string|null Media path or null if the object doesn't have media folder.
*/
public function getMediaFolder();
/**
* Get display order for the associated media.
*
* @return array Empty array means default ordering.
*/
public function getMediaOrder();
}
@@ -1,9 +1,17 @@
<?php
/**
* @package Grav\Common\Media
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Media\Interfaces;
/**
* Class implements media object interface.
*/
interface MediaObjectInterface
interface MediaObjectInterface extends \Grav\Framework\Media\Interfaces\MediaObjectInterface
{
}
@@ -1,10 +1,19 @@
<?php
/**
* @package Grav\Common\Media
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Media\Traits;
use Grav\Common\Cache;
use Grav\Common\Grav;
use Grav\Common\Media\Interfaces\MediaCollectionInterface;
use Grav\Common\Page\Media;
use Psr\SimpleCache\CacheInterface;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
trait MediaTrait
@@ -23,7 +32,10 @@ trait MediaTrait
*
* @return array Empty array means default ordering.
*/
abstract public function getMediaOrder();
public function getMediaOrder()
{
return [];
}
/**
* Get URI ot the associated media. Method will return null if path isn't URI.
@@ -42,7 +54,7 @@ trait MediaTrait
$locator = Grav::instance()['locator'];
$user = $locator->findResource('user://');
if (strpos($folder, $user) === 0) {
return 'user://' . substr($folder, strlen($user)+1);
return 'user://' . substr($folder, \strlen($user)+1);
}
return null;
@@ -55,14 +67,14 @@ trait MediaTrait
*/
public function getMedia()
{
$cache = $this->getMediaCache();
if ($this->media === null) {
$cache = $this->getMediaCache();
// Use cached media if possible.
$cacheKey = md5('media' . $this->getCacheKey());
if (!$media = $cache->fetch($cacheKey)) {
if (!$media = $cache->get($cacheKey)) {
$media = new Media($this->getMediaFolder(), $this->getMediaOrder());
$cache->save($cacheKey, $media);
$cache->set($cacheKey, $media);
}
$this->media = $media;
}
@@ -80,7 +92,7 @@ trait MediaTrait
{
$cache = $this->getMediaCache();
$cacheKey = md5('media' . $this->getCacheKey());
$cache->save($cacheKey, $media);
$cache->set($cacheKey, $media);
$this->media = $media;
@@ -95,14 +107,19 @@ trait MediaTrait
$cache = $this->getMediaCache();
$cacheKey = md5('media' . $this->getCacheKey());
$cache->delete($cacheKey);
$this->media = null;
}
/**
* @return Cache
* @return CacheInterface
*/
protected function getMediaCache()
{
return Grav::instance()['cache'];
/** @var Cache $cache */
$cache = Grav::instance()['cache'];
return $cache->getSimpleCache();
}
/**
+43 -45
View File
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.Page
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -10,6 +11,7 @@ namespace Grav\Common\Page;
use Grav\Common\Grav;
use Grav\Common\Iterator;
use Grav\Common\Page\Interfaces\PageInterface;
use Grav\Common\Utils;
class Collection extends Iterator
@@ -52,11 +54,11 @@ class Collection extends Iterator
/**
* Add a single page to a collection
*
* @param Page $page
* @param PageInterface $page
*
* @return $this
*/
public function addPage(Page $page)
public function addPage(PageInterface $page)
{
$this->items[$page->path()] = ['slug' => $page->slug()];
@@ -66,8 +68,8 @@ class Collection extends Iterator
/**
* Add a page with path and slug
*
* @param $path
* @param $slug
* @param string $path
* @param string $slug
* @return $this
*/
public function add($path, $slug)
@@ -100,6 +102,7 @@ class Collection extends Iterator
foreach($collection as $page) {
$this->addPage($page);
}
return $this;
}
@@ -117,6 +120,7 @@ class Collection extends Iterator
$this->items = array_uintersect($array1, $array2, function($val1, $val2) {
return strcmp($val1['slug'], $val2['slug']);
});
return $this;
}
@@ -130,13 +134,14 @@ class Collection extends Iterator
public function setParams(array $params)
{
$this->params = array_merge($this->params, $params);
return $this;
}
/**
* Returns current page.
*
* @return Page
* @return PageInterface
*/
public function current()
{
@@ -166,13 +171,13 @@ class Collection extends Iterator
*/
public function offsetGet($offset)
{
return !empty($this->items[$offset]) ? $this->pages->get($offset) : null;
return $this->pages->get($offset) ?: null;
}
/**
* Split collection into array of smaller collections.
*
* @param $size
* @param int $size
* @return array|Collection[]
*/
public function batch($size)
@@ -190,19 +195,19 @@ class Collection extends Iterator
/**
* Remove item from the list.
*
* @param Page|string|null $key
* @param PageInterface|string|null $key
*
* @return $this
* @throws \InvalidArgumentException
*/
public function remove($key = null)
{
if ($key instanceof Page) {
if ($key instanceof PageInterface) {
$key = $key->path();
} elseif (is_null($key)) {
$key = key($this->items);
} elseif (null === $key) {
$key = (string)key($this->items);
}
if (!is_string($key)) {
if (!\is_string($key)) {
throw new \InvalidArgumentException('Invalid argument $key.');
}
@@ -233,15 +238,11 @@ class Collection extends Iterator
*
* @param string $path
*
* @return boolean True if item is first.
* @return bool True if item is first.
*/
public function isFirst($path)
{
if ($this->items && $path == array_keys($this->items)[0]) {
return true;
} else {
return false;
}
return $this->items && $path === array_keys($this->items)[0];
}
/**
@@ -249,15 +250,11 @@ class Collection extends Iterator
*
* @param string $path
*
* @return boolean True if item is last.
* @return bool True if item is last.
*/
public function isLast($path)
{
if ($this->items && $path == array_keys($this->items)[count($this->items) - 1]) {
return true;
} else {
return false;
}
return $this->items && $path === array_keys($this->items)[\count($this->items) - 1];
}
/**
@@ -265,7 +262,7 @@ class Collection extends Iterator
*
* @param string $path
*
* @return Page The previous item.
* @return PageInterface The previous item.
*/
public function prevSibling($path)
{
@@ -277,7 +274,7 @@ class Collection extends Iterator
*
* @param string $path
*
* @return Page The next item.
* @return PageInterface The next item.
*/
public function nextSibling($path)
{
@@ -288,9 +285,9 @@ class Collection extends Iterator
* Returns the adjacent sibling based on a direction.
*
* @param string $path
* @param integer $direction either -1 or +1
* @param int $direction either -1 or +1
*
* @return Page The sibling item.
* @return PageInterface|Collection The sibling item.
*/
public function adjacentSibling($path, $direction = 1)
{
@@ -312,11 +309,11 @@ class Collection extends Iterator
*
* @param string $path the path the item
*
* @return Integer the index of the current page.
* @return int the index of the current page.
*/
public function currentPosition($path)
{
return array_search($path, array_keys($this->items));
return \array_search($path, \array_keys($this->items), true);
}
/**
@@ -325,14 +322,14 @@ class Collection extends Iterator
* Dates can be passed in as text that strtotime() can process
* http://php.net/manual/en/function.strtotime.php
*
* @param $startDate
* @param string $startDate
* @param bool $endDate
* @param $field
* @param string|null $field
*
* @return $this
* @throws \Exception
*/
public function dateRange($startDate, $endDate = false, $field = false)
public function dateRange($startDate, $endDate = false, $field = null)
{
$start = Utils::date2timestamp($startDate);
$end = $endDate ? Utils::date2timestamp($endDate) : false;
@@ -350,6 +347,7 @@ class Collection extends Iterator
}
$this->items = $date_range;
return $this;
}
@@ -518,7 +516,7 @@ class Collection extends Iterator
/**
* Creates new collection with only pages of the specified type
*
* @param $type
* @param string $type
*
* @return Collection The collection
*/
@@ -528,7 +526,7 @@ class Collection extends Iterator
foreach ($this->items as $path => $slug) {
$page = $this->pages->get($path);
if ($page !== null && $page->template() == $type) {
if ($page !== null && $page->template() === $type) {
$items[$path] = $slug;
}
}
@@ -541,7 +539,7 @@ class Collection extends Iterator
/**
* Creates new collection with only pages of one of the specified types
*
* @param $types
* @param string[] $types
*
* @return Collection The collection
*/
@@ -551,7 +549,7 @@ class Collection extends Iterator
foreach ($this->items as $path => $slug) {
$page = $this->pages->get($path);
if ($page !== null && in_array($page->template(), $types)) {
if ($page !== null && \in_array($page->template(), $types, true)) {
$items[$path] = $slug;
}
}
@@ -564,7 +562,7 @@ class Collection extends Iterator
/**
* Creates new collection with only pages of one of the specified access levels
*
* @param $accessLevels
* @param array $accessLevels
*
* @return Collection The collection
*/
@@ -576,19 +574,19 @@ class Collection extends Iterator
$page = $this->pages->get($path);
if ($page !== null && isset($page->header()->access)) {
if (is_array($page->header()->access)) {
if (\is_array($page->header()->access)) {
//Multiple values for access
$valid = false;
foreach ($page->header()->access as $index => $accessLevel) {
if (is_array($accessLevel)) {
if (\is_array($accessLevel)) {
foreach ($accessLevel as $innerIndex => $innerAccessLevel) {
if (in_array($innerAccessLevel, $accessLevels)) {
if (\in_array($innerAccessLevel, $accessLevels)) {
$valid = true;
}
}
} else {
if (in_array($index, $accessLevels)) {
if (\in_array($index, $accessLevels)) {
$valid = true;
}
}
@@ -598,7 +596,7 @@ class Collection extends Iterator
}
} else {
//Single value for access
if (in_array($page->header()->access, $accessLevels)) {
if (\in_array($page->header()->access, $accessLevels)) {
$items[$path] = $slug;
}
}
+3 -2
View File
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.Page
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -0,0 +1,267 @@
<?php
/**
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Page\Interfaces;
use Grav\Common\Page\Media;
/**
* Methods currently implemented in Flex Page emulation layer.
*/
interface PageContentInterface
{
/**
* Gets and Sets the header based on the YAML configuration at the top of the .md file
*
* @param object|array $var a YAML object representing the configuration for the file
*
* @return object the current YAML configuration
*/
public function header($var = null);
/**
* Get the summary.
*
* @param int $size Max summary size.
*
* @param bool $textOnly Only count text size.
*
* @return string
*/
public function summary($size = null, $textOnly = false);
/**
* Gets and Sets the content based on content portion of the .md file
*
* @param string $var Content
*
* @return string Content
*/
public function content($var = null);
/**
* Needed by the onPageContentProcessed event to get the raw page content
*
* @return string the current page content
*/
public function getRawContent();
/**
* Needed by the onPageContentProcessed event to set the raw page content
*
* @param string $content
*/
public function setRawContent($content);
/**
* Gets and Sets the Page raw content
*
* @param string|null $var
*
* @return null
*/
public function rawMarkdown($var = null);
/**
* Get value from a page variable (used mostly for creating edit forms).
*
* @param string $name Variable name.
* @param mixed $default
*
* @return mixed
*/
public function value($name, $default = null);
/**
* Gets and sets the associated media as found in the page folder.
*
* @param Media $var Representation of associated media.
*
* @return Media Representation of associated media.
*/
public function media($var = null);
/**
* Gets and sets the title for this Page. If no title is set, it will use the slug() to get a name
*
* @param string $var the title of the Page
*
* @return string the title of the Page
*/
public function title($var = null);
/**
* Gets and sets the menu name for this Page. This is the text that can be used specifically for navigation.
* If no menu field is set, it will use the title()
*
* @param string $var the menu field for the page
*
* @return string the menu field for the page
*/
public function menu($var = null);
/**
* Gets and Sets whether or not this Page is visible for navigation
*
* @param bool $var true if the page is visible
*
* @return bool true if the page is visible
*/
public function visible($var = null);
/**
* Gets and Sets whether or not this Page is considered published
*
* @param bool $var true if the page is published
*
* @return bool true if the page is published
*/
public function published($var = null);
/**
* Gets and Sets the Page publish date
*
* @param string $var string representation of a date
*
* @return int unix timestamp representation of the date
*/
public function publishDate($var = null);
/**
* Gets and Sets the Page unpublish date
*
* @param string $var string representation of a date
*
* @return int|null unix timestamp representation of the date
*/
public function unpublishDate($var = null);
/**
* Gets and Sets the process setup for this Page. This is multi-dimensional array that consists of
* a simple array of arrays with the form array("markdown"=>true) for example
*
* @param array $var an Array of name value pairs where the name is the process and value is true or false
*
* @return array an Array of name value pairs where the name is the process and value is true or false
*/
public function process($var = null);
/**
* Gets and Sets the slug for the Page. The slug is used in the URL routing. If not set it uses
* the parent folder from the path
*
* @param string $var the slug, e.g. 'my-blog'
*
* @return string the slug
*/
public function slug($var = null);
/**
* Get/set order number of this page.
*
* @param int $var
*
* @return int|bool
*/
public function order($var = null);
/**
* Gets and sets the identifier for this Page object.
*
* @param string $var the identifier
*
* @return string the identifier
*/
public function id($var = null);
/**
* Gets and sets the modified timestamp.
*
* @param int $var modified unix timestamp
*
* @return int modified unix timestamp
*/
public function modified($var = null);
/**
* Gets and sets the option to show the last_modified header for the page.
*
* @param boolean $var show last_modified header
*
* @return boolean show last_modified header
*/
public function lastModified($var = null);
/**
* Get/set the folder.
*
* @param string $var Optional path
*
* @return string|null
*/
public function folder($var = null);
/**
* Gets and sets the date for this Page object. This is typically passed in via the page headers
*
* @param string $var string representation of a date
*
* @return int unix timestamp representation of the date
*/
public function date($var = null);
/**
* Gets and sets the date format for this Page object. This is typically passed in via the page headers
* using typical PHP date string structure - http://php.net/manual/en/function.date.php
*
* @param string $var string representation of a date format
*
* @return string string representation of a date format
*/
public function dateformat($var = null);
/**
* Gets and sets the taxonomy array which defines which taxonomies this page identifies itself with.
*
* @param array $var an array of taxonomies
*
* @return array an array of taxonomies
*/
public function taxonomy($var = null);
/**
* Gets the configured state of the processing method.
*
* @param string $process the process, eg "twig" or "markdown"
*
* @return bool whether or not the processing method is enabled for this Page
*/
public function shouldProcess($process);
/**
* Returns whether or not this Page object has a .md file associated with it or if its just a directory.
*
* @return bool True if its a page with a .md file associated
*/
public function isPage();
/**
* Returns whether or not this Page object is a directory or a page.
*
* @return bool True if its a directory
*/
public function isDir();
/**
* Returns whether the page exists in the filesystem.
*
* @return bool
*/
public function exists();
}
@@ -1,9 +1,19 @@
<?php
/**
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Page\Interfaces;
use Grav\Common\Media\Interfaces\MediaInterface;
/**
* Class implements page interface.
*/
interface PageInterface
interface PageInterface extends PageContentInterface, PageRoutableInterface, PageTranslateInterface, MediaInterface, PageLegacyInterface
{
}
@@ -0,0 +1,496 @@
<?php
namespace Grav\Common\Page\Interfaces;
use Exception;
use Grav\Common\Data\Blueprint;
use Grav\Common\Page\Collection;
use RocketTheme\Toolbox\File\MarkdownFile;
interface PageLegacyInterface
{
/**
* Initializes the page instance variables based on a file
*
* @param \SplFileInfo $file The file information for the .md file that the page represents
* @param string $extension
*
* @return $this
*/
public function init(\SplFileInfo $file, $extension = null);
/**
* Gets and Sets the raw data
*
* @param string $var Raw content string
*
* @return string Raw content string
*/
public function raw($var = null);
/**
* Gets and Sets the page frontmatter
*
* @param string|null $var
*
* @return string
*/
public function frontmatter($var = null);
/**
* Modify a header value directly
*
* @param string $key
* @param mixed $value
*/
public function modifyHeader($key, $value);
/**
* @return int
*/
public function httpResponseCode();
public function httpHeaders();
/**
* Sets the summary of the page
*
* @param string $summary Summary
*/
public function setSummary($summary);
/**
* Get the contentMeta array and initialize content first if it's not already
*
* @return mixed
*/
public function contentMeta();
/**
* Add an entry to the page's contentMeta array
*
* @param string $name
* @param string $value
*/
public function addContentMeta($name, $value);
/**
* Return the whole contentMeta array as it currently stands
*
* @param string|null $name
*
* @return mixed
*/
public function getContentMeta($name = null);
/**
* Sets the whole content meta array in one shot
*
* @param array $content_meta
*
* @return array
*/
public function setContentMeta($content_meta);
/**
* Fires the onPageContentProcessed event, and caches the page content using a unique ID for the page
*/
public function cachePageContent();
/**
* Get file object to the page.
*
* @return MarkdownFile|null
*/
public function file();
/**
* Save page if there's a file assigned to it.
*
* @param bool|mixed $reorder Internal use.
*/
public function save($reorder = true);
/**
* Prepare move page to new location. Moves also everything that's under the current page.
*
* You need to call $this->save() in order to perform the move.
*
* @param PageInterface $parent New parent page.
*
* @return $this
*/
public function move(PageInterface $parent);
/**
* Prepare a copy from the page. Copies also everything that's under the current page.
*
* Returns a new Page object for the copy.
* You need to call $this->save() in order to perform the move.
*
* @param PageInterface $parent New parent page.
*
* @return $this
*/
public function copy(PageInterface $parent);
/**
* Get blueprints for the page.
*
* @return Blueprint
*/
public function blueprints();
/**
* Get the blueprint name for this page. Use the blueprint form field if set
*
* @return string
*/
public function blueprintName();
/**
* Validate page header.
*
* @throws Exception
*/
public function validate();
/**
* Filter page header from illegal contents.
*/
public function filter();
/**
* Get unknown header variables.
*
* @return array
*/
public function extra();
/**
* Convert page to an array.
*
* @return array
*/
public function toArray();
/**
* Convert page to YAML encoded string.
*
* @return string
*/
public function toYaml();
/**
* Convert page to JSON encoded string.
*
* @return string
*/
public function toJson();
/**
* Returns normalized list of name => form pairs.
*
* @return array
*/
public function forms();
/**
* @param array $new
*/
public function addForms(array $new);
/**
* Gets and sets the name field. If no name field is set, it will return 'default.md'.
*
* @param string $var The name of this page.
*
* @return string The name of this page.
*/
public function name($var = null);
/**
* Returns child page type.
*
* @return string
*/
public function childType();
/**
* Gets and sets the template field. This is used to find the correct Twig template file to render.
* If no field is set, it will return the name without the .md extension
*
* @param string $var the template name
*
* @return string the template name
*/
public function template($var = null);
/**
* Allows a page to override the output render format, usually the extension provided
* in the URL. (e.g. `html`, `json`, `xml`, etc).
*
* @param null $var
*
* @return null
*/
public function templateFormat($var = null);
/**
* Gets and sets the extension field.
*
* @param null $var
*
* @return null|string
*/
public function extension($var = null);
/**
* Gets and sets the expires field. If not set will return the default
*
* @param int $var The new expires value.
*
* @return int The expires value
*/
public function expires($var = null);
/**
* Gets and sets the cache-control property. If not set it will return the default value (null)
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control for more details on valid options
*
* @param null $var
* @return null
*/
public function cacheControl($var = null);
public function ssl($var = null);
/**
* Returns the state of the debugger override etting for this page
*
* @return mixed
*/
public function debugger();
/**
* Function to merge page metadata tags and build an array of Metadata objects
* that can then be rendered in the page.
*
* @param array $var an Array of metadata values to set
*
* @return array an Array of metadata values for the page
*/
public function metadata($var = null);
/**
* Gets and sets the option to show the etag header for the page.
*
* @param bool $var show etag header
*
* @return bool show etag header
*/
public function eTag($var = null);
/**
* Gets and sets the path to the .md file for this Page object.
*
* @param string $var the file path
*
* @return string|null the file path
*/
public function filePath($var = null);
/**
* Gets the relative path to the .md file
*
* @return string The relative file path
*/
public function filePathClean();
/**
* Gets and sets the order by which any sub-pages should be sorted.
*
* @param string $var the order, either "asc" or "desc"
*
* @return string the order, either "asc" or "desc"
* @deprecated 1.6
*/
public function orderDir($var = null);
/**
* Gets and sets the order by which the sub-pages should be sorted.
*
* default - is the order based on the file system, ie 01.Home before 02.Advark
* title - is the order based on the title set in the pages
* date - is the order based on the date set in the pages
* folder - is the order based on the name of the folder with any numerics omitted
*
* @param string $var supported options include "default", "title", "date", and "folder"
*
* @return string supported options include "default", "title", "date", and "folder"
* @deprecated 1.6
*/
public function orderBy($var = null);
/**
* Gets the manual order set in the header.
*
* @param string $var supported options include "default", "title", "date", and "folder"
*
* @return array
* @deprecated 1.6
*/
public function orderManual($var = null);
/**
* Gets and sets the maxCount field which describes how many sub-pages should be displayed if the
* sub_pages header property is set for this page object.
*
* @param int $var the maximum number of sub-pages
*
* @return int the maximum number of sub-pages
* @deprecated 1.6
*/
public function maxCount($var = null);
/**
* Gets and sets the modular var that helps identify this page is a modular child
*
* @param bool $var true if modular_twig
*
* @return bool true if modular_twig
*/
public function modular($var = null);
/**
* Gets and sets the modular_twig var that helps identify this page as a modular child page that will need
* twig processing handled differently from a regular page.
*
* @param bool $var true if modular_twig
*
* @return bool true if modular_twig
*/
public function modularTwig($var = null);
/**
* Returns children of this page.
*
* @return \Grav\Common\Page\Collection
*/
public function children();
/**
* Check to see if this item is the first in an array of sub-pages.
*
* @return bool True if item is first.
*/
public function isFirst();
/**
* Check to see if this item is the last in an array of sub-pages.
*
* @return bool True if item is last
*/
public function isLast();
/**
* Gets the previous sibling based on current position.
*
* @return PageInterface the previous Page item
*/
public function prevSibling();
/**
* Gets the next sibling based on current position.
*
* @return PageInterface the next Page item
*/
public function nextSibling();
/**
* Returns the adjacent sibling based on a direction.
*
* @param int $direction either -1 or +1
*
* @return PageInterface|bool the sibling page
*/
public function adjacentSibling($direction = 1);
/**
* Helper method to return an ancestor page.
*
* @param bool $lookup Name of the parent folder
*
* @return PageInterface page you were looking for if it exists
*/
public function ancestor($lookup = null);
/**
* Helper method to return an ancestor page to inherit from. The current
* page object is returned.
*
* @param string $field Name of the parent folder
*
* @return PageInterface
*/
public function inherited($field);
/**
* Helper method to return an ancestor field only to inherit from. The
* first occurrence of an ancestor field will be returned if at all.
*
* @param string $field Name of the parent folder
*
* @return array
*/
public function inheritedField($field);
/**
* Helper method to return a page.
*
* @param string $url the url of the page
* @param bool $all
*
* @return PageInterface page you were looking for if it exists
*/
public function find($url, $all = false);
/**
* Get a collection of pages in the current context.
*
* @param string|array $params
* @param bool $pagination
*
* @return Collection
* @throws \InvalidArgumentException
*/
public function collection($params = 'content', $pagination = true);
/**
* @param string|array $value
* @param bool $only_published
* @return mixed
* @internal
*/
public function evaluate($value, $only_published = true);
/**
* Returns whether or not the current folder exists
*
* @return bool
*/
public function folderExists();
/**
* Gets the Page Unmodified (original) version of the page.
*
* @return PageInterface The original version of the page.
*/
public function getOriginal();
/**
* Gets the action.
*
* @return string The Action string.
*/
public function getAction();
}
@@ -0,0 +1,188 @@
<?php
namespace Grav\Common\Page\Interfaces;
interface PageRoutableInterface
{
/**
* Returns the page extension, got from the page `url_extension` config and falls back to the
* system config `system.pages.append_url_extension`.
*
* @return string The extension of this page. For example `.html`
*/
public function urlExtension();
/**
* Gets and Sets whether or not this Page is routable, ie you can reach it
* via a URL.
* The page must be *routable* and *published*
*
* @param bool $var true if the page is routable
*
* @return bool true if the page is routable
*/
public function routable($var = null);
/**
* Gets the URL for a page - alias of url().
*
* @param bool $include_host
*
* @return string the permalink
*/
public function link($include_host = false);
/**
* Gets the URL with host information, aka Permalink.
* @return string The permalink.
*/
public function permalink();
/**
* Returns the canonical URL for a page
*
* @param bool $include_lang
*
* @return string
*/
public function canonical($include_lang = true);
/**
* Gets the url for the Page.
*
* @param bool $include_host Defaults false, but true would include http://yourhost.com
* @param bool $canonical true to return the canonical URL
* @param bool $include_lang
* @param bool $raw_route
*
* @return string The url.
*/
public function url($include_host = false, $canonical = false, $include_lang = true, $raw_route = false);
/**
* Gets the route for the page based on the route headers if available, else from
* the parents route and the current Page's slug.
*
* @param string $var Set new default route.
*
* @return string The route for the Page.
*/
public function route($var = null);
/**
* Helper method to clear the route out so it regenerates next time you use it
*/
public function unsetRouteSlug();
/**
* Gets and Sets the page raw route
*
* @param string|null $var
*
* @return string
*/
public function rawRoute($var = null);
/**
* Gets the route aliases for the page based on page headers.
*
* @param array $var list of route aliases
*
* @return array The route aliases for the Page.
*/
public function routeAliases($var = null);
/**
* Gets the canonical route for this page if its set. If provided it will use
* that value, else if it's `true` it will use the default route.
*
* @param string|null $var
*
* @return bool|string
*/
public function routeCanonical($var = null);
/**
* Gets the redirect set in the header.
*
* @param string $var redirect url
*
* @return string
*/
public function redirect($var = null);
/**
* Returns the clean path to the page file
*/
public function relativePagePath();
/**
* Gets and sets the path to the folder where the .md for this Page object resides.
* This is equivalent to the filePath but without the filename.
*
* @param string $var the path
*
* @return string|null the path
*/
public function path($var = null);
/**
* Get/set the folder.
*
* @param string $var Optional path
*
* @return string|null
*/
public function folder($var = null);
/**
* Gets and Sets the parent object for this page
*
* @param PageInterface $var the parent page object
*
* @return PageInterface|null the parent page object if it exists.
*/
public function parent(PageInterface $var = null);
/**
* Gets the top parent object for this page
*
* @return PageInterface|null the top parent page object if it exists.
*/
public function topParent();
/**
* Returns the item in the current position.
*
* @return int the index of the current page.
*/
public function currentPosition();
/**
* Returns whether or not this page is the currently active page requested via the URL.
*
* @return bool True if it is active
*/
public function active();
/**
* Returns whether or not this URI's URL contains the URL of the active page.
* Or in other words, is this page's URL in the current URL
*
* @return bool True if active child exists
*/
public function activeChild();
/**
* Returns whether or not this page is the currently configured home page.
*
* @return bool True if it is the homepage
*/
public function home();
/**
* Returns whether or not this page is the root node of the pages tree.
*
* @return bool True if it is the root
*/
public function root();
}
@@ -0,0 +1,32 @@
<?php
namespace Grav\Common\Page\Interfaces;
interface PageTranslateInterface
{
/**
* Return an array with the routes of other translated languages
*
* @param bool $onlyPublished only return published translations
*
* @return array the page translated languages
*/
public function translatedLanguages($onlyPublished = false);
/**
* Return an array listing untranslated languages available
*
* @param bool $includeUnpublished also list unpublished translations
*
* @return array the page untranslated languages
*/
public function untranslatedLanguages($includeUnpublished = false);
/**
* Get page language
*
* @param string $var
*
* @return mixed
*/
public function language($var = null);
}
@@ -0,0 +1,329 @@
<?php
/**
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Page\Markdown;
use Grav\Common\Grav;
use Grav\Common\Page\Interfaces\PageInterface;
use Grav\Common\Page\Medium\Link;
use Grav\Common\Uri;
use Grav\Common\Page\Medium\Medium;
use Grav\Common\Utils;
use RocketTheme\Toolbox\Event\Event;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
class Excerpts
{
/** @var PageInterface */
protected $page;
/** @var array */
protected $config;
public function __construct(PageInterface $page = null, array $config = null)
{
$this->page = $page ?? Grav::instance()['page'] ?? null;
// Add defaults to the configuration.
if (null === $config || !isset($config['markdown'], $config['images'])) {
$c = Grav::instance()['config'];
$config = $config ?? [];
$config += [
'markdown' => $c->get('system.pages.markdown', []),
'images' => $c->get('system.images', [])
];
}
$this->config = $config;
}
public function getPage(): PageInterface
{
return $this->page;
}
public function getConfig(): array
{
return $this->config;
}
public function fireInitializedEvent($markdown): void
{
$grav = Grav::instance();
$grav->fireEvent('onMarkdownInitialized', new Event(['markdown' => $markdown, 'page' => $this->page]));
}
/**
* Process a Link excerpt
*
* @param array $excerpt
* @param string $type
* @return array
*/
public function processLinkExcerpt(array $excerpt, string $type = 'link'): array
{
$url = htmlspecialchars_decode(rawurldecode($excerpt['element']['attributes']['href']));
$url_parts = $this->parseUrl($url);
// If there is a query, then parse it and build action calls.
if (isset($url_parts['query'])) {
$actions = array_reduce(
explode('&', $url_parts['query']),
static function ($carry, $item) {
$parts = explode('=', $item, 2);
$value = isset($parts[1]) ? rawurldecode($parts[1]) : true;
$carry[$parts[0]] = $value;
return $carry;
},
[]
);
// Valid attributes supported.
$valid_attributes = ['rel', 'target', 'id', 'class', 'classes'];
// Unless told to not process, go through actions.
if (array_key_exists('noprocess', $actions)) {
unset($actions['noprocess']);
} else {
// Loop through actions for the image and call them.
foreach ($actions as $attrib => $value) {
$key = $attrib;
if (in_array($attrib, $valid_attributes, true)) {
// support both class and classes.
if ($attrib === 'classes') {
$attrib = 'class';
}
$excerpt['element']['attributes'][$attrib] = str_replace(',', ' ', $value);
unset($actions[$key]);
}
}
}
$url_parts['query'] = http_build_query($actions, null, '&', PHP_QUERY_RFC3986);
}
// If no query elements left, unset query.
if (empty($url_parts['query'])) {
unset ($url_parts['query']);
}
// Set path to / if not set.
if (empty($url_parts['path'])) {
$url_parts['path'] = '';
}
// If scheme isn't http(s)..
if (!empty($url_parts['scheme']) && !in_array($url_parts['scheme'], ['http', 'https'])) {
// Handle custom streams.
if ($type !== 'image' && !empty($url_parts['stream']) && !empty($url_parts['path'])) {
$grav = Grav::instance();
$url_parts['path'] = $grav['base_url_relative'] . '/' . $this->resolveStream("{$url_parts['scheme']}://{$url_parts['path']}");
unset($url_parts['stream'], $url_parts['scheme']);
}
$excerpt['element']['attributes']['href'] = Uri::buildUrl($url_parts);
return $excerpt;
}
// Handle paths and such.
$url_parts = Uri::convertUrl($this->page, $url_parts, $type);
// Build the URL from the component parts and set it on the element.
$excerpt['element']['attributes']['href'] = Uri::buildUrl($url_parts);
return $excerpt;
}
/**
* Process an image excerpt
*
* @param array $excerpt
* @return array
*/
public function processImageExcerpt(array $excerpt): array
{
$url = htmlspecialchars_decode(urldecode($excerpt['element']['attributes']['src']));
$url_parts = $this->parseUrl($url);
$media = null;
$filename = null;
if (!empty($url_parts['stream'])) {
$filename = $url_parts['scheme'] . '://' . ($url_parts['path'] ?? '');
$media = $this->page->getMedia();
} else {
$grav = Grav::instance();
// File is also local if scheme is http(s) and host matches.
$local_file = isset($url_parts['path'])
&& (empty($url_parts['scheme']) || in_array($url_parts['scheme'], ['http', 'https'], true))
&& (empty($url_parts['host']) || $url_parts['host'] === $grav['uri']->host());
if ($local_file) {
$filename = basename($url_parts['path']);
$folder = dirname($url_parts['path']);
// Get the local path to page media if possible.
if ($this->page && $folder === $this->page->url(false, false, false)) {
// Get the media objects for this page.
$media = $this->page->getMedia();
} else {
// see if this is an external page to this one
$base_url = rtrim($grav['base_url_relative'] . $grav['pages']->base(), '/');
$page_route = '/' . ltrim(str_replace($base_url, '', $folder), '/');
/** @var PageInterface $ext_page */
$ext_page = $grav['pages']->dispatch($page_route, true);
if ($ext_page) {
$media = $ext_page->getMedia();
} else {
$grav->fireEvent('onMediaLocate', new Event(['route' => $page_route, 'media' => &$media]));
}
}
}
}
// If there is a media file that matches the path referenced..
if ($media && $filename && isset($media[$filename])) {
// Get the medium object.
/** @var Medium $medium */
$medium = $media[$filename];
// Process operations
$medium = $this->processMediaActions($medium, $url_parts);
$element_excerpt = $excerpt['element']['attributes'];
$alt = $element_excerpt['alt'] ?? '';
$title = $element_excerpt['title'] ?? '';
$class = $element_excerpt['class'] ?? '';
$id = $element_excerpt['id'] ?? '';
$excerpt['element'] = $medium->parsedownElement($title, $alt, $class, $id, true);
} else {
// Not a current page media file, see if it needs converting to relative.
$excerpt['element']['attributes']['src'] = Uri::buildUrl($url_parts);
}
return $excerpt;
}
/**
* Process media actions
*
* @param Medium $medium
* @param string|array $url
* @return Medium|Link
*/
public function processMediaActions($medium, $url)
{
$url_parts = is_string($url) ? $this->parseUrl($url) : $url;
$actions = [];
// if there is a query, then parse it and build action calls
if (isset($url_parts['query'])) {
$actions = array_reduce(
explode('&', $url_parts['query']),
static function ($carry, $item) {
$parts = explode('=', $item, 2);
$value = $parts[1] ?? null;
$carry[] = ['method' => $parts[0], 'params' => $value];
return $carry;
},
[]
);
}
$config = $this->getConfig();
if (!empty($config['images']['auto_fix_orientation'])) {
$actions[] = ['method' => 'fixOrientation', 'params' => ''];
}
$defaults = $config['images']['defaults'] ?? [];
if (count($defaults)) {
foreach ($defaults as $method => $params) {
$actions[] = [
'method' => $method,
'params' => $params,
];
}
}
// loop through actions for the image and call them
foreach ($actions as $action) {
$matches = [];
if (preg_match('/\[(.*)\]/', $action['params'], $matches)) {
$args = [explode(',', $matches[1])];
} else {
$args = explode(',', $action['params']);
}
$medium = call_user_func_array([$medium, $action['method']], $args);
}
if (isset($url_parts['fragment'])) {
$medium->urlHash($url_parts['fragment']);
}
return $medium;
}
/**
* Variation of parse_url() which works also with local streams.
*
* @param string $url
* @return array|bool
*/
protected function parseUrl(string $url)
{
$url_parts = Utils::multibyteParseUrl($url);
if (isset($url_parts['scheme'])) {
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
// Special handling for the streams.
if ($locator->schemeExists($url_parts['scheme'])) {
if (isset($url_parts['host'])) {
// Merge host and path into a path.
$url_parts['path'] = $url_parts['host'] . (isset($url_parts['path']) ? '/' . $url_parts['path'] : '');
unset($url_parts['host']);
}
$url_parts['stream'] = true;
}
}
return $url_parts;
}
/**
* @param string $url
* @return bool|string
*/
protected function resolveStream(string $url)
{
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
if ($locator->isStream($url)) {
return $locator->findResource($url, false) ?: $locator->findResource($url, false, true);
}
return $url;
}
}
+28 -19
View File
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.Page
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -14,26 +15,28 @@ use Grav\Common\Page\Medium\AbstractMedia;
use Grav\Common\Page\Medium\GlobalMedia;
use Grav\Common\Page\Medium\MediumFactory;
use RocketTheme\Toolbox\File\File;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
class Media extends AbstractMedia
{
protected static $global;
protected $path;
protected $standard_exif = ['FileSize', 'MimeType', 'height', 'width'];
/**
* @param string $path
* @param array $media_order
* @param bool $load
*/
public function __construct($path, array $media_order = null)
public function __construct($path, array $media_order = null, $load = true)
{
$this->path = $path;
$this->setPath($path);
$this->media_order = $media_order;
$this->__wakeup();
$this->init();
if ($load) {
$this->init();
}
}
/**
@@ -72,37 +75,40 @@ class Media extends AbstractMedia
*/
protected function init()
{
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
$config = Grav::instance()['config'];
$locator = Grav::instance()['locator'];
$exif_reader = isset(Grav::instance()['exif']) ? Grav::instance()['exif']->getReader() : false;
$media_types = array_keys(Grav::instance()['config']->get('media.types'));
// Handle special cases where page doesn't exist in filesystem.
if (!is_dir($this->path)) {
if (!is_dir($this->getPath())) {
return;
}
$iterator = new \FilesystemIterator($this->path, \FilesystemIterator::UNIX_PATHS | \FilesystemIterator::SKIP_DOTS);
$iterator = new \FilesystemIterator($this->getPath(), \FilesystemIterator::UNIX_PATHS | \FilesystemIterator::SKIP_DOTS);
$media = [];
/** @var \DirectoryIterator $info */
foreach ($iterator as $path => $info) {
// Ignore folders and Markdown files.
if (!$info->isFile() || $info->getExtension() === 'md' || $info->getFilename()[0] === '.') {
if (!$info->isFile() || $info->getExtension() === 'md' || strpos($info->getFilename(), '.') === 0) {
continue;
}
// Find out what type we're dealing with
list($basename, $ext, $type, $extra) = $this->getFileParts($info->getFilename());
if (!in_array(strtolower($ext), $media_types)) {
if (!\in_array(strtolower($ext), $media_types, true)) {
continue;
}
if ($type === 'alternative') {
$media["{$basename}.{$ext}"][$type][$extra] = [ 'file' => $path, 'size' => $info->getSize() ];
$media["{$basename}.{$ext}"][$type][$extra] = ['file' => $path, 'size' => $info->getSize()];
} else {
$media["{$basename}.{$ext}"][$type] = [ 'file' => $path, 'size' => $info->getSize() ];
$media["{$basename}.{$ext}"][$type] = ['file' => $path, 'size' => $info->getSize()];
}
}
@@ -147,7 +153,7 @@ class Media extends AbstractMedia
if (file_exists($meta_path)) {
$types['meta']['file'] = $meta_path;
} elseif ($file_path && $medium->get('mime') === 'image/jpeg' && empty($types['meta']) && $config->get('system.media.auto_metadata_exif') && $exif_reader) {
} elseif ($file_path && $exif_reader && $medium->get('mime') === 'image/jpeg' && empty($types['meta']) && $config->get('system.media.auto_metadata_exif')) {
$meta = $exif_reader->read($file_path);
@@ -155,7 +161,11 @@ class Media extends AbstractMedia
$meta_data = $meta->getData();
$meta_trimmed = array_diff_key($meta_data, array_flip($this->standard_exif));
if ($meta_trimmed) {
$file = File::instance($meta_path);
if ($locator->isStream($meta_path)) {
$file = File::instance($locator->findResource($meta_path, true, true));
} else {
$file = File::instance($meta_path);
}
$file->save(Yaml::dump($meta_trimmed));
$types['meta']['file'] = $meta_path;
}
@@ -202,12 +212,11 @@ class Media extends AbstractMedia
}
/**
* Enable accessing the media path
*
* @return mixed
* @return string
* @deprecated 1.6 Use $this->getPath() instead.
*/
public function path()
{
return $this->path;
return $this->getPath();
}
}
@@ -1,30 +1,55 @@
<?php
/**
* @package Grav.Common.Page
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Page\Medium;
use Grav\Common\Getters;
use Grav\Common\Grav;
use Grav\Common\Media\Interfaces\MediaCollectionInterface;
use Grav\Common\Media\Interfaces\MediaObjectInterface;
use Grav\Common\Page\Page;
use Grav\Common\Utils;
use RocketTheme\Toolbox\ArrayTraits\ArrayAccess;
use RocketTheme\Toolbox\ArrayTraits\Countable;
use RocketTheme\Toolbox\ArrayTraits\Export;
use RocketTheme\Toolbox\ArrayTraits\ExportInterface;
use RocketTheme\Toolbox\ArrayTraits\Iterator;
abstract class AbstractMedia extends Getters implements MediaCollectionInterface
abstract class AbstractMedia implements ExportInterface, MediaCollectionInterface
{
protected $gettersVariable = 'instances';
use ArrayAccess;
use Countable;
use Iterator;
use Export;
protected $instances = [];
protected $items = [];
protected $path;
protected $images = [];
protected $videos = [];
protected $audios = [];
protected $files = [];
protected $media_order;
/**
* Return media path.
*
* @return string
*/
public function getPath()
{
return $this->path;
}
public function setPath(?string $path)
{
$this->path = $path;
}
/**
* Get medium by filename.
*
@@ -48,73 +73,78 @@ abstract class AbstractMedia extends Getters implements MediaCollectionInterface
}
/**
* @param mixed $offset
* Set file modification timestamps (query params) for all the media files.
*
* @return mixed
* @param string|int|null $timestamp
* @return $this
*/
public function offsetGet($offset)
public function setTimestamps($timestamp = null)
{
$object = parent::offsetGet($offset);
/** @var Medium $instance */
foreach ($this->items as $instance) {
$instance->setTimestamp($timestamp);
}
// It would be nice if previous image modification would not affect the later ones.
//$object = $object ? clone($object) : null;
return $object;
return $this;
}
/**
* Get a list of all media.
*
* @return array|MediaObjectInterface[]
* @return MediaObjectInterface[]
*/
public function all()
{
$this->instances = $this->orderMedia($this->instances);
$this->items = $this->orderMedia($this->items);
return $this->instances;
return $this->items;
}
/**
* Get a list of all image media.
*
* @return array|MediaObjectInterface[]
* @return MediaObjectInterface[]
*/
public function images()
{
$this->images = $this->orderMedia($this->images);
return $this->images;
}
/**
* Get a list of all video media.
*
* @return array|MediaObjectInterface[]
* @return MediaObjectInterface[]
*/
public function videos()
{
$this->videos = $this->orderMedia($this->videos);
return $this->videos;
}
/**
* Get a list of all audio media.
*
* @return array|MediaObjectInterface[]
* @return MediaObjectInterface[]
*/
public function audios()
{
$this->audios = $this->orderMedia($this->audios);
return $this->audios;
}
/**
* Get a list of all file media.
*
* @return array|MediaObjectInterface[]
* @return MediaObjectInterface[]
*/
public function files()
{
$this->files = $this->orderMedia($this->files);
return $this->files;
}
@@ -122,9 +152,12 @@ abstract class AbstractMedia extends Getters implements MediaCollectionInterface
* @param string $name
* @param MediaObjectInterface $file
*/
protected function add($name, $file)
public function add($name, $file)
{
$this->instances[$name] = $file;
if (!$file) {
return;
}
$this->offsetSet($name, $file);
switch ($file->type) {
case 'image':
$this->images[$name] = $file;
@@ -143,13 +176,14 @@ abstract class AbstractMedia extends Getters implements MediaCollectionInterface
/**
* Order the media based on the page's media_order
*
* @param $media
* @param array $media
* @return array
*/
protected function orderMedia($media)
{
if (null === $this->media_order) {
$page = Grav::instance()['pages']->get($this->path);
/** @var Page $page */
$page = Grav::instance()['pages']->get($this->getPath());
if ($page && isset($page->header()->media_order)) {
$this->media_order = array_map('trim', explode(',', $page->header()->media_order));
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.Page
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -16,7 +17,7 @@ class AudioMedium extends Medium
* Parsedown element for source display mode
*
* @param array $attributes
* @param boolean $reset
* @param bool $reset
* @return array
*/
protected function sourceParsedownElement(array $attributes, $reset = true)
@@ -38,31 +39,29 @@ class AudioMedium extends Medium
*/
public function controls($display = true)
{
if($display)
{
if($display) {
$this->attributes['controls'] = true;
}
else
{
} else {
unset($this->attributes['controls']);
}
return $this;
}
/**
* Allows to set the preload behaviour
*
* @param $preload
* @param string $preload
* @return $this
*/
public function preload($preload)
{
$validPreloadAttrs = array('auto','metadata','none');
$validPreloadAttrs = ['auto', 'metadata', 'none'];
if (in_array($preload, $validPreloadAttrs))
{
if (\in_array($preload, $validPreloadAttrs, true)) {
$this->attributes['preload'] = $preload;
}
return $this;
}
@@ -70,13 +69,14 @@ class AudioMedium extends Medium
* Allows to set the controlsList behaviour
* Separate multiple values with a hyphen
*
* @param $controlsList
* @param string $controlsList
* @return $this
*/
public function controlsList($controlsList)
{
$controlsList = str_replace('-', ' ', $controlsList);
$this->attributes['controlsList'] = $controlsList;
return $this;
}
@@ -88,14 +88,12 @@ class AudioMedium extends Medium
*/
public function muted($status = false)
{
if($status)
{
if($status) {
$this->attributes['muted'] = true;
}
else
{
} else {
unset($this->attributes['muted']);
}
return $this;
}
@@ -107,14 +105,12 @@ class AudioMedium extends Medium
*/
public function loop($status = false)
{
if($status)
{
if($status) {
$this->attributes['loop'] = true;
}
else
{
} else {
unset($this->attributes['loop']);
}
return $this;
}
@@ -126,14 +122,12 @@ class AudioMedium extends Medium
*/
public function autoplay($status = false)
{
if($status)
{
if($status) {
$this->attributes['autoplay'] = true;
}
else
{
} else {
unset($this->attributes['autoplay']);
}
return $this;
}
@@ -148,6 +142,7 @@ class AudioMedium extends Medium
parent::reset();
$this->attributes['controls'] = true;
return $this;
}
}
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.Page
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -13,6 +14,16 @@ use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
class GlobalMedia extends AbstractMedia
{
/**
* Return media path.
*
* @return null
*/
public function getPath()
{
return null;
}
/**
* @param mixed $offset
*
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.Page
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -32,13 +33,14 @@ class ImageFile extends Image
/**
* This is the same as the Gregwar Image class except this one fires a Grav Event on creation of new cached file
*
* @param string $type the image type
* @param int $quality the quality (for JPEG)
* @param bool $actual
* @param string $type the image type
* @param int $quality the quality (for JPEG)
* @param bool $actual
* @param array $extras
*
* @return string
*/
public function cacheFile($type = 'jpg', $quality = 80, $actual = false)
public function cacheFile($type = 'jpg', $quality = 80, $actual = false, $extras = [])
{
if ($type === 'guess') {
$type = $this->guessType();
@@ -49,21 +51,16 @@ class ImageFile extends Image
}
// Computes the hash
$this->hash = $this->getHash($type, $quality);
$this->hash = $this->getHash($type, $quality, $extras);
// Generates the cache file
$cacheFile = '';
// Seo friendly image names
$seofriendly = Grav::instance()['config']->get('system.images.seofriendly', false);
if (!$this->prettyName || $this->prettyPrefix) {
$cacheFile .= $this->hash;
}
if ($this->prettyPrefix) {
$cacheFile .= '-';
}
if ($this->prettyName) {
$cacheFile .= $this->prettyName;
if ($seofriendly) {
$mini_hash = substr($this->hash, 0, 4) . substr($this->hash, -4);
$cacheFile = "{$this->prettyName}-{$mini_hash}";
} else {
$cacheFile = "{$this->hash}-{$this->prettyName}";
}
$cacheFile .= '.' . $type;
@@ -107,4 +104,42 @@ class ImageFile extends Image
return $this->getFilename($file);
}
/**
* Gets the hash.
* @param string $type
* @param int $quality
* @param [] $extras
* @return null
*/
public function getHash($type = 'guess', $quality = 80, $extras = [])
{
if (null === $this->hash) {
$this->generateHash($type, $quality, $extras);
}
return $this->hash;
}
/**
* Generates the hash.
* @param string $type
* @param int $quality
* @param array $extras
*/
public function generateHash($type = 'guess', $quality = 80, $extras = [])
{
$inputInfos = $this->source->getInfos();
$datas = array(
$inputInfos,
$this->serializeOperations(),
$type,
$quality,
$extras
);
$this->hash = sha1(serialize($datas));
}
}
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.Page
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -41,7 +42,7 @@ class ImageMedium extends Medium
protected $default_quality;
/**
* @var boolean
* @var bool
*/
protected $debug_watermarked = false;
@@ -83,11 +84,13 @@ class ImageMedium extends Medium
$config = Grav::instance()['config'];
if (filesize($this->get('filepath')) === 0) {
$path = $this->get('filepath');
if (!$path || !file_exists($path) || !filesize($path)) {
return;
}
$image_info = getimagesize($this->get('filepath'));
$image_info = getimagesize($path);
$this->def('width', $image_info[0]);
$this->def('height', $image_info[1]);
$this->def('mime', $image_info['mime']);
@@ -119,7 +122,7 @@ class ImageMedium extends Medium
/**
* Add meta file for the medium.
*
* @param $filepath
* @param string $filepath
* @return $this
*/
public function addMetaFile($filepath)
@@ -167,8 +170,7 @@ class ImageMedium extends Medium
{
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
$image_path = $locator->findResource('cache://images', true);
$image_dir = $locator->findResource('cache://images', false);
$image_path = $locator->findResource('cache://images', true) ?: $locator->findResource('cache://images', true, true);
$saved_image_path = $this->saveImage();
$output = preg_replace('|^' . preg_quote(GRAV_ROOT, '|') . '|', '', $saved_image_path);
@@ -178,6 +180,7 @@ class ImageMedium extends Medium
}
if (Utils::startsWith($output, $image_path)) {
$image_dir = $locator->findResource('cache://images', false);
$output = '/' . $image_dir . preg_replace('|^' . preg_quote($image_path, '|') . '|', '', $output);
}
@@ -185,7 +188,7 @@ class ImageMedium extends Medium
$this->reset();
}
return trim(Grav::instance()['base_url'] . '/' . ltrim($output . $this->querystring() . $this->urlHash(), '/'), '\\');
return trim(Grav::instance()['base_url'] . '/' . $this->urlQuerystring($output), '\\');
}
/**
@@ -229,9 +232,9 @@ class ImageMedium extends Medium
}
/**
* Allows the ability to override the Inmage's Pretty name stored in cache
* Allows the ability to override the image's pretty name stored in cache
*
* @param $name
* @param string $name
*/
public function setImagePrettyName($name)
{
@@ -260,11 +263,12 @@ class ImageMedium extends Medium
* widths. Existing image alternatives won't be overwritten.
*
* @param int|int[] $min_width
* @param int [$max_width=2500]
* @param int [$step=200]
* @param int $max_width
* @param int $step
* @return $this
*/
public function derivatives($min_width, $max_width = 2500, $step = 200) {
public function derivatives($min_width, $max_width = 2500, $step = 200)
{
if (!empty($this->alternatives)) {
$max = max(array_keys($this->alternatives));
$base = $this->alternatives[$max];
@@ -331,7 +335,7 @@ class ImageMedium extends Medium
* Parsedown element for source display mode
*
* @param array $attributes
* @param boolean $reset
* @param bool $reset
* @return array
*/
public function sourceParsedownElement(array $attributes, $reset = true)
@@ -344,7 +348,7 @@ class ImageMedium extends Medium
$attributes['sizes'] = $this->sizes();
}
return [ 'name' => 'img', 'attributes' => $attributes ];
return ['name' => 'img', 'attributes' => $attributes];
}
/**
@@ -358,7 +362,7 @@ class ImageMedium extends Medium
if ($this->image) {
$this->image();
$this->querystring('');
$this->medium_querystring = [];
$this->filter();
$this->clearAlternatives();
}
@@ -374,7 +378,7 @@ class ImageMedium extends Medium
/**
* Turn the current Medium into a Link
*
* @param boolean $reset
* @param bool $reset
* @param array $attributes
* @return Link
*/
@@ -394,7 +398,7 @@ class ImageMedium extends Medium
*
* @param int $width
* @param int $height
* @param boolean $reset
* @param bool $reset
* @return Link
*/
public function lightbox($width = null, $height = null, $reset = true)
@@ -404,7 +408,7 @@ class ImageMedium extends Medium
}
if ($width && $height) {
$this->cropResize($width, $height);
$this->__call('cropResize', [$width, $height]);
}
return parent::lightbox($width, $height, $reset);
@@ -414,7 +418,7 @@ class ImageMedium extends Medium
* Sets or gets the quality of the image
*
* @param int $quality 0-100 quality
* @return Medium
* @return int|$this
*/
public function quality($quality = null)
{
@@ -424,6 +428,7 @@ class ImageMedium extends Medium
}
$this->quality = $quality;
return $this;
}
@@ -443,6 +448,7 @@ class ImageMedium extends Medium
}
$this->format = $format;
return $this;
}
@@ -457,6 +463,7 @@ class ImageMedium extends Medium
if ($sizes) {
$this->sizes = $sizes;
return $this;
}
@@ -477,10 +484,12 @@ class ImageMedium extends Medium
*/
public function width($value = 'auto')
{
if (!$value || $value === 'auto')
if (!$value || $value === 'auto') {
$this->attributes['width'] = $this->get('width');
else
} else {
$this->attributes['width'] = $value;
}
return $this;
}
@@ -498,10 +507,12 @@ class ImageMedium extends Medium
*/
public function height($value = 'auto')
{
if (!$value || $value === 'auto')
if (!$value || $value === 'auto') {
$this->attributes['height'] = $this->get('height');
else
} else {
$this->attributes['height'] = $value;
}
return $this;
}
@@ -608,7 +619,7 @@ class ImageMedium extends Medium
$this->image->merge(ImageFile::open($overlay));
}
return $this->image->cacheFile($this->format, $this->quality);
return $this->image->cacheFile($this->format, $this->quality, false, [$this->get('width'), $this->get('height'), $this->get('modified')]);
}
/**
+4 -3
View File
@@ -1,8 +1,9 @@
<?php
/**
* @package Grav.Common.Page
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -37,7 +38,7 @@ class Link implements RenderableInterface
* @param string $alt
* @param string $class
* @param string $id
* @param boolean $reset
* @param bool $reset
* @return array
*/
public function parsedownElement($title = null, $alt = null, $class = null, $id = null, $reset = true)

Some files were not shown because too many files have changed in this diff Show More