maj+ header

This commit is contained in:
2018-08-22 14:46:33 +02:00
parent 28c9e9d76f
commit 3fc62ab1f1
443 changed files with 24198 additions and 6201 deletions
+36
View File
@@ -996,6 +996,18 @@ form:
validate:
type: bool
session.initialize:
type: toggle
label: PLUGIN_ADMIN.SESSION_INITIALIZE
help: PLUGIN_ADMIN.SESSION_INITIALIZE_HELP
highlight: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
default: true
validate:
type: bool
session.timeout:
type: text
size: small
@@ -1206,3 +1218,27 @@ form:
placeholder: "e.g. http://yoursite.com/yourpath"
label: PLUGIN_ADMIN.CUSTOM_BASE_URL
help: PLUGIN_ADMIN.CUSTOM_BASE_URL_HELP
strict_mode.yaml_compat:
type: toggle
label: PLUGIN_ADMIN.STRICT_YAML_COMPAT
highlight: 1
default: 1
help: PLUGIN_ADMIN.STRICT_YAML_COMPAT_HELP
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
strict_mode.twig_compat:
type: toggle
label: PLUGIN_ADMIN.STRICT_TWIG_COMPAT
highlight: 1
default: 1
help: PLUGIN_ADMIN.STRICT_TWIG_COMPAT_HELP
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
+1
View File
@@ -94,6 +94,7 @@ form:
twofa_secret:
type: 2fa_secret
outerclasses: 'twofa-secret'
markdown: true
label: PLUGIN_ADMIN.2FA_SECRET
sublabel: PLUGIN_ADMIN.2FA_SECRET_HELP
+5 -1
View File
@@ -88,7 +88,7 @@ twig:
cache: true # Set to true to enable Twig caching
debug: true # Enable Twig debug
auto_reload: true # Refresh cache on changes
autoescape: false # Autoescape Twig vars
autoescape: false # Autoescape Twig vars (DEPRECATED, always enabled in strict mode)
undefined_functions: true # Allow undefined functions
undefined_filters: true # Allow undefined filters
umask_fix: false # By default Twig creates cached files as 755, fix switches this to 775
@@ -146,3 +146,7 @@ gpm:
method: 'auto' # Either 'curl', 'fopen' or 'auto'. 'auto' will try fopen first and if not available cURL
verify_peer: true # Sometimes on some systems (Windows most commonly) GPM is unable to connect because the SSL certificate cannot be verified. Disabling this setting might help.
official_gpm_only: true # By default GPM direct-install will only allow URLs via the official GPM proxy to ensure security
strict_mode:
yaml_compat: true # Grav 1.5+: Enables YAML backwards compatibility
twig_compat: true # Grav 1.5+: Enables deprecated Twig autoescape setting (autoescape: false)
+2 -2
View File
@@ -8,12 +8,12 @@
// Some standard defines
define('GRAV', true);
define('GRAV_VERSION', '1.4.8');
define('GRAV_VERSION', '1.5.0');
define('GRAV_TESTING', false);
define('DS', '/');
if (!defined('GRAV_PHP_MIN')) {
define('GRAV_PHP_MIN', '5.5.9');
define('GRAV_PHP_MIN', '5.6.4');
}
// Directories and Paths
+15 -11
View File
@@ -262,18 +262,22 @@ class Setup extends Data
);
}
if (!$locator->findResource('environment://config', true)) {
// If environment does not have its own directory, remove it from the lookup.
$this->set('streams.schemes.environment.prefixes', ['config' => []]);
$this->initializeLocator($locator);
}
try {
if (!$locator->findResource('environment://config', true)) {
// If environment does not have its own directory, remove it from the lookup.
$this->set('streams.schemes.environment.prefixes', ['config' => []]);
$this->initializeLocator($locator);
}
// 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();
// 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();
}
} catch (\RuntimeException $e) {
throw new \RuntimeException(sprintf('Grav failed to initialize: %s', $e->getMessage()), 500, $e);
}
}
}
+18 -8
View File
@@ -11,8 +11,8 @@ namespace Grav\Common\Data;
use Grav\Common\Grav;
use Grav\Common\Utils;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Parser;
use Symfony\Component\Yaml\Yaml;
use RocketTheme\Toolbox\Compat\Yaml\Yaml as FallbackYaml;
class Validation
{
@@ -107,7 +107,7 @@ class Validation
$method = 'filter' . ucfirst(strtr($type, '-', '_'));
// If this is a YAML field validate/filter as such
if ($type != 'yaml' && isset($field['yaml']) && $field['yaml'] === true) {
if ($type !== 'yaml' && isset($field['yaml']) && $field['yaml'] === true) {
$method = 'filterYaml';
}
@@ -128,10 +128,12 @@ class Validation
*/
public static function typeText($value, array $params, array $field)
{
if (!is_string($value)) {
if (!is_string($value) && !is_numeric($value)) {
return false;
}
$value = (string)$value;
if (isset($params['min']) && strlen($value) < $params['min']) {
return false;
}
@@ -643,13 +645,21 @@ class Validation
public static function filterYaml($value, $params)
{
if (!is_string($value)) {
return $value;
}
try {
if (is_string($value)) {
return (array) Yaml::parse($value);
} else {
return $value;
}
return (array) Yaml::parse($value);
} catch (ParseException $e) {
// If YAML compatibility mode is set on, fall back to older YAML parser.
if (Grav::instance()['config']->get('system.strict_mode.yaml_compat', true)) {
try {
return (array) FallbackYaml::parse($value);
} catch (ParseException $e2) {
}
}
return $value;
}
}
+7 -1
View File
@@ -58,8 +58,14 @@ class Debugger
$this->enabled = $this->config->get('system.debugger.enabled');
if ($this->enabled()) {
$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((array)$this->config->get('plugins'), 'Plugins'));
$this->debugbar->addCollector(new ConfigCollector($plugins_config, 'Plugins'));
$this->addMessage('Grav v' . GRAV_VERSION);
}
@@ -20,9 +20,6 @@ trait CompiledFile
*/
public function content($var = null)
{
// Set some options
$this->settings(['native' => true, 'compat' => true]);
try {
// If nothing has been loaded, attempt to get pre-compiled version of the file first.
if ($var === null && $this->raw === null && $this->content === null) {
+8 -3
View File
@@ -13,7 +13,7 @@ use Grav\Common\Filesystem\Folder;
use Grav\Common\Inflector;
use Grav\Common\Iterator;
use Grav\Common\Utils;
use Symfony\Component\Yaml\Yaml;
use RocketTheme\Toolbox\File\YamlFile;
class GPM extends Iterator
{
@@ -624,7 +624,10 @@ class GPM extends Iterator
return false;
}
$blueprint = (array)Yaml::parse(file_get_contents($blueprint_file));
$file = YamlFile::instance($blueprint_file);
$blueprint = (array)$file->content();
$file->free();
return $blueprint;
}
@@ -873,7 +876,9 @@ class GPM extends Iterator
// get currently installed version
$locator = Grav::instance()['locator'];
$blueprints_path = $locator->findResource('plugins://' . $dependency_slug . DS . 'blueprints.yaml');
$package_yaml = Yaml::parse(file_get_contents($blueprints_path));
$file = YamlFile::instance($blueprints_path);
$package_yaml = $file->content();
$file->free();
$currentlyInstalledVersion = $package_yaml['version'];
// if requirement is next significant release, check is compatible with currently installed version, might not be
+1 -1
View File
@@ -114,7 +114,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);
}
+3 -7
View File
@@ -9,7 +9,6 @@
namespace Grav\Common;
use Grav\Common\Config\Config;
use Grav\Common\Language\Language;
use Grav\Common\Page\Medium\ImageMedium;
use Grav\Common\Page\Medium\Medium;
use Grav\Common\Page\Page;
@@ -205,11 +204,8 @@ class Grav extends Container
*/
public function redirectLangSafe($route, $code = null)
{
/** @var Language $language */
$language = $this['language'];
if (!$this['uri']->isExternal($route) && $language->enabled() && $language->isIncludeDefaultLanguage()) {
$this->redirect($language->getLanguage() . $route, $code);
if (!$this['uri']->isExternal($route)) {
$this->redirect($this['pages']->route($route), $code);
} else {
$this->redirect($route, $code);
}
@@ -443,7 +439,7 @@ class Grav extends Container
/** @var Config $config */
$config = $this['config'];
$uri_extension = $uri->extension();
$uri_extension = strtolower($uri->extension());
$fallback_types = $config->get('system.media.allowed_fallback_types', null);
$supported_types = $config->get('media.types');
+4 -3
View File
@@ -190,10 +190,11 @@ class Inflector
public function hyphenize($word)
{
$regex1 = preg_replace('/([A-Z]+)([A-Z][a-z])/', '\1-\2', $word);
$regex2 = preg_replace('/([a-zd])([A-Z])/', '\1-\2', $regex1);
$regex3 = preg_replace('/[^A-Z^a-z^0-9]+/', '-', $regex2);
$regex2 = preg_replace('/([a-z])([A-Z])/', '\1-\2', $regex1);
$regex3 = preg_replace('/([0-9])([A-Z])/', '\1-\2', $regex2);
$regex4 = preg_replace('/[^A-Z^a-z^0-9]+/', '-', $regex3);
return strtolower($regex3);
return strtolower($regex4);
}
/**
@@ -0,0 +1,9 @@
<?php
namespace Grav\Common\Media\Interfaces;
/**
* Class implements media collection interface.
*/
interface MediaCollectionInterface
{
}
@@ -0,0 +1,29 @@
<?php
namespace Grav\Common\Media\Interfaces;
/**
* Class implements media interface.
*/
interface 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();
}
@@ -0,0 +1,9 @@
<?php
namespace Grav\Common\Media\Interfaces;
/**
* Class implements media object interface.
*/
interface MediaObjectInterface
{
}
@@ -0,0 +1,112 @@
<?php
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 RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
trait MediaTrait
{
protected $media;
/**
* Get filesystem path to the associated media.
*
* @return string|null
*/
abstract public function getMediaFolder();
/**
* Get display order for the associated media.
*
* @return array Empty array means default ordering.
*/
abstract public function getMediaOrder();
/**
* Get URI ot the associated media. Method will return null if path isn't URI.
*
* @return null|string
*/
public function getMediaUri()
{
$folder = $this->getMediaFolder();
if (strpos($folder, '://')) {
return $folder;
}
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
$user = $locator->findResource('user://');
if (strpos($folder, $user) === 0) {
return 'user://' . substr($folder, strlen($user)+1);
}
return null;
}
/**
* Gets the associated media collection.
*
* @return MediaCollectionInterface Representation of associated media.
*/
public function getMedia()
{
$cache = $this->getMediaCache();
if ($this->media === null) {
// Use cached media if possible.
$cacheKey = md5('media' . $this->getCacheKey());
if (!$media = $cache->fetch($cacheKey)) {
$media = new Media($this->getMediaFolder(), $this->getMediaOrder());
$cache->save($cacheKey, $media);
}
$this->media = $media;
}
return $this->media;
}
/**
* Sets the associated media collection.
*
* @param MediaCollectionInterface $media Representation of associated media.
* @return $this
*/
protected function setMedia(MediaCollectionInterface $media)
{
$cache = $this->getMediaCache();
$cacheKey = md5('media' . $this->getCacheKey());
$cache->save($cacheKey, $media);
$this->media = $media;
return $this;
}
/**
* Clear media cache.
*/
protected function clearMediaCache()
{
$cache = $this->getMediaCache();
$cacheKey = md5('media' . $this->getCacheKey());
$cache->delete($cacheKey);
}
/**
* @return Cache
*/
protected function getMediaCache()
{
return Grav::instance()['cache'];
}
/**
* @return string
*/
abstract protected function getCacheKey();
}
@@ -0,0 +1,9 @@
<?php
namespace Grav\Common\Page\Interfaces;
/**
* Class implements page interface.
*/
interface PageInterface
{
}
+4 -2
View File
@@ -24,11 +24,13 @@ class Media extends AbstractMedia
protected $standard_exif = ['FileSize', 'MimeType', 'height', 'width'];
/**
* @param $path
* @param string $path
* @param array $media_order
*/
public function __construct($path)
public function __construct($path, array $media_order = null)
{
$this->path = $path;
$this->media_order = $media_order;
$this->__wakeup();
$this->init();
@@ -10,9 +10,11 @@ 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\Utils;
abstract class AbstractMedia extends Getters
abstract class AbstractMedia extends Getters implements MediaCollectionInterface
{
protected $gettersVariable = 'instances';
@@ -21,6 +23,7 @@ abstract class AbstractMedia extends Getters
protected $videos = [];
protected $audios = [];
protected $files = [];
protected $media_order;
/**
* Get medium by filename.
@@ -62,7 +65,7 @@ abstract class AbstractMedia extends Getters
/**
* Get a list of all media.
*
* @return array|Medium[]
* @return array|MediaObjectInterface[]
*/
public function all()
{
@@ -74,7 +77,7 @@ abstract class AbstractMedia extends Getters
/**
* Get a list of all image media.
*
* @return array|Medium[]
* @return array|MediaObjectInterface[]
*/
public function images()
{
@@ -85,7 +88,7 @@ abstract class AbstractMedia extends Getters
/**
* Get a list of all video media.
*
* @return array|Medium[]
* @return array|MediaObjectInterface[]
*/
public function videos()
{
@@ -96,7 +99,7 @@ abstract class AbstractMedia extends Getters
/**
* Get a list of all audio media.
*
* @return array|Medium[]
* @return array|MediaObjectInterface[]
*/
public function audios()
{
@@ -107,7 +110,7 @@ abstract class AbstractMedia extends Getters
/**
* Get a list of all file media.
*
* @return array|Medium[]
* @return array|MediaObjectInterface[]
*/
public function files()
{
@@ -117,7 +120,7 @@ abstract class AbstractMedia extends Getters
/**
* @param string $name
* @param Medium $file
* @param MediaObjectInterface $file
*/
protected function add($name, $file)
{
@@ -145,14 +148,20 @@ abstract class AbstractMedia extends Getters
*/
protected function orderMedia($media)
{
$page = Grav::instance()['pages']->get($this->path);
if (null === $this->media_order) {
$page = Grav::instance()['pages']->get($this->path);
if ($page && isset($page->header()->media_order)) {
$media_order = array_map('trim', explode(',', $page->header()->media_order));
$media = Utils::sortArrayByArray($media, $media_order);
if ($page && isset($page->header()->media_order)) {
$this->media_order = array_map('trim', explode(',', $page->header()->media_order));
}
}
if (!empty($this->media_order) && is_array($this->media_order)) {
$media = Utils::sortArrayByArray($media, $this->media_order);
} else {
ksort($media, SORT_NATURAL | SORT_FLAG_CASE);
}
return $media;
}
@@ -11,6 +11,7 @@ namespace Grav\Common\Page\Medium;
use Grav\Common\Data\Blueprint;
use Grav\Common\Grav;
use Grav\Common\Utils;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
class ImageMedium extends Medium
{
@@ -164,12 +165,18 @@ class ImageMedium extends Medium
*/
public function url($reset = true)
{
$image_path = Grav::instance()['locator']->findResource('cache://images', true);
$image_dir = Grav::instance()['locator']->findResource('cache://images', false);
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
$image_path = $locator->findResource('cache://images', true);
$image_dir = $locator->findResource('cache://images', false);
$saved_image_path = $this->saveImage();
$output = preg_replace('|^' . preg_quote(GRAV_ROOT, '|') . '|', '', $saved_image_path);
if ($locator->isStream($output)) {
$output = $locator->findResource($output, false);
}
if (Utils::startsWith($output, $image_path)) {
$output = '/' . $image_dir . preg_replace('|^' . preg_quote($image_path, '|') . '|', '', $output);
}
@@ -12,9 +12,9 @@ use Grav\Common\File\CompiledYamlFile;
use Grav\Common\Grav;
use Grav\Common\Data\Data;
use Grav\Common\Data\Blueprint;
use Grav\Common\Utils;
use Grav\Common\Media\Interfaces\MediaObjectInterface;
class Medium extends Data implements RenderableInterface
class Medium extends Data implements RenderableInterface, MediaObjectInterface
{
use ParsedownHtmlTrait;
@@ -199,7 +199,12 @@ class Medium extends Data implements RenderableInterface
*/
public function url($reset = true)
{
$output = preg_replace('|^' . preg_quote(GRAV_ROOT) . '|', '', $this->get('filepath'));
$output = preg_replace('|^' . preg_quote(GRAV_ROOT, '|') . '|', '', $this->get('filepath'));
$locator = Grav::instance()['locator'];
if ($locator->isStream($output)) {
$output = $locator->findResource($output, false);
}
if ($reset) {
$this->reset();
@@ -94,6 +94,40 @@ class VideoMedium extends Medium
return $this;
}
/**
* Allows to set the playsinline attribute
*
* @param bool $status
* @return $this
*/
public function playsinline($status = false)
{
if($status) {
$this->attributes['playsinline'] = true;
} else {
unset($this->attributes['playsinline']);
}
return $this;
}
/**
* Allows to set the muted attribute
*
* @param bool $status
* @return $this
*/
public function muted($status = false)
{
if($status) {
$this->attributes['muted'] = true;
} else {
unset($this->attributes['muted']);
}
return $this;
}
/**
* Reset medium.
*
+59 -49
View File
@@ -12,11 +12,14 @@ use Exception;
use Grav\Common\Cache;
use Grav\Common\Config\Config;
use Grav\Common\Data\Blueprint;
use Grav\Common\File\CompiledYamlFile;
use Grav\Common\Filesystem\Folder;
use Grav\Common\Grav;
use Grav\Common\Language\Language;
use Grav\Common\Markdown\Parsedown;
use Grav\Common\Markdown\ParsedownExtra;
use Grav\Common\Page\Interfaces\PageInterface;
use Grav\Common\Media\Traits\MediaTrait;
use Grav\Common\Taxonomy;
use Grav\Common\Uri;
use Grav\Common\Utils;
@@ -27,8 +30,10 @@ use Symfony\Component\Yaml\Yaml;
define('PAGE_ORDER_PREFIX_REGEX', '/^[0-9]+\./u');
class Page
class Page implements PageInterface
{
use MediaTrait;
/**
* @var string Filename. Leave as null if page is folder.
*/
@@ -65,7 +70,6 @@ class Page
protected $summary;
protected $raw_content;
protected $pagination;
protected $media;
protected $metadata;
protected $title;
protected $max_count;
@@ -318,8 +322,6 @@ class Page
if (!$this->header) {
$file = $this->file();
if ($file) {
// Set some options
$file->settings(['native' => true, 'compat' => true]);
try {
$this->raw_content = $file->markdown();
$this->frontmatter = $file->frontmatter();
@@ -328,11 +330,12 @@ class Page
if (!Utils::isAdminPlugin()) {
// If there's a `frontmatter.yaml` file merge that in with the page header
// note page's own frontmatter has precedence and will overwrite any defaults
$frontmatter_file = $this->path . '/' . $this->folder . '/frontmatter.yaml';
if (file_exists($frontmatter_file)) {
$frontmatter_data = (array)Yaml::parse(file_get_contents($frontmatter_file));
$frontmatterFile = CompiledYamlFile::instance($this->path . '/' . $this->folder . '/frontmatter.yaml');
if ($frontmatterFile->exists()) {
$frontmatter_data = (array)$frontmatterFile->content();
$this->header = (object)array_replace_recursive($frontmatter_data,
(array)$this->header);
$frontmatterFile->free();
}
// Process frontmatter with Twig if enabled
if (Grav::instance()['config']->get('system.pages.frontmatter.process_twig') === true) {
@@ -1124,6 +1127,14 @@ class Page
return json_encode($this->toArray());
}
/**
* @return string
*/
protected function getCacheKey()
{
return $this->id();
}
/**
* Gets and sets the associated media as found in the page folder.
*
@@ -1133,23 +1144,33 @@ class Page
*/
public function media($var = null)
{
/** @var Cache $cache */
$cache = Grav::instance()['cache'];
if ($var) {
$this->media = $var;
}
if ($this->media === null) {
// Use cached media if possible.
$media_cache_id = md5('media' . $this->id());
if (!$media = $cache->fetch($media_cache_id)) {
$media = new Media($this->path());
$cache->save($media_cache_id, $media);
}
$this->media = $media;
$this->setMedia($var);
}
return $this->media;
return $this->getMedia();
}
/**
* Get filesystem path to the associated media.
*
* @return string|null
*/
public function getMediaFolder()
{
return $this->path();
}
/**
* Get display order for the associated media.
*
* @return array Empty array means default ordering.
*/
public function getMediaOrder()
{
$header = $this->header();
return isset($header->media_order) ? array_map('trim', explode(',', (string)$header->media_order)) : [];
}
/**
@@ -1628,14 +1649,19 @@ class Page
* 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 $canonical True to return the canonical URL
* @param bool $include_base Include base url on multisite as well as language code
* @param bool $raw_route
*
* @return string The url.
*/
public function url($include_host = false, $canonical = false, $include_lang = true, $raw_route = false)
public function url($include_host = false, $canonical = false, $include_base = true, $raw_route = false)
{
// Override any URL when external_url is set
if (isset($this->external_url)) {
return $this->external_url;
}
$grav = Grav::instance();
/** @var Pages $pages */
@@ -1644,41 +1670,25 @@ class Page
/** @var Config $config */
$config = $grav['config'];
/** @var Language $language */
$language = $grav['language'];
/** @var Uri $uri */
$uri = $grav['uri'];
// Override any URL when external_url is set
if (isset($this->external_url)) {
return $this->external_url;
}
// get pre-route
if ($include_lang && $language->enabled()) {
$pre_route = $language->getLanguageURLPrefix();
} else {
$pre_route = '';
}
// get base route (multisite base and language)
$route = $include_base ? $pages->baseRoute() : '';
// add full route if configured to do so
if ($config->get('system.absolute_urls', false)) {
if (!$include_host && $config->get('system.absolute_urls', false)) {
$include_host = true;
}
// get canonical route if requested
if ($canonical) {
$route = $pre_route . $this->routeCanonical();
$route .= $this->routeCanonical();
} elseif ($raw_route) {
$route = $pre_route . $this->rawRoute();
$route .= $this->rawRoute();
} else {
$route = $pre_route . $this->route();
$route .= $this->route();
}
$rootUrl = $uri->rootUrl($include_host) . $pages->base();
$url = $rootUrl . '/' . trim($route, '/') . $this->urlExtension();
/** @var Uri $uri */
$uri = $grav['uri'];
$url = $uri->rootUrl($include_host) . '/' . trim($route, '/') . $this->urlExtension();
// trim trailing / if not root
if ($url !== '/') {
+50 -28
View File
@@ -49,7 +49,7 @@ class Pages
/**
* @var array|string[]
*/
protected $baseUrl = [];
protected $baseRoute = [];
/**
* @var array|string[]
@@ -120,47 +120,69 @@ class Pages
if ($path !== null) {
$path = trim($path, '/');
$this->base = $path ? '/' . $path : null;
$this->baseUrl = [];
$this->baseRoute = [];
}
return $this->base;
}
/**
*
* Get base route for Grav pages.
*
* @param string $lang Optional language code for multilingual routes.
*
* @return string
*/
public function baseRoute($lang = null)
{
$key = $lang ?: 'default';
if (!isset($this->baseRoute[$key])) {
/** @var Language $language */
$language = $this->grav['language'];
$path_base = rtrim($this->base(), '/');
$path_lang = $language->enabled() ? $language->getLanguageURLPrefix($lang) : '';
$this->baseRoute[$key] = $path_base . $path_lang;
}
return $this->baseRoute[$key];
}
/**
*
* Get route for Grav site.
*
* @param string $route Optional route to the page.
* @param string $lang Optional language code for multilingual links.
*
* @return string
*/
public function route($route = '/', $lang = null)
{
if (!$route || $route === '/') {
return $this->baseRoute($lang) ?: '/';
}
return $this->baseRoute($lang) . $route;
}
/**
*
* Get base URL for Grav pages.
*
* @param string $lang Optional language code for multilingual links.
* @param bool $absolute If true, return absolute url, if false, return relative url. Otherwise return default.
* @param string $lang Optional language code for multilingual links.
* @param bool|null $absolute If true, return absolute url, if false, return relative url. Otherwise return default.
*
* @return string
*/
public function baseUrl($lang = null, $absolute = null)
{
$lang = (string) $lang;
$type = $absolute === null ? 'base_url' : ($absolute ? 'base_url_absolute' : 'base_url_relative');
$key = "{$lang} {$type}";
if (!isset($this->baseUrl[$key])) {
/** @var Config $config */
$config = $this->grav['config'];
/** @var Language $language */
$language = $this->grav['language'];
if (!$lang) {
$lang = $language->getActive();
}
$path_append = rtrim($this->grav['pages']->base(), '/');
if ($language->getDefault() !== $lang || $config->get('system.languages.include_default_lang') === true) {
$path_append .= $lang ? '/' . $lang : '';
}
$this->baseUrl[$key] = $this->grav[$type] . $path_append;
}
return $this->baseUrl[$key];
return $this->grav[$type] . $this->baseRoute($lang);
}
/**
@@ -179,7 +201,7 @@ class Pages
/**
*
* Get home URL for Grav site.
* Get URL for Grav site.
*
* @param string $route Optional route to the page.
* @param string $lang Optional language code for multilingual links.
@@ -189,7 +211,7 @@ class Pages
*/
public function url($route = '/', $lang = null, $absolute = null)
{
if ($route === '/') {
if (!$route || $route === '/') {
return $this->homeUrl($lang, $absolute);
}
@@ -8,6 +8,10 @@
namespace Grav\Common\Processors;
use Grav\Common\Config\Config;
use Grav\Common\Uri;
use Grav\Common\Utils;
class InitializeProcessor extends ProcessorBase implements ProcessorInterface
{
public $id = 'init';
@@ -15,29 +19,36 @@ class InitializeProcessor extends ProcessorBase implements ProcessorInterface
public function process()
{
$this->container['config']->debug();
/** @var Config $config */
$config = $this->container['config'];
$config->debug();
// Use output buffering to prevent headers from being sent too early.
ob_start();
if ($this->container['config']->get('system.cache.gzip')) {
if ($config->get('system.cache.gzip') && !@ob_start('ob_gzhandler')) {
// Enable zip/deflate with a fallback in case of if browser does not support compressing.
if (!@ob_start("ob_gzhandler")) {
ob_start();
}
ob_start();
}
// Initialize the timezone.
if ($this->container['config']->get('system.timezone')) {
if ($config->get('system.timezone')) {
date_default_timezone_set($this->container['config']->get('system.timezone'));
}
// FIXME: Initialize session should happen later after plugins have been loaded. This is a workaround to fix session issues in AWS.
if ($this->container['config']->get('system.session.initialize', 1) && isset($this->container['session'])) {
if (isset($this->container['session']) && $config->get('system.session.initialize', true)) {
$this->container['session']->init();
}
// Initialize uri.
$this->container['uri']->init();
/** @var Uri $uri */
$uri = $this->container['uri'];
$uri->init();
// Redirect pages with trailing slash if configured to do so.
$path = $uri->path() ?: '/';
if ($path !== '/' && $config->get('system.pages.redirect_trailing_slash', false) && Utils::endsWith($path, '/')) {
$this->container->redirect(rtrim($path, '/'), 302);
}
$this->container->setLocale();
}
@@ -16,6 +16,7 @@ use Grav\Common\Config\ConfigFileFinder;
use Grav\Common\Config\Setup;
use Pimple\Container;
use Pimple\ServiceProviderInterface;
use RocketTheme\Toolbox\File\YamlFile;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
class ConfigServiceProvider implements ServiceProviderInterface
@@ -31,7 +32,14 @@ class ConfigServiceProvider implements ServiceProviderInterface
};
$container['config'] = function ($c) {
return static::load($c);
$config = static::load($c);
// After configuration has been loaded, we can disable YAML compatibility if strict mode has been enabled.
if (!$config->get('system.strict_mode.yaml_compat', true)) {
YamlFile::globalSettings(['compat' => false, 'native' => true]);
}
return $config;
};
$container['languages'] = function ($c) {
@@ -65,6 +73,10 @@ class ConfigServiceProvider implements ServiceProviderInterface
return $blueprints->name("master-{$setup->environment}")->load();
}
/**
* @param Container $container
* @return Config
*/
public static function load(Container $container)
{
/** Setup $setup */
@@ -8,6 +8,7 @@
namespace Grav\Common\Service;
use Grav\Common\Config\Config;
use Grav\Common\Grav;
use Grav\Common\Language\Language;
use Grav\Common\Page\Page;
@@ -26,35 +27,33 @@ class PageServiceProvider implements ServiceProviderInterface
/** @var Pages $pages */
$pages = $c['pages'];
/** @var Config $config */
$config = $c['config'];
/** @var Uri $uri */
$uri = $c['uri'];
$path = $uri->path(); // Don't trim to support trailing slash default routes
$path = $path ?: '/';
$path = $uri->path() ?: '/'; // Don't trim to support trailing slash default routes
$page = $pages->dispatch($path);
// Redirection tests
if ($page) {
/** @var Language $language */
$language = $c['language'];
// some debugger override logic
if ($page->debugger() === false) {
$c['debugger']->enabled(false);
}
if ($c['config']->get('system.force_ssl')) {
if (!isset($_SERVER['HTTPS']) || $_SERVER["HTTPS"] != "on") {
$url = "https://" . $_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"];
if ($config->get('system.force_ssl')) {
if (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] !== 'on') {
$url = 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
$c->redirect($url);
}
}
$url = $page->route();
$url = $pages->route($page->route());
if ($uri->params()) {
if ($url == '/') { //Avoid double slash
if ($url === '/') { //Avoid double slash
$url = $uri->params();
} else {
$url .= $uri->params();
@@ -67,18 +66,16 @@ class PageServiceProvider implements ServiceProviderInterface
$url .= '#' . $uri->fragment();
}
/** @var Language $language */
$language = $c['language'];
// Language-specific redirection scenarios
if ($language->enabled()) {
if ($language->isLanguageInUrl() && !$language->isIncludeDefaultLanguage()) {
$c->redirect($url);
}
if (!$language->isLanguageInUrl() && $language->isIncludeDefaultLanguage()) {
$c->redirectLangSafe($url);
}
if ($language->enabled() && ($language->isLanguageInUrl() xor $language->isIncludeDefaultLanguage())) {
$c->redirect($url);
}
// Default route test and redirect
if ($c['config']->get('system.pages.redirect_default_route') && $page->route() != $path) {
$c->redirectLangSafe($url);
if ($config->get('system.pages.redirect_default_route') && $page->route() !== $path) {
$c->redirect($url);
}
}
@@ -29,21 +29,22 @@ class SessionServiceProvider implements ServiceProviderInterface
/** @var Uri $uri */
$uri = $c['uri'];
// Get session parameters.
$session_timeout = (int)$config->get('system.session.timeout', 1800);
$session_path = $config->get('system.session.path');
if (null === $session_path) {
$session_path = '/' . ltrim(Uri::filterPath($uri->rootUrl(false)), '/');
}
$domain = $uri->host();
if ($domain === 'localhost') {
$domain = '';
}
// Get session options.
$secure = (bool)$config->get('system.session.secure', false);
$httponly = (bool)$config->get('system.session.httponly', true);
$enabled = (bool)$config->get('system.session.enabled', false);
$cookie_secure = (bool)$config->get('system.session.secure', false);
$cookie_httponly = (bool)$config->get('system.session.httponly', true);
$cookie_lifetime = (int)$config->get('system.session.timeout', 1800);
$cookie_path = $config->get('system.session.path');
if (null === $cookie_path) {
$cookie_path = '/' . trim(Uri::filterPath($uri->rootUrl(false)), '/');
}
// Session cookie path requires trailing slash.
$cookie_path = rtrim($cookie_path, '/') . '/';
$cookie_domain = $uri->host();
if ($cookie_domain === 'localhost') {
$cookie_domain = '';
}
// Activate admin if we're inside the admin path.
$is_admin = false;
@@ -56,14 +57,14 @@ class SessionServiceProvider implements ServiceProviderInterface
// Check no language, simple language prefix (en) and region specific language prefix (en-US).
$pos = strpos($current_route, $base);
if ($pos === 0 || $pos === 3 || $pos === 6) {
$session_timeout = $config->get('plugins.admin.session.timeout', 1800);
$cookie_lifetime = $config->get('plugins.admin.session.timeout', 1800);
$enabled = $is_admin = true;
}
}
// Fix for HUGE session timeouts.
if ($session_timeout > 99999999999) {
$session_timeout = 9999999999;
if ($cookie_lifetime > 99999999999) {
$cookie_lifetime = 9999999999;
}
$inflector = new Inflector();
@@ -73,10 +74,16 @@ class SessionServiceProvider implements ServiceProviderInterface
}
// Define session service.
$session = new Session($session_timeout, $session_path, $domain);
$session->setName($session_name);
$session->setSecure($secure);
$session->setHttpOnly($httponly);
$options = [
'name' => $session_name,
'cookie_lifetime' => $cookie_lifetime,
'cookie_path' => $cookie_path,
'cookie_domain' => $cookie_domain,
'cookie_secure' => $cookie_secure,
'cookie_httponly' => $cookie_httponly
] + (array) $config->get('system.session.options');
$session = new Session($options);
$session->setAutoStart($enabled);
return $session;
+17 -38
View File
@@ -8,34 +8,18 @@
namespace Grav\Common;
use RocketTheme\Toolbox\Session\Session as BaseSession;
class Session extends BaseSession
class Session extends \Grav\Framework\Session\Session
{
/** @var bool */
protected $autoStart = false;
protected $lifetime;
protected $path;
protected $domain;
protected $secure;
protected $httpOnly;
/**
* @param int $lifetime Defaults to 1800 seconds.
* @param string $path Cookie path.
* @param string $domain Optional, domain for the session
* @throws \RuntimeException
* @return \Grav\Framework\Session\Session
* @deprecated 1.5
*/
public function __construct($lifetime, $path, $domain = null)
public static function instance()
{
$this->lifetime = $lifetime;
$this->path = $path;
$this->domain = $domain;
if (php_sapi_name() !== 'cli') {
parent::__construct($lifetime, $path, $domain);
}
return static::getInstance();
}
/**
@@ -48,9 +32,6 @@ class Session extends BaseSession
if ($this->autoStart) {
$this->start();
// TODO: This setcookie shouldn't be here, session should by itself be able to update its cookie.
setcookie(session_name(), session_id(), $this->lifetime ? time() + $this->lifetime : 0, $this->path, $this->domain, $this->secure, $this->httpOnly);
$this->autoStart = false;
}
}
@@ -67,27 +48,25 @@ class Session extends BaseSession
}
/**
* @param bool $secure
* @return $this
* Returns attributes.
*
* @return array Attributes
* @deprecated 1.5
*/
public function setSecure($secure)
public function all()
{
$this->secure = $secure;
ini_set('session.cookie_secure', (bool)$secure);
return $this;
return $this->getAll();
}
/**
* @param bool $httpOnly
* @return $this
* Checks if the session was started.
*
* @return Boolean
* @deprecated 1.5
*/
public function setHttpOnly($httpOnly)
public function started()
{
$this->httpOnly = $httpOnly;
ini_set('session.cookie_httponly', (bool)$httpOnly);
return $this;
return $this->isStarted();
}
/**
@@ -12,7 +12,7 @@ class TwigNodeMarkdown extends \Twig_Node implements \Twig_NodeOutputInterface
{
public function __construct(\Twig_Node $body, $lineno, $tag = 'markdown')
{
parent::__construct(array('body' => $body), array(), $lineno, $tag);
parent::__construct(['body' => $body], [], $lineno, $tag);
}
/**
* Compiles the node to PHP.
@@ -24,20 +24,17 @@ class TwigNodeSwitch extends \Twig_Node implements \Twig_NodeOutputInterface
{
$compiler
->addDebugInfo($this)
->write("switch (")
->write('switch (')
->subcompile($this->getNode('value'))
->raw(") {\n")
->indent();
foreach ($this->getNode('cases') as $case)
{
if (!$case->hasNode('body'))
{
foreach ($this->getNode('cases') as $case) {
if (!$case->hasNode('body')) {
continue;
}
foreach ($case->getNode('values') as $value)
{
foreach ($case->getNode('values') as $value) {
$compiler
->write('case ')
->subcompile($value)
@@ -53,8 +50,7 @@ class TwigNodeSwitch extends \Twig_Node implements \Twig_NodeOutputInterface
->write("}\n");
}
if ($this->hasNode('default') && $this->getNode('default') !== null)
{
if ($this->hasNode('default') && $this->getNode('default') !== null) {
$compiler
->write("default:\n")
->write("{\n")
@@ -37,8 +37,7 @@ class TwigTokenParserSwitch extends \Twig_TokenParser
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
// There can be some whitespace between the {% switch %} and first {% case %} tag.
while ($stream->getCurrent()->getType() == \Twig_Token::TEXT_TYPE && trim($stream->getCurrent()->getValue()) == '')
{
while ($stream->getCurrent()->getType() === \Twig_Token::TEXT_TYPE && trim($stream->getCurrent()->getValue()) === '') {
$stream->next();
}
@@ -47,56 +46,45 @@ class TwigTokenParserSwitch extends \Twig_TokenParser
$expressionParser = $this->parser->getExpressionParser();
$default = null;
$cases = array();
$cases = [];
$end = false;
while (!$end)
{
while (!$end) {
$next = $stream->next();
switch ($next->getValue())
{
switch ($next->getValue()) {
case 'case':
{
$values = array();
$values = [];
while (true)
{
$values[] = $expressionParser->parsePrimaryExpression();
// Multiple allowed values?
if ($stream->test(\Twig_Token::OPERATOR_TYPE, 'or'))
{
$stream->next();
}
else
{
break;
}
while (true) {
$values[] = $expressionParser->parsePrimaryExpression();
// Multiple allowed values?
if ($stream->test(\Twig_Token::OPERATOR_TYPE, 'or')) {
$stream->next();
} else {
break;
}
}
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
$body = $this->parser->subparse(array($this, 'decideIfFork'));
$cases[] = new \Twig_Node([
'values' => new \Twig_Node($values),
'body' => $body
]);
break;
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
$body = $this->parser->subparse(array($this, 'decideIfFork'));
$cases[] = new \Twig_Node(array(
'values' => new \Twig_Node($values),
'body' => $body
));
break;
}
case 'default':
{
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
$default = $this->parser->subparse(array($this, 'decideIfEnd'));
break;
}
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
$default = $this->parser->subparse(array($this, 'decideIfEnd'));
break;
case 'endswitch':
{
$end = true;
break;
}
$end = true;
break;
default:
{
throw new \Twig_Error_Syntax(sprintf('Unexpected end of template. Twig was looking for the following tags "case", "default", or "endswitch" to close the "switch" block started at line %d)', $lineno), -1);
}
throw new \Twig_Error_Syntax(sprintf('Unexpected end of template. Twig was looking for the following tags "case", "default", or "endswitch" to close the "switch" block started at line %d)', $lineno), -1);
}
}
@@ -127,7 +115,6 @@ class TwigTokenParserSwitch extends \Twig_TokenParser
return $token->test(array('endswitch'));
}
/**
* {@inheritdoc}
*/
+4 -1
View File
@@ -113,7 +113,10 @@ class Twig
$params['cache'] = new \Twig_Cache_Filesystem($cachePath, \Twig_Cache_Filesystem::FORCE_BYTECODE_INVALIDATION);
}
if (!empty($this->autoescape)) {
if (!$config->get('system.strict_mode.twig_compat', true)) {
// Force autoescape on for all files if in strict mode.
$params['autoescape'] = true;
} elseif (!empty($this->autoescape)) {
$params['autoescape'] = $this->autoescape;
}
+77 -25
View File
@@ -72,7 +72,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
new \Twig_SimpleFilter('fieldName', [$this, 'fieldNameFilter']),
new \Twig_SimpleFilter('ksort', [$this, 'ksortFilter']),
new \Twig_SimpleFilter('ltrim', [$this, 'ltrimFilter']),
new \Twig_SimpleFilter('markdown', [$this, 'markdownFunction']),
new \Twig_SimpleFilter('markdown', [$this, 'markdownFunction'], ['is_safe' => ['html']]),
new \Twig_SimpleFilter('md5', [$this, 'md5Filter']),
new \Twig_SimpleFilter('base32_encode', [$this, 'base32EncodeFilter']),
new \Twig_SimpleFilter('base32_decode', [$this, 'base32DecodeFilter']),
@@ -88,9 +88,6 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
new \Twig_SimpleFilter('safe_truncate_html', ['\Grav\Common\Utils', 'safeTruncateHTML']),
new \Twig_SimpleFilter('sort_by_key', [$this, 'sortByKeyFilter']),
new \Twig_SimpleFilter('starts_with', [$this, 'startsWithFilter']),
new \Twig_SimpleFilter('t', [$this, 'translate']),
new \Twig_SimpleFilter('tl', [$this, 'translateLanguage']),
new \Twig_SimpleFilter('ta', [$this, 'translateArray']),
new \Twig_SimpleFilter('truncate', ['\Grav\Common\Utils', 'truncate']),
new \Twig_SimpleFilter('truncate_html', ['\Grav\Common\Utils', 'truncateHTML']),
new \Twig_SimpleFilter('json_decode', [$this, 'jsonDecodeFilter']),
@@ -100,6 +97,18 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
new \Twig_SimpleFilter('print_r', 'print_r'),
new \Twig_SimpleFilter('yaml_encode', [$this, 'yamlEncodeFilter']),
new \Twig_SimpleFilter('yaml_decode', [$this, 'yamlDecodeFilter']),
// Translations
new \Twig_SimpleFilter('t', [$this, 'translate']),
new \Twig_SimpleFilter('tl', [$this, 'translateLanguage']),
new \Twig_SimpleFilter('ta', [$this, 'translateArray']),
// Casting values
new \Twig_SimpleFilter('string', [$this, 'stringFilter']),
new \Twig_SimpleFilter('int', [$this, 'intFilter'], ['is_safe' => true]),
new \Twig_SimpleFilter('bool', [$this, 'boolFilter']),
new \Twig_SimpleFilter('float', [$this, 'floatFilter'], ['is_safe' => true]),
new \Twig_SimpleFilter('array', [$this, 'arrayFilter']),
];
}
@@ -111,7 +120,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
public function getFunctions()
{
return [
new \Twig_SimpleFunction('array', [$this, 'arrayFunc']),
new \Twig_SimpleFunction('array', [$this, 'arrayFilter']),
new \Twig_SimpleFunction('array_key_value', [$this, 'arrayKeyValueFunc']),
new \Twig_SimpleFunction('array_key_exists', 'array_key_exists'),
new \Twig_SimpleFunction('array_unique', 'array_unique'),
@@ -132,9 +141,6 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
new \Twig_SimpleFunction('regex_replace', [$this, 'regexReplace']),
new \Twig_SimpleFunction('regex_filter', [$this, 'regexFilter']),
new \Twig_SimpleFunction('string', [$this, 'stringFunc']),
new \Twig_simpleFunction('t', [$this, 'translate']),
new \Twig_simpleFunction('tl', [$this, 'translateLanguage']),
new \Twig_simpleFunction('ta', [$this, 'translateArray']),
new \Twig_SimpleFunction('url', [$this, 'urlFunc']),
new \Twig_SimpleFunction('json_decode', [$this, 'jsonDecodeFilter']),
new \Twig_SimpleFunction('get_cookie', [$this, 'getCookie']),
@@ -151,6 +157,10 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
new \Twig_SimpleFunction('nicefilesize', [$this, 'niceFilesizeFunc']),
new \Twig_SimpleFunction('nicetime', [$this, 'nicetimeFilter']),
// Translations
new \Twig_simpleFunction('t', [$this, 'translate']),
new \Twig_simpleFunction('tl', [$this, 'translateLanguage']),
new \Twig_simpleFunction('ta', [$this, 'translateArray']),
];
}
@@ -617,6 +627,62 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
return ltrim($value, $chars);
}
/**
* Casts input to string.
*
* @param mixed $input
* @return string
*/
public function stringFilter($input)
{
return (string) $input;
}
/**
* Casts input to int.
*
* @param mixed $input
* @return int
*/
public function intFilter($input)
{
return (int) $input;
}
/**
* Casts input to bool.
*
* @param mixed $input
* @return bool
*/
public function boolFilter($input)
{
return (bool) $input;
}
/**
* Casts input to float.
*
* @param mixed $input
* @return float
*/
public function floatFilter($input)
{
return (float) $input;
}
/**
* Casts input to array.
*
* @param mixed $input
* @return array
*/
public function arrayFilter($input)
{
return (array) $input;
}
/**
* @return mixed
*/
@@ -693,7 +759,6 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
$template = $env->createTemplate($twig);
return $template->render($context);
;
}
/**
@@ -748,7 +813,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
* Output a Gist
*
* @param string $id
* @param string $file
* @param string|bool $file
*
* @return string
*/
@@ -788,19 +853,6 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
return str_pad($input, (int)$pad_length, $pad_string, $pad_type);
}
/**
* Cast a value to array
*
* @param $value
*
* @return array
*/
public function arrayFunc($value)
{
return (array)$value;
}
/**
* Workaround for twig associative array initialization
* Returns a key => val array
@@ -976,7 +1028,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
public function redirectFunc($url, $statusCode = 303)
{
header('Location: ' . $url, true, $statusCode);
die();
exit();
}
/**
@@ -1060,7 +1112,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
if (file_exists($filepath)) {
return file_get_contents($filepath);
}
}
return false;
}
+41 -25
View File
@@ -11,6 +11,7 @@ namespace Grav\Common;
use Grav\Common\Config\Config;
use Grav\Common\Language\Language;
use Grav\Common\Page\Page;
use Grav\Common\Page\Pages;
use Grav\Framework\Route\RouteFactory;
use Grav\Framework\Uri\UriFactory;
use Grav\Framework\Uri\UriPartsFilter;
@@ -156,12 +157,6 @@ class Uri
$uri = preg_replace('|^' . preg_quote($setup_base, '|') . '|', '', $uri);
}
// If configured to, redirect trailing slash URI's with a 302 redirect
$redirect = str_replace($this->root, '', rtrim($uri, '/'));
if ($redirect && $uri !== '/' && $redirect !== $this->base() && $config->get('system.pages.redirect_trailing_slash', false) && Utils::endsWith($uri, '/')) {
$grav->redirect($redirect, 302);
}
// process params
$uri = $this->processParams($uri, $config->get('system.param_sep'));
@@ -206,9 +201,9 @@ class Uri
}
// Set some Grav stuff
$grav['base_url_absolute'] = $grav['config']->get('system.custom_base_url') ?: $this->rootUrl(true);
$grav['base_url_absolute'] = $config->get('system.custom_base_url') ?: $this->rootUrl(true);
$grav['base_url_relative'] = $this->rootUrl(false);
$grav['base_url'] = $grav['config']->get('system.absolute_urls') ? $grav['base_url_absolute'] : $grav['base_url_relative'];
$grav['base_url'] = $config->get('system.absolute_urls') ? $grav['base_url_absolute'] : $grav['base_url_relative'];
RouteFactory::setRoot($this->root_path);
RouteFactory::setLanguage($language->getLanguageURLPrefix());
@@ -376,6 +371,17 @@ class Uri
return $this->extension;
}
public function method()
{
$method = isset($_SERVER['REQUEST_METHOD']) ? strtoupper($_SERVER['REQUEST_METHOD']) : 'GET';
if ($method === 'POST' && isset($_SERVER['X-HTTP-METHOD-OVERRIDE'])) {
$method = strtoupper($_SERVER['X-HTTP-METHOD-OVERRIDE']);
}
return $method;
}
/**
* Return the scheme of the URI
*
@@ -481,11 +487,9 @@ class Uri
{
if ($include_root) {
return $this->uri;
} else {
$uri = str_replace($this->root_path, '', $this->uri);
return $uri;
}
return str_replace($this->root_path, '', $this->uri);
}
/**
@@ -508,16 +512,10 @@ class Uri
{
$grav = Grav::instance();
// Link processing should prepend language
$language = $grav['language'];
$language_append = '';
if ($language->enabled()) {
$language_append = $language->getLanguageURLPrefix();
}
/** @var Pages $pages */
$pages = $grav['pages'];
$base = $grav['base_url_relative'];
return rtrim($base . $grav['pages']->base(), '/') . $language_append;
return $pages->baseUrl(null, false);
}
/**
@@ -633,10 +631,9 @@ class Uri
}
return $ip;
}
/**
/**
* Returns current Uri.
*
* @return \Grav\Framework\Uri\Uri
@@ -883,7 +880,26 @@ class Uri
public static function parseUrl($url)
{
$grav = Grav::instance();
$parts = parse_url($url);
$encodedUrl = preg_replace_callback(
'%[^:/@?&=#]+%usD',
function ($matches) { return rawurlencode($matches[0]); },
$url
);
$parts = parse_url($encodedUrl);
if (false === $parts) {
return false;
}
foreach($parts as $name => $value) {
$parts[$name] = rawurldecode($value);
}
if (!isset($parts['path'])) {
$parts['path'] = '';
}
list($stripped_path, $params) = static::extractParams($parts['path'], $grav['config']->get('system.param_sep'));
@@ -1262,7 +1278,7 @@ class Uri
{
if (!$this->post) {
$content_type = $this->getContentType();
if ($content_type == 'application/json') {
if ($content_type === 'application/json') {
$json = file_get_contents('php://input');
$this->post = json_decode($json, true);
} elseif (!empty($_POST)) {
@@ -1270,7 +1286,7 @@ class Uri
}
}
if ($this->post && !is_null($element)) {
if ($this->post && null !== $element) {
$item = Utils::getDotNotation($this->post, $element);
if ($filter_type) {
$item = filter_var($item, $filter_type);
+28 -62
View File
@@ -45,8 +45,20 @@ abstract class Utils
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
// Get relative path to the resource (or false if not found).
$resource = $locator->findResource($input, false);
$parts = Uri::parseUrl($input);
if ($parts) {
$resource = $locator->findResource("{$parts['scheme']}://{$parts['host']}{$parts['path']}", false);
if (isset($parts['query'])) {
$resource = $resource . '?' . $parts['query'];
}
} else {
// Not a valid URL (can still be a stream).
$resource = $locator->findResource($input, false);
}
} else {
$resource = $input;
}
@@ -262,7 +274,7 @@ abstract class Utils
// is $break present between $limit and the end of the string?
if ($up_to_break && false !== ($breakpoint = mb_strpos($string, $break, $limit))) {
if ($breakpoint < mb_strlen($string) - 1) {
$string = mb_substr($string, 0, $breakpoint) . $break;
$string = mb_substr($string, 0, $breakpoint) . $pad;
}
} else {
$string = mb_substr($string, 0, $limit) . $pad;
@@ -705,11 +717,11 @@ abstract class Utils
* with reverse proxy setups.
*
* @param string $action
* @param bool $plusOneTick if true, generates the token for the next tick (the next 12 hours)
* @param bool $previousTick if true, generates the token for the previous tick (the previous 12 hours)
*
* @return string the nonce string
*/
private static function generateNonceString($action, $plusOneTick = false)
private static function generateNonceString($action, $previousTick = false)
{
$username = '';
if (isset(Grav::instance()['user'])) {
@@ -720,29 +732,8 @@ abstract class Utils
$token = session_id();
$i = self::nonceTick();
if ($plusOneTick) {
$i++;
}
return ($i . '|' . $action . '|' . $username . '|' . $token . '|' . Grav::instance()['config']->get('security.salt'));
}
//Added in version 1.0.8 to ensure that existing nonces are not broken.
private static function generateNonceStringOldStyle($action, $plusOneTick = false)
{
if (isset(Grav::instance()['user'])) {
$user = Grav::instance()['user'];
$username = $user->username;
if (isset($_SERVER['REMOTE_ADDR'])) {
$username .= $_SERVER['REMOTE_ADDR'];
}
} else {
$username = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '';
}
$token = session_id();
$i = self::nonceTick();
if ($plusOneTick) {
$i++;
if ($previousTick) {
$i--;
}
return ($i . '|' . $action . '|' . $username . '|' . $token . '|' . Grav::instance()['config']->get('security.salt'));
@@ -768,33 +759,20 @@ abstract class Utils
* action is the same for 12 hours.
*
* @param string $action the action the nonce is tied to (e.g. save-user-admin or move-page-homepage)
* @param bool $plusOneTick if true, generates the token for the next tick (the next 12 hours)
* @param bool $previousTick if true, generates the token for the previous tick (the previous 12 hours)
*
* @return string the nonce
*/
public static function getNonce($action, $plusOneTick = false)
public static function getNonce($action, $previousTick = false)
{
// Don't regenerate this again if not needed
if (isset(static::$nonces[$action])) {
return static::$nonces[$action];
if (isset(static::$nonces[$action][$previousTick])) {
return static::$nonces[$action][$previousTick];
}
$nonce = md5(self::generateNonceString($action, $plusOneTick));
static::$nonces[$action] = $nonce;
$nonce = md5(self::generateNonceString($action, $previousTick));
static::$nonces[$action][$previousTick] = $nonce;
return static::$nonces[$action];
}
//Added in version 1.0.8 to ensure that existing nonces are not broken.
public static function getNonceOldStyle($action, $plusOneTick = false)
{
// Don't regenerate this again if not needed
if (isset(static::$nonces[$action])) {
return static::$nonces[$action];
}
$nonce = md5(self::generateNonceStringOldStyle($action, $plusOneTick));
static::$nonces[$action] = $nonce;
return static::$nonces[$action];
return static::$nonces[$action][$previousTick];
}
/**
@@ -818,20 +796,8 @@ abstract class Utils
}
//Nonce generated 12-24 hours ago
$plusOneTick = true;
if ($nonce === self::getNonce($action, $plusOneTick)) {
return true;
}
//Added in version 1.0.8 to ensure that existing nonces are not broken.
//Nonce generated 0-12 hours ago
if ($nonce === self::getNonceOldStyle($action)) {
return true;
}
//Nonce generated 12-24 hours ago
$plusOneTick = true;
if ($nonce === self::getNonceOldStyle($action, $plusOneTick)) {
$previousTick = true;
if ($nonce === self::getNonce($action, $previousTick)) {
return true;
}
+25 -13
View File
@@ -9,9 +9,9 @@
namespace Grav\Console\Cli;
use Grav\Console\ConsoleCommand;
use RocketTheme\Toolbox\File\YamlFile;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Yaml\Yaml;
class InstallCommand extends ConsoleCommand
{
@@ -71,9 +71,9 @@ class InstallCommand extends ConsoleCommand
// Look for dependencies file in ROOT and USER dir
if (file_exists($this->user_path . $dependencies_file)) {
$this->config = Yaml::parse(file_get_contents($this->user_path . $dependencies_file));
$file = YamlFile::instance($this->user_path . $dependencies_file);
} elseif (file_exists($this->destination . $dependencies_file)) {
$this->config = Yaml::parse(file_get_contents($this->destination . $dependencies_file));
$file = YamlFile::instance($this->destination . $dependencies_file);
} else {
$this->output->writeln('<red>ERROR</red> Missing .dependencies file in <cyan>user/</cyan> folder');
if ($this->input->getArgument('destination')) {
@@ -85,6 +85,9 @@ class InstallCommand extends ConsoleCommand
return;
}
$this->config = $file->content();
$file->free();
// If yaml config, process
if ($this->config) {
if (!$this->input->getOption('symlink')) {
@@ -153,23 +156,32 @@ class InstallCommand extends ConsoleCommand
exec('cd ' . $this->destination);
foreach ($this->config['links'] as $repo => $data) {
$from = $this->local_config[$data['scm'] . '_repos'] . $data['src'];
$repos = (array) $this->local_config[$data['scm'] . '_repos'];
$from = false;
$to = $this->destination . $data['path'];
if (file_exists($from)) {
if (!file_exists($to)) {
symlink($from, $to);
$this->output->writeln('<green>SUCCESS</green> symlinked <magenta>' . $data['src'] . '</magenta> -> <cyan>' . $data['path'] . '</cyan>');
$this->output->writeln('');
} else {
$this->output->writeln('<red>destination: ' . $to . ' already exists, skipping...</red>');
$this->output->writeln('');
foreach ($repos as $repo) {
$path = $repo . $data['src'];
if (file_exists($path)) {
$from = $path;
continue;
}
} else {
}
if (!$from) {
$this->output->writeln('<red>source: ' . $from . ' does not exists, skipping...</red>');
$this->output->writeln('');
}
if (!file_exists($to)) {
symlink($from, $to);
$this->output->writeln('<green>SUCCESS</green> symlinked <magenta>' . $data['src'] . '</magenta> -> <cyan>' . $data['path'] . '</cyan>');
$this->output->writeln('');
} else {
$this->output->writeln('<red>destination: ' . $to . ' already exists, skipping...</red>');
$this->output->writeln('');
}
}
}
}
+4 -2
View File
@@ -12,11 +12,11 @@ use Grav\Common\Grav;
use Grav\Common\Composer;
use Grav\Common\GravTrait;
use Grav\Console\Cli\ClearCacheCommand;
use RocketTheme\Toolbox\File\YamlFile;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Yaml\Yaml;
trait ConsoleTrait
{
@@ -123,7 +123,9 @@ trait ConsoleTrait
$local_config_file = $home_folder . '/.grav/config';
if (file_exists($local_config_file)) {
$this->local_config = Yaml::parse(file_get_contents($local_config_file));
$file = YamlFile::instance($local_config_file);
$this->local_config = $file->content();
$file->free();
return $local_config_file;
}
@@ -444,18 +444,21 @@ class InstallCommand extends ConsoleCommand
{
$matches = $this->getGitRegexMatches($package);
foreach ($this->local_config as $path) {
foreach ($this->local_config as $paths) {
if (Utils::endsWith($matches[2], '.git')) {
$repo_dir = preg_replace('/\.git$/', '', $matches[2]);
} else {
$repo_dir = $matches[2];
}
$from = rtrim($path, '/') . '/' . $repo_dir;
if (file_exists($from)) {
return $from;
$paths = (array) $paths;
foreach ($paths as $repo) {
$path = rtrim($repo, '/') . '/' . $repo_dir;
if (file_exists($path)) {
return $path;
}
}
}
return false;
@@ -11,9 +11,9 @@ namespace Grav\Console\Gpm;
use Grav\Common\GPM\GPM;
use Grav\Common\GPM\Upgrader;
use Grav\Console\ConsoleCommand;
use RocketTheme\Toolbox\File\YamlFile;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Yaml\Yaml;
class VersionCommand extends ConsoleCommand
{
@@ -84,7 +84,10 @@ class VersionCommand extends ConsoleCommand
}
}
$package_yaml = Yaml::parse(file_get_contents($blueprints_path));
$file = YamlFile::instance($blueprints_path);
$package_yaml = $file->content();
$file->free();
$version = $package_yaml['version'];
if (!$version) {
+18 -9
View File
@@ -16,21 +16,18 @@ use Grav\Framework\Cache\Exception\InvalidArgumentException;
*/
trait CacheTrait
{
/**
* @var string
*/
/** @var string */
private $namespace = '';
/**
* @var int|null
*/
/** @var int|null */
private $defaultLifetime = null;
/**
* @var \stdClass
*/
/** @var \stdClass */
private $miss;
/** @var bool */
private $validation = true;
/**
* Always call from constructor.
*
@@ -45,6 +42,14 @@ trait CacheTrait
$this->miss = new \stdClass;
}
/**
* @param $validation
*/
public function setValidation($validation)
{
$this->validation = (bool) $validation;
}
/**
* @return string
*/
@@ -307,6 +312,10 @@ trait CacheTrait
*/
protected function validateKeys($keys)
{
if (!$this->validation) {
return;
}
foreach ($keys as $key) {
$this->validateKey($key);
}
@@ -24,11 +24,6 @@ class ArrayCollection extends BaseArrayCollection implements CollectionInterface
*/
public function reverse()
{
// TODO: remove when PHP 5.6 is minimum (with doctrine/collections v1.4).
if (!method_exists($this, 'createFrom')) {
return new static(array_reverse($this->toArray()));
}
return $this->createFrom(array_reverse($this->toArray()));
}
@@ -42,11 +37,6 @@ class ArrayCollection extends BaseArrayCollection implements CollectionInterface
$keys = $this->getKeys();
shuffle($keys);
// TODO: remove when PHP 5.6 is minimum (with doctrine/collections v1.4).
if (!method_exists($this, 'createFrom')) {
return new static(array_replace(array_flip($keys), $this->toArray()));
}
return $this->createFrom(array_replace(array_flip($keys), $this->toArray()));
}
@@ -27,6 +27,7 @@ class ContentBlock implements ContentBlockInterface
protected $tokenTemplate = '@@BLOCK-%s@@';
protected $content = '';
protected $blocks = [];
protected $checksum;
/**
* @param string $id
@@ -40,6 +41,7 @@ class ContentBlock implements ContentBlockInterface
/**
* @param array $serialized
* @return ContentBlockInterface
* @throws \InvalidArgumentException
*/
public static function fromArray(array $serialized)
{
@@ -48,14 +50,14 @@ class ContentBlock implements ContentBlockInterface
$id = isset($serialized['id']) ? $serialized['id'] : null;
if (!$type || !$id || !is_a($type, 'Grav\Framework\ContentBlock\ContentBlockInterface', true)) {
throw new \RuntimeException('Bad data');
throw new \InvalidArgumentException('Bad data');
}
/** @var ContentBlockInterface $instance */
$instance = new $type($id);
$instance->build($serialized);
} catch (\Exception $e) {
throw new \RuntimeException(sprintf('Cannot unserialize Block: %s', $e->getMessage()), $e->getCode(), $e);
throw new \InvalidArgumentException(sprintf('Cannot unserialize Block: %s', $e->getMessage()), $e->getCode(), $e);
}
return $instance;
@@ -104,9 +106,13 @@ class ContentBlock implements ContentBlockInterface
$array = [
'_type' => get_class($this),
'_version' => $this->version,
'id' => $this->id,
'id' => $this->id
];
if ($this->checksum) {
$array['checksum'] = $this->checksum;
}
if ($this->content) {
$array['content'] = $this->content;
}
@@ -158,6 +164,7 @@ class ContentBlock implements ContentBlockInterface
$this->checkVersion($serialized);
$this->id = isset($serialized['id']) ? $serialized['id'] : $this->generateId();
$this->checksum = isset($serialized['checksum']) ? $serialized['checksum'] : null;
if (isset($serialized['content'])) {
$this->setContent($serialized['content']);
@@ -169,6 +176,25 @@ class ContentBlock implements ContentBlockInterface
}
}
/**
* @param string $checksum
* @return $this
*/
public function setChecksum($checksum)
{
$this->checksum = $checksum;
return $this;
}
/**
* @return string
*/
public function getChecksum()
{
return $this->checksum;
}
/**
* @param string $content
* @return $this
@@ -222,7 +248,7 @@ class ContentBlock implements ContentBlockInterface
*/
protected function checkVersion(array $serialized)
{
$version = isset($serialized['_version']) ? (string) $serialized['_version'] : '1';
$version = isset($serialized['_version']) ? (int) $serialized['_version'] : 1;
if ($version !== $this->version) {
throw new \RuntimeException(sprintf('Unsupported version %s', $version));
}
@@ -61,6 +61,17 @@ interface ContentBlockInterface extends \Serializable
*/
public function build(array $serialized);
/**
* @param string $checksum
* @return $this
*/
public function setChecksum($checksum);
/**
* @return string
*/
public function getChecksum();
/**
* @param string $content
* @return $this
@@ -15,6 +15,7 @@ namespace Grav\Framework\ContentBlock;
*/
class HtmlBlock extends ContentBlock implements HtmlBlockInterface
{
protected $version = 1;
protected $frameworks = [];
protected $styles = [];
protected $scripts = [];
@@ -0,0 +1,44 @@
<?php
/**
* @package Grav\Framework\File\Formatter
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Framework\File\Formatter;
interface FormatterInterface
{
/**
* Get default file extension from current formatter (with dot).
*
* Default file extension is the first defined extension.
*
* @return string File extension (can be empty).
*/
public function getDefaultFileExtension();
/**
* Get file extensions supported by current formatter (with dot).
*
* @return string[]
*/
public function getSupportedFileExtensions();
/**
* Encode data into a string.
*
* @param array $data
* @return string
*/
public function encode($data);
/**
* Decode a string into data.
*
* @param string $data
* @return array
*/
public function decode($data);
}
@@ -0,0 +1,83 @@
<?php
/**
* @package Grav\Framework\File\Formatter
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Framework\File\Formatter;
class IniFormatter implements FormatterInterface
{
/** @var array */
private $config;
/**
* IniFormatter constructor.
* @param array $config
*/
public function __construct(array $config = [])
{
$this->config = $config + [
'file_extension' => '.ini'
];
}
/**
* @deprecated 1.5 Use $formatter->getDefaultFileExtension() instead.
*/
public function getFileExtension()
{
return $this->getDefaultFileExtension();
}
/**
* {@inheritdoc}
*/
public function getDefaultFileExtension()
{
$extensions = $this->getSupportedFileExtensions();
return (string) reset($extensions);
}
/**
* {@inheritdoc}
*/
public function getSupportedFileExtensions()
{
return (array) $this->config['file_extension'];
}
/**
* {@inheritdoc}
*/
public function encode($data)
{
$string = '';
foreach ($data as $key => $value) {
$string .= $key . '="' . preg_replace(
['/"/', '/\\\/', "/\t/", "/\n/", "/\r/"],
['\"', '\\\\', '\t', '\n', '\r'],
$value
) . "\"\n";
}
return $string;
}
/**
* {@inheritdoc}
*/
public function decode($data)
{
$decoded = @parse_ini_string($data);
if ($decoded === false) {
throw new \RuntimeException('Decoding INI failed');
}
return $decoded;
}
}
@@ -0,0 +1,78 @@
<?php
/**
* @package Grav\Framework\File\Formatter
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Framework\File\Formatter;
class JsonFormatter implements FormatterInterface
{
/** @var array */
private $config;
public function __construct(array $config = [])
{
$this->config = $config + [
'file_extension' => '.json',
'encode_options' => 0,
'decode_assoc' => true
];
}
/**
* @deprecated 1.5 Use $formatter->getDefaultFileExtension() instead.
*/
public function getFileExtension()
{
return $this->getDefaultFileExtension();
}
/**
* {@inheritdoc}
*/
public function getDefaultFileExtension()
{
$extensions = $this->getSupportedFileExtensions();
return (string) reset($extensions);
}
/**
* {@inheritdoc}
*/
public function getSupportedFileExtensions()
{
return (array) $this->config['file_extension'];
}
/**
* {@inheritdoc}
*/
public function encode($data)
{
$encoded = @json_encode($data, $this->config['encode_options']);
if ($encoded === false) {
throw new \RuntimeException('Encoding JSON failed');
}
return $encoded;
}
/**
* {@inheritdoc}
*/
public function decode($data)
{
$decoded = @json_decode($data, $this->config['decode_assoc']);
if ($decoded === false) {
throw new \RuntimeException('Decoding JSON failed');
}
return $decoded;
}
}
@@ -0,0 +1,116 @@
<?php
/**
* @package Grav\Framework\File\Formatter
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Framework\File\Formatter;
class MarkdownFormatter implements FormatterInterface
{
/** @var array */
private $config;
/** @var FormatterInterface */
private $headerFormatter;
public function __construct(array $config = [], FormatterInterface $headerFormatter = null)
{
$this->config = $config + [
'file_extension' => '.md',
'header' => 'header',
'body' => 'markdown',
'raw' => 'frontmatter',
'yaml' => ['inline' => 20]
];
$this->headerFormatter = $headerFormatter ?: new YamlFormatter($this->config['yaml']);
}
/**
* @deprecated 1.5 Use $formatter->getDefaultFileExtension() instead.
*/
public function getFileExtension()
{
return $this->getDefaultFileExtension();
}
/**
* {@inheritdoc}
*/
public function getDefaultFileExtension()
{
$extensions = $this->getSupportedFileExtensions();
return (string) reset($extensions);
}
/**
* {@inheritdoc}
*/
public function getSupportedFileExtensions()
{
return (array) $this->config['file_extension'];
}
/**
* {@inheritdoc}
*/
public function encode($data)
{
$headerVar = $this->config['header'];
$bodyVar = $this->config['body'];
$header = isset($data[$headerVar]) ? (array) $data[$headerVar] : [];
$body = isset($data[$bodyVar]) ? (string) $data[$bodyVar] : '';
// Create Markdown file with YAML header.
$encoded = '';
if ($header) {
$encoded = "---\n" . trim($this->headerFormatter->encode($data['header'])) . "\n---\n\n";
}
$encoded .= $body;
// Normalize line endings to Unix style.
$encoded = preg_replace("/(\r\n|\r)/", "\n", $encoded);
return $encoded;
}
/**
* {@inheritdoc}
*/
public function decode($data)
{
$headerVar = $this->config['header'];
$bodyVar = $this->config['body'];
$rawVar = $this->config['raw'];
$content = [
$headerVar => [],
$bodyVar => ''
];
$headerRegex = "/^---\n(.+?)\n---\n{0,}(.*)$/uis";
// Normalize line endings to Unix style.
$data = preg_replace("/(\r\n|\r)/", "\n", $data);
// Parse header.
preg_match($headerRegex, ltrim($data), $matches);
if(empty($matches)) {
$content[$bodyVar] = $data;
} else {
// Normalize frontmatter.
$frontmatter = preg_replace("/\n\t/", "\n ", $matches[1]);
if ($rawVar) {
$content[$rawVar] = $frontmatter;
}
$content[$headerVar] = $this->headerFormatter->decode($frontmatter);
$content[$bodyVar] = $matches[2];
}
return $content;
}
}
@@ -0,0 +1,96 @@
<?php
/**
* @package Grav\Framework\File\Formatter
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Framework\File\Formatter;
class SerializeFormatter implements FormatterInterface
{
/** @var array */
private $config;
/**
* IniFormatter constructor.
* @param array $config
*/
public function __construct(array $config = [])
{
$this->config = $config + [
'file_extension' => '.ser'
];
}
/**
* @deprecated 1.5 Use $formatter->getDefaultFileExtension() instead.
*/
public function getFileExtension()
{
return $this->getDefaultFileExtension();
}
/**
* {@inheritdoc}
*/
public function getDefaultFileExtension()
{
$extensions = $this->getSupportedFileExtensions();
return (string) reset($extensions);
}
/**
* {@inheritdoc}
*/
public function getSupportedFileExtensions()
{
return (array) $this->config['file_extension'];
}
/**
* {@inheritdoc}
*/
public function encode($data)
{
return serialize($this->preserveLines($data, ["\n", "\r"], ['\\n', '\\r']));
}
/**
* {@inheritdoc}
*/
public function decode($data)
{
$decoded = @unserialize($data);
if ($decoded === false) {
throw new \RuntimeException('Decoding serialized data failed');
}
return $this->preserveLines($decoded, ['\\n', '\\r'], ["\n", "\r"]);
}
/**
* Preserve new lines, recursive function.
*
* @param mixed $data
* @param array $search
* @param array $replace
* @return mixed
*/
protected function preserveLines($data, $search, $replace)
{
if (is_string($data)) {
$data = str_replace($search, $replace, $data);
} elseif (is_array($data)) {
foreach ($data as &$value) {
$value = $this->preserveLines($value, $search, $replace);
}
unset($value);
}
return $data;
}
}
@@ -0,0 +1,103 @@
<?php
/**
* @package Grav\Framework\File\Formatter
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Framework\File\Formatter;
use Symfony\Component\Yaml\Exception\DumpException;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Yaml as YamlParser;
use RocketTheme\Toolbox\Compat\Yaml\Yaml as FallbackYamlParser;
class YamlFormatter implements FormatterInterface
{
/** @var array */
private $config;
public function __construct(array $config = [])
{
$this->config = $config + [
'file_extension' => '.yaml',
'inline' => 5,
'indent' => 2,
'native' => true,
'compat' => true
];
}
/**
* @deprecated 1.5 Use $formatter->getDefaultFileExtension() instead.
*/
public function getFileExtension()
{
return $this->getDefaultFileExtension();
}
/**
* {@inheritdoc}
*/
public function getDefaultFileExtension()
{
$extensions = $this->getSupportedFileExtensions();
return (string) reset($extensions);
}
/**
* {@inheritdoc}
*/
public function getSupportedFileExtensions()
{
return (array) $this->config['file_extension'];
}
/**
* {@inheritdoc}
*/
public function encode($data)
{
try {
return (string) YamlParser::dump(
$data,
$this->config['inline'],
$this->config['indent'],
YamlParser::DUMP_EXCEPTION_ON_INVALID_TYPE
);
} catch (DumpException $e) {
throw new \RuntimeException('Encoding YAML failed: ' . $e->getMessage(), 0, $e);
}
}
/**
* {@inheritdoc}
*/
public function decode($data)
{
// Try native PECL YAML PHP extension first if available.
if ($this->config['native'] && function_exists('yaml_parse')) {
// Safely decode YAML.
$saved = @ini_get('yaml.decode_php');
@ini_set('yaml.decode_php', 0);
$decoded = @yaml_parse($data);
@ini_set('yaml.decode_php', $saved);
if ($decoded !== false) {
return (array) $decoded;
}
}
try {
return (array) YamlParser::parse($data);
} catch (ParseException $e) {
if ($this->config['compat']) {
return (array) FallbackYamlParser::parse($data);
}
throw new \RuntimeException('Decoding YAML failed: ' . $e->getMessage(), 0, $e);
}
}
}
@@ -32,11 +32,6 @@ trait ObjectCollectionTrait
$list[$key] = is_object($value) ? clone $value : $value;
}
// TODO: remove when PHP 5.6 is minimum (with doctrine/collections v1.4).
if (!method_exists($this, 'createFrom')) {
return new static($list);
}
return $this->createFrom($list);
}
@@ -170,12 +165,7 @@ trait ObjectCollectionTrait
{
$collections = [];
foreach ($this->group($property) as $id => $elements) {
// TODO: remove when PHP 5.6 is minimum (with doctrine/collections v1.4).
if (!method_exists($this, 'createFrom')) {
$collection = new static($elements);
} else {
$collection = $this->createFrom($elements);
}
$collection = $this->createFrom($elements);
$collections[$id] = $collection;
}
@@ -15,7 +15,7 @@ namespace Grav\Framework\Object\Base;
*/
trait ObjectTrait
{
static protected $prefix;
/** @var string */
static protected $type;
/**
@@ -23,18 +23,28 @@ trait ObjectTrait
*/
private $_key;
/**
* @return string
*/
protected function getTypePrefix()
{
return '';
}
/**
* @param bool $prefix
* @return string
*/
public function getType($prefix = true)
{
$type = $prefix ? $this->getTypePrefix() : '';
if (static::$type) {
return ($prefix ? static::$prefix : '') . static::$type;
return $type . static::$type;
}
$class = get_class($this);
return ($prefix ? static::$prefix : '') . strtolower(substr($class, strrpos($class, '\\') + 1));
return $type . strtolower(substr($class, strrpos($class, '\\') + 1));
}
/**
@@ -108,7 +118,7 @@ trait ObjectTrait
*/
public function serialize()
{
return serialize($this->jsonSerialize());
return serialize($this->doSerialize());
}
/**
@@ -124,6 +134,14 @@ trait ObjectTrait
$this->doUnserialize($data);
}
/**
* @return array
*/
protected function doSerialize()
{
return $this->jsonSerialize();
}
/**
* @param array $serialized
*/
@@ -159,10 +177,13 @@ trait ObjectTrait
/**
* @param string $key
* @return $this
*/
protected function setKey($key)
{
$this->_key = (string) $key;
return $this;
}
abstract protected function doHasProperty($property);
@@ -0,0 +1,198 @@
<?php
/**
* @package Grav\Framework\Object
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Framework\Object\Collection;
use Doctrine\Common\Collections\Expr\ClosureExpressionVisitor;
use Doctrine\Common\Collections\Expr\Comparison;
class ObjectExpressionVisitor extends ClosureExpressionVisitor
{
/**
* Accesses the field of a given object.
*
* @param object $object
* @param string $field
*
* @return mixed
*/
public static function getObjectFieldValue($object, $field)
{
$op = $value = null;
$pos = strpos($field, '(');
if (false !== $pos) {
list ($op, $field) = explode('(', $field, 2);
$field = rtrim($field, ')');
}
if (isset($object[$field])) {
$value = $object[$field];
} else {
$accessors = array('', 'get', 'is');
foreach ($accessors as $accessor) {
$accessor .= $field;
if (!method_exists($object, $accessor)) {
continue;
}
$value = $object->{$accessor}();
break;
}
}
if ($op) {
$function = 'filter' . ucfirst(strtolower($op));
if (method_exists(static::class, $function)) {
$value = static::$function($value);
}
}
return $value;
}
public static function filterLower($str)
{
return mb_strtolower($str);
}
public static function filterUpper($str)
{
return mb_strtoupper($str);
}
public static function filterLength($str)
{
return mb_strlen($str);
}
public static function filterLtrim($str)
{
return ltrim($str);
}
public static function filterRtrim($str)
{
return rtrim($str);
}
public static function filterTrim($str)
{
return trim($str);
}
/**
* Helper for sorting arrays of objects based on multiple fields + orientations.
*
* @param string $name
* @param int $orientation
* @param \Closure $next
*
* @return \Closure
*/
public static function sortByField($name, $orientation = 1, \Closure $next = null)
{
if (!$next) {
$next = function() {
return 0;
};
}
return function ($a, $b) use ($name, $next, $orientation) {
$aValue = static::getObjectFieldValue($a, $name);
$bValue = static::getObjectFieldValue($b, $name);
if ($aValue === $bValue) {
return $next($a, $b);
}
return (($aValue > $bValue) ? 1 : -1) * $orientation;
};
}
/**
* {@inheritDoc}
*/
public function walkComparison(Comparison $comparison)
{
$field = $comparison->getField();
$value = $comparison->getValue()->getValue(); // shortcut for walkValue()
switch ($comparison->getOperator()) {
case Comparison::EQ:
return function ($object) use ($field, $value) {
return static::getObjectFieldValue($object, $field) === $value;
};
case Comparison::NEQ:
return function ($object) use ($field, $value) {
return static::getObjectFieldValue($object, $field) !== $value;
};
case Comparison::LT:
return function ($object) use ($field, $value) {
return static::getObjectFieldValue($object, $field) < $value;
};
case Comparison::LTE:
return function ($object) use ($field, $value) {
return static::getObjectFieldValue($object, $field) <= $value;
};
case Comparison::GT:
return function ($object) use ($field, $value) {
return static::getObjectFieldValue($object, $field) > $value;
};
case Comparison::GTE:
return function ($object) use ($field, $value) {
return static::getObjectFieldValue($object, $field) >= $value;
};
case Comparison::IN:
return function ($object) use ($field, $value) {
return \in_array(static::getObjectFieldValue($object, $field), $value, true);
};
case Comparison::NIN:
return function ($object) use ($field, $value) {
return !\in_array(static::getObjectFieldValue($object, $field), $value, true);
};
case Comparison::CONTAINS:
return function ($object) use ($field, $value) {
return false !== strpos(static::getObjectFieldValue($object, $field), $value);
};
case Comparison::MEMBER_OF:
return function ($object) use ($field, $value) {
$fieldValues = static::getObjectFieldValue($object, $field);
if (!is_array($fieldValues)) {
$fieldValues = iterator_to_array($fieldValues);
}
return \in_array($value, $fieldValues, true);
};
case Comparison::STARTS_WITH:
return function ($object) use ($field, $value) {
return 0 === strpos(static::getObjectFieldValue($object, $field), $value);
};
case Comparison::ENDS_WITH:
return function ($object) use ($field, $value) {
return $value === substr(static::getObjectFieldValue($object, $field), -strlen($value));
};
default:
throw new \RuntimeException("Unknown comparison operator: " . $comparison->getOperator());
}
}
}
@@ -8,13 +8,14 @@
namespace Grav\Framework\Object\Interfaces;
use Doctrine\Common\Collections\Selectable;
use Grav\Framework\Collection\CollectionInterface;
/**
* ObjectCollection Interface
* @package Grav\Framework\Collection
*/
interface ObjectCollectionInterface extends CollectionInterface, ObjectInterface
interface ObjectCollectionInterface extends CollectionInterface, Selectable, ObjectInterface
{
/**
* Create a copy from this collection by cloning all objects in the collection.
@@ -8,9 +8,11 @@
namespace Grav\Framework\Object;
use Doctrine\Common\Collections\Criteria;
use Grav\Framework\Collection\ArrayCollection;
use Grav\Framework\Object\Access\NestedPropertyCollectionTrait;
use Grav\Framework\Object\Base\ObjectCollectionTrait;
use Grav\Framework\Object\Collection\ObjectExpressionVisitor;
use Grav\Framework\Object\Interfaces\NestedObjectInterface;
use Grav\Framework\Object\Interfaces\ObjectCollectionInterface;
@@ -36,6 +38,39 @@ class ObjectCollection extends ArrayCollection implements ObjectCollectionInterf
$this->setKey($key);
}
/**
* {@inheritDoc}
*/
public function matching(Criteria $criteria)
{
$expr = $criteria->getWhereExpression();
$filtered = $this->getElements();
if ($expr) {
$visitor = new ObjectExpressionVisitor();
$filter = $visitor->dispatch($expr);
$filtered = array_filter($filtered, $filter);
}
if ($orderings = $criteria->getOrderings()) {
$next = null;
foreach (array_reverse($orderings) as $field => $ordering) {
$next = ObjectExpressionVisitor::sortByField($field, $ordering == Criteria::DESC ? -1 : 1, $next);
}
uasort($filtered, $next);
}
$offset = $criteria->getFirstResult();
$length = $criteria->getMaxResults();
if ($offset || $length) {
$filtered = array_slice($filtered, (int)$offset, $length);
}
return $this->createFrom($filtered);
}
protected function getElements()
{
return $this->toArray();
@@ -95,10 +95,10 @@ trait ObjectPropertyTrait
}
/**
* @param string $property Object property to be fetched.
* @param mixed $default Default value if property has not been set.
* @param bool $doCreate Set true to create variable.
* @return mixed Property value.
* @param string $property Object property to be fetched.
* @param mixed $default Default value if property has not been set.
* @param callable|bool $doCreate Set true to create variable.
* @return mixed Property value.
*/
protected function &doGetProperty($property, $default = null, $doCreate = false)
{
+4 -5
View File
@@ -178,7 +178,7 @@ class Route
*/
public function withGravParam($param, $value)
{
return $this->withParam('gravParams', $param, $value);
return $this->withParam('gravParams', $param, null !== $value ? (string)$value : null);
}
/**
@@ -222,17 +222,16 @@ class Route
protected function withParam($type, $param, $value)
{
$oldValue = isset($this->{$type}[$param]) ? $this->{$type}[$param] : null;
$newValue = null !== $value ? (string)$value : null;
if ($oldValue === $newValue) {
if ($oldValue === $value) {
return $this;
}
$new = clone $this;
if ($newValue === null) {
if ($value === null) {
unset($new->{$type}[$param]);
} else {
$new->{$type}[$param] = $newValue;
$new->{$type}[$param] = $value;
}
return $new;
@@ -28,6 +28,23 @@ class RouteFactory
return new Route($parts);
}
public static function createFromString($path)
{
$path = ltrim($path, '/');
$parts = [
'path' => $path,
'query' => '',
'query_params' => [],
'grav' => [
'root' => self::$root,
'language' => self::$language,
'route' => $path,
'params' => ''
],
];
return new Route($parts);
}
public static function getRoot()
{
return self::$root;
@@ -0,0 +1,340 @@
<?php
/**
* @package Grav\Framework\Session
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Framework\Session;
/**
* Class Session
* @package Grav\Framework\Session
*/
class Session implements SessionInterface
{
/**
* @var bool
*/
protected $started = false;
/**
* @var Session
*/
protected static $instance;
/**
* @inheritdoc
*/
public static function getInstance()
{
if (null === self::$instance) {
throw new \RuntimeException("Session hasn't been initialized.", 500);
}
return self::$instance;
}
/**
* @inheritdoc
*/
public function __construct(array $options = [])
{
// Session is a singleton.
if (\PHP_SAPI === 'cli') {
self::$instance = $this;
return;
}
if (null !== self::$instance) {
throw new \RuntimeException('Session has already been initialized.', 500);
}
// Destroy any existing sessions started with session.auto_start
if ($this->isSessionStarted()) {
session_unset();
session_destroy();
}
// Set default options.
$options += array(
'cache_limiter' => 'nocache',
'use_trans_sid' => 0,
'use_cookies' => 1,
'lazy_write' => 1,
'use_strict_mode' => 1
);
$this->setOptions($options);
session_register_shutdown();
self::$instance = $this;
}
/**
* @inheritdoc
*/
public function getId()
{
return session_id();
}
/**
* @inheritdoc
*/
public function setId($id)
{
session_id($id);
return $this;
}
/**
* @inheritdoc
*/
public function getName()
{
return session_name();
}
/**
* @inheritdoc
*/
public function setName($name)
{
session_name($name);
return $this;
}
/**
* @inheritdoc
*/
public function setOptions(array $options)
{
if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) {
return;
}
$allowedOptions = [
'save_path' => true,
'name' => true,
'save_handler' => true,
'gc_probability' => true,
'gc_divisor' => true,
'gc_maxlifetime' => true,
'serialize_handler' => true,
'cookie_lifetime' => true,
'cookie_path' => true,
'cookie_domain' => true,
'cookie_secure' => true,
'cookie_httponly' => true,
'use_strict_mode' => true,
'use_cookies' => true,
'use_only_cookies' => true,
'referer_check' => true,
'cache_limiter' => true,
'cache_expire' => true,
'use_trans_sid' => true,
'trans_sid_tags' => true, // PHP 7.1
'trans_sid_hosts' => true, // PHP 7.1
'sid_length' => true, // PHP 7.1
'sid_bits_per_character' => true, // PHP 7.1
'upload_progress.enabled' => true,
'upload_progress.cleanup' => true,
'upload_progress.prefix' => true,
'upload_progress.name' => true,
'upload_progress.freq' => true,
'upload_progress.min-freq' => true,
'lazy_write' => true,
'url_rewriter.tags' => true, // Not used in PHP 7.1
'hash_function' => true, // Not used in PHP 7.1
'hash_bits_per_character' => true, // Not used in PHP 7.1
'entropy_file' => true, // Not used in PHP 7.1
'entropy_length' => true, // Not used in PHP 7.1
];
foreach ($options as $key => $value) {
if (is_array($value)) {
// Allow nested options.
foreach ($value as $key2 => $value2) {
$ckey = "{$key}.{$key2}";
if (isset($value2, $allowedOptions[$ckey])) {
$this->ini_set("session.{$ckey}", $value2);
}
}
} elseif (isset($value, $allowedOptions[$key])) {
$this->ini_set("session.{$key}", $value);
}
}
}
/**
* @inheritdoc
*/
public function start($readonly = false)
{
// Protection against invalid session cookie names throwing exception: http://php.net/manual/en/function.session-id.php#116836
if (isset($_COOKIE[session_name()]) && !preg_match('/^[-,a-zA-Z0-9]{1,128}$/', $_COOKIE[session_name()])) {
unset($_COOKIE[session_name()]);
}
$options = $readonly ? ['read_and_close' => '1'] : [];
$success = @session_start($options);
if (!$success) {
$last = error_get_last();
$error = $last ? $last['message'] : 'Unknown error';
throw new \RuntimeException('Failed to start session: ' . $error, 500);
}
$params = session_get_cookie_params();
setcookie(
session_name(),
session_id(),
time() + $params['lifetime'],
$params['path'],
$params['domain'],
$params['secure'],
$params['httponly']
);
$this->started = true;
return $this;
}
/**
* @inheritdoc
*/
public function invalidate()
{
$params = session_get_cookie_params();
setcookie(
session_name(),
'',
time() - 42000,
$params['path'],
$params['domain'],
$params['secure'],
$params['httponly']
);
session_unset();
session_destroy();
$this->started = false;
return $this;
}
/**
* @inheritdoc
*/
public function close()
{
if ($this->started) {
session_write_close();
}
$this->started = false;
return $this;
}
/**
* @inheritdoc
*/
public function clear()
{
session_unset();
return $this;
}
/**
* @inheritdoc
*/
public function getAll()
{
return $_SESSION;
}
/**
* @inheritdoc
*/
public function getIterator()
{
return new \ArrayIterator($_SESSION);
}
/**
* @inheritdoc
*/
public function isStarted()
{
return $this->started;
}
/**
* @inheritdoc
*/
public function __isset($name)
{
return isset($_SESSION[$name]);
}
/**
* @inheritdoc
*/
public function __get($name)
{
return isset($_SESSION[$name]) ? $_SESSION[$name] : null;
}
/**
* @inheritdoc
*/
public function __set($name, $value)
{
$_SESSION[$name] = $value;
}
/**
* @inheritdoc
*/
public function __unset($name)
{
unset($_SESSION[$name]);
}
/**
* http://php.net/manual/en/function.session-status.php#113468
* Check if session is started nicely.
* @return bool
*/
protected function isSessionStarted()
{
return \PHP_SAPI !== 'cli' ? \PHP_SESSION_ACTIVE === session_status() : false;
}
/**
* @param string $key
* @param mixed $value
*/
protected function ini_set($key, $value)
{
if (!is_string($value)) {
if (is_bool($value)) {
$value = $value ? '1' : '0';
}
$value = (string)$value;
}
ini_set($key, $value);
}
}
@@ -0,0 +1,147 @@
<?php
/**
* @package Grav\Framework\Session
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Framework\Session;
/**
* Class Session
* @package Grav\Framework\Session
*/
interface SessionInterface extends \IteratorAggregate
{
/**
* Get current session instance.
*
* @return Session
* @throws \RuntimeException
*/
public static function getInstance();
/**
* Get session ID
*
* @return string|null Session ID
*/
public function getId();
/**
* Set session ID
*
* @param string $id Session ID
*
* @return $this
*/
public function setId($id);
/**
* Get session name
*
* @return string|null
*/
public function getName();
/**
* Set session name
*
* @param string $name
*
* @return $this
*/
public function setName($name);
/**
* Sets session.* ini variables.
*
* @param array $options
*
* @see http://php.net/session.configuration
*/
public function setOptions(array $options);
/**
* Starts the session storage
*
* @param bool $readonly
* @return $this
* @throws \RuntimeException
*/
public function start($readonly = false);
/**
* Invalidates the current session.
*
* @return $this
*/
public function invalidate();
/**
* Force the session to be saved and closed
*
* @return $this
*/
public function close();
/**
* Free all session variables.
*
* @return $this
*/
public function clear();
/**
* Returns all session variables.
*
* @return array
*/
public function getAll();
/**
* Retrieve an external iterator
*
* @return \ArrayIterator Return an ArrayIterator of $_SESSION
*/
public function getIterator();
/**
* Checks if the session was started.
*
* @return Boolean
*/
public function isStarted();
/**
* Checks if session variable is defined.
*
* @param string $name
* @return bool
*/
public function __isset($name);
/**
* Returns session variable.
*
* @param string $name
* @return mixed
*/
public function __get($name);
/**
* Sets session variable.
*
* @param string $name
* @param mixed $value
*/
public function __set($name, $value);
/**
* Removes session variable.
*
* @param string $name
*/
public function __unset($name);
}