maj && modif graphique
@@ -27,6 +27,8 @@ user/themes/quark/*
|
||||
!user/themes/quark/.*
|
||||
user/localhost/config/security.yaml
|
||||
user/config/security.yaml
|
||||
user/pages/*
|
||||
user/pages/.*
|
||||
|
||||
# OS Generated
|
||||
.DS_Store*
|
||||
|
||||
@@ -1,3 +1,131 @@
|
||||
# v1.6.28
|
||||
## 10/07/2020
|
||||
|
||||
1. [](#new)
|
||||
* Back-ported twig `{% cache %}` tag from Grav 1.7
|
||||
* Back-ported `Utils::fullPath()` helper function from Grav 1.7
|
||||
* Back-ported `{{ svg_image() }}` Twig function from Grav 1.7
|
||||
* Back-ported `Folder::countChildren()` function from Grav 1.7
|
||||
1. [](#improved)
|
||||
* Use new `{{ theme_var() }}` enhanced logic from Grav 1.7
|
||||
* Improved `Excerpts` class with fixes and functionality from Grav 1.7
|
||||
* Ensure `onBlueprintCreated()` is initialized first
|
||||
* Do not cache default `404` error page
|
||||
* Composer update of vendor libraries
|
||||
* Switched `Caddyfile` to use new Caddy2 syntax + improved usability
|
||||
1. [](#bugfix)
|
||||
* Fixed Referer reference during GPM calls.
|
||||
* Fixed fatal error with toggled lists
|
||||
|
||||
# v1.6.27
|
||||
## 09/01/2020
|
||||
|
||||
1. [](#improved)
|
||||
* Right trim route for safety
|
||||
* Use the proper ellipsis for summary [#2939](https://github.com/getgrav/grav/pull/2939)
|
||||
* Left pad schedule times with zeros [#2921](https://github.com/getgrav/grav/pull/2921)
|
||||
|
||||
# v1.6.26
|
||||
## 06/08/2020
|
||||
|
||||
1. [](#improved)
|
||||
* Added new configuration option to control the supported attributes in markdown links [#2882](https://github.com/getgrav/grav/issues/2882)
|
||||
1. [](#bugfix)
|
||||
* Fixed blueprint for `system.pages.hide_empty_folders` [#1925](https://github.com/getgrav/grav/issues/2925)
|
||||
* JSON Route of homepage with no ‘route’ set is valid
|
||||
* Fix case-insensitive search of location header [form#425](https://github.com/getgrav/grav-plugin-form/issues/425)
|
||||
|
||||
# v1.6.25
|
||||
## 05/14/2020
|
||||
|
||||
1. [](#improved)
|
||||
* Added system configuration support for `HTTP_X_Forwarded` headers (host disabled by default)
|
||||
* Updated `PHPUserAgentParser` to 1.0.0
|
||||
* Bump `Go` to version 1.13 in `travis.yaml`
|
||||
|
||||
# v1.6.24
|
||||
## 04/27/2020
|
||||
|
||||
1. [](#improved)
|
||||
* Added support for `X-Forwarded-Host` [#2891](https://github.com/getgrav/grav/pull/2891)
|
||||
* Disable XDebug in Travis builds
|
||||
|
||||
# v1.6.23
|
||||
## 03/19/2020
|
||||
|
||||
1. [](#new)
|
||||
* Moved `Parsedown` 1.6 and `ParsedownExtra` 0.7 into `Grav\Framework\Parsedown` to allow fixes
|
||||
* Added `aliases.php` with references to direct `\Parsedown` and `\ParsedownExtra` references
|
||||
1. [](#improved)
|
||||
* Upgraded `jQuery` to latest 3.4.1 version [#2859](https://github.com/getgrav/grav/issues/2859)
|
||||
1. [](#bugfix)
|
||||
* Fixed PHP 7.4 issue in ParsedownExtra [#2832](https://github.com/getgrav/grav/issues/2832)
|
||||
* Fix for [user reported](https://twitter.com/OriginalSicksec) CVE path-based open redirect
|
||||
* Fix for `stream_set_option` error with PHP 7.4 via Toolbox#28 [#2850](https://github.com/getgrav/grav/issues/2850)
|
||||
|
||||
# v1.6.22
|
||||
## 03/05/2020
|
||||
|
||||
1. [](#new)
|
||||
* Added `Pages::reset()` method
|
||||
1. [](#improved)
|
||||
* Updated Negotiation library to address issues [#2513](https://github.com/getgrav/grav/issues/2513)
|
||||
1. [](#bugfix)
|
||||
* Fixed issue with search plugins not being able to switch between page translations
|
||||
* Fixed issues with `Pages::baseRoute()` not picking up active language reliably
|
||||
* Reverted `validation: strict` fix as it breaks sites, see [#1273](https://github.com/getgrav/grav/issues/1273)
|
||||
|
||||
# v1.6.21
|
||||
## 02/11/2020
|
||||
|
||||
1. [](#new)
|
||||
* Added `ConsoleCommand::setLanguage()` method to set language to be used from CLI
|
||||
* Added `ConsoleCommand::initializeGrav()` method to properly set up Grav instance to be used from CLI
|
||||
* Added `ConsoleCommand::initializePlugins()`method to properly set up all plugins to be used from CLI
|
||||
* Added `ConsoleCommand::initializeThemes()`method to properly set up current theme to be used from CLI
|
||||
* Added `ConsoleCommand::initializePages()` method to properly set up pages to be used from CLI
|
||||
1. [](#improved)
|
||||
* Vendor updates
|
||||
1. [](#bugfix)
|
||||
* Fixed `bin/plugin` CLI calling `$themes->init()` way too early (removed it, use above methods instead)
|
||||
* Fixed call to `$grav['page']` crashing CLI
|
||||
* Fixed encoding problems when PHP INI setting `default_charset` is not `utf-8` [#2154](https://github.com/getgrav/grav/issues/2154)
|
||||
|
||||
# v1.6.20
|
||||
## 02/03/2020
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fixed incorrect routing caused by `str_replace()` in `Uri::init()` [#2754](https://github.com/getgrav/grav/issues/2754)
|
||||
* Fixed session cookie is being set twice in the HTTP header [#2745](https://github.com/getgrav/grav/issues/2745)
|
||||
* Fixed session not restarting if user was invalid (downgrading from Grav 1.7)
|
||||
* Fixed filesystem iterator calls with non-existing folders
|
||||
* Fixed `checkbox` field not being saved, requires also Form v4.0.2 [#1225](https://github.com/getgrav/grav/issues/1225)
|
||||
* Fixed `validation: strict` not working in blueprints [#1273](https://github.com/getgrav/grav/issues/1273)
|
||||
* Fixed `Data::filter()` removing empty fields (such as empty list) by default [#2805](https://github.com/getgrav/grav/issues/2805)
|
||||
* Fixed fatal error with non-integer page param value [#2803](https://github.com/getgrav/grav/issues/2803)
|
||||
* Fixed `Assets::addInlineJs()` parameter type mismatch between v1.5 and v1.6 [#2659](https://github.com/getgrav/grav/issues/2659)
|
||||
* Fixed `site.metadata` saving issues [#2615](https://github.com/getgrav/grav/issues/2615)
|
||||
|
||||
# v1.6.19
|
||||
## 12/04/2019
|
||||
|
||||
1. [](#new)
|
||||
* Catch PHP 7.4 deprecation messages and report them in debugbar instead of throwing fatal error
|
||||
1. [](#bugfix)
|
||||
* Fixed fatal error when calling `{{ grav.undefined }}`
|
||||
* Fixed multiple issues when there are no pages in the site
|
||||
* PHP 7.4 fix for [#2750](https://github.com/getgrav/grav/issues/2750)
|
||||
|
||||
# v1.6.18
|
||||
## 12/02/2019
|
||||
|
||||
1. [](#bugfix)
|
||||
* PHP 7.4 fix in `Pages::buildSort()`
|
||||
* Updated vendor libraries for PHP 7.4 fixes in Twig and other libraries
|
||||
* Fixed fatal error when `$page->id()` is null [#2731](https://github.com/getgrav/grav/pull/2731)
|
||||
* Fixed cache conflicts on pages with no set id
|
||||
* Fix rewrite rule for for `lighttpd` default config [#721](https://github.com/getgrav/grav/pull/2721)
|
||||
|
||||
# v1.6.17
|
||||
## 11/06/2019
|
||||
|
||||
@@ -50,7 +178,7 @@
|
||||
* Support new GRAV_BASEDIR environment variable [#2541](https://github.com/getgrav/grav/pull/2541)
|
||||
* Allow users to override plugin handler priorities [#2165](https://github.com/getgrav/grav/pull/2165)
|
||||
1. [](#improved)
|
||||
* Use new `Utils::getSupportedPageTypes()` to enforce `html,htm` at the front of the list [#2531](https://github.com/getgrav/grav/issues/2531)
|
||||
* Use new `Utils::getSupportedPageTypes()` to enforce `html,htm` at the front of the list [#2531](https://github.com/getgrav/grav/issues/2531)
|
||||
* Updated vendor libraries
|
||||
* Markdown filter is now page-aware so that it works with modular references [admin#1731](https://github.com/getgrav/grav-plugin-admin/issues/1731)
|
||||
* Check of `GRAV_USER_INSTANCE` constant is already defined [#2621](https://github.com/getgrav/grav/pull/2621)
|
||||
@@ -69,7 +197,7 @@
|
||||
* Fixed `FlexObject::exists()` failing sometimes just after the object has been saved
|
||||
* Fixed CSV formatter not encoding strings with `"` and `,` properly
|
||||
* Fixed var order in `Validation.php` [#2610](https://github.com/getgrav/grav/issues/2610)
|
||||
|
||||
|
||||
# v1.6.11
|
||||
## 06/21/2019
|
||||
|
||||
@@ -100,7 +228,7 @@
|
||||
* Fixed regression with `bin/plugin` not listing the plugins available (1c725c0)
|
||||
* Fixed bitwise operator in `TwigExtension::exifFunc()` [#2518](https://github.com/getgrav/grav/issues/2518)
|
||||
* Fixed issue with lang prefix incorrectly identifying as admin [#2511](https://github.com/getgrav/grav/issues/2511)
|
||||
* Fixed issue with `U0ils::pathPrefixedBYLanguageCode()` and trailing slash [#2510](https://github.com/getgrav/grav/issues/2511)
|
||||
* Fixed issue with `U0ils::pathPrefixedBYLanguageCode()` and trailing slash [#2510](https://github.com/getgrav/grav/issues/2511)
|
||||
* Fixed regresssion issue of `Utils::Url()` not returning `false` on failure. Added new optional `fail_gracefully` 3rd attribute to return string that caused failure [#2524](https://github.com/getgrav/grav/issues/2524)
|
||||
|
||||
# v1.6.9
|
||||
|
||||
@@ -28,6 +28,13 @@ if (!ini_get('date.timezone')) {
|
||||
date_default_timezone_set('UTC');
|
||||
}
|
||||
|
||||
// Set internal encoding.
|
||||
if (!\extension_loaded('mbstring')) {
|
||||
die("'mbstring' extension is not loaded. This is required for Grav to run correctly");
|
||||
}
|
||||
@ini_set('default_charset', 'UTF-8');
|
||||
mb_internal_encoding('UTF-8');
|
||||
|
||||
if (!file_exists(GRAV_ROOT . '/index.php')) {
|
||||
exit('FATAL: Must be run from ROOT directory of Grav!');
|
||||
}
|
||||
@@ -55,7 +62,7 @@ $grav->setup($environment);
|
||||
|
||||
$grav['config']->init();
|
||||
$grav['uri']->init();
|
||||
$grav['users'];
|
||||
$grav['accounts'];
|
||||
|
||||
$app = new Application('Grav Package Manager', GRAV_VERSION);
|
||||
$app->addCommands(array(
|
||||
|
||||
@@ -25,6 +25,17 @@ if (version_compare($ver = PHP_VERSION, $req = GRAV_PHP_MIN, '<')) {
|
||||
exit(sprintf("You are running PHP %s, but Grav needs at least PHP %s to run.\n", $ver, $req));
|
||||
}
|
||||
|
||||
if (!ini_get('date.timezone')) {
|
||||
date_default_timezone_set('UTC');
|
||||
}
|
||||
|
||||
// Set internal encoding.
|
||||
if (!\extension_loaded('mbstring')) {
|
||||
die("'mbstring' extension is not loaded. This is required for Grav to run correctly");
|
||||
}
|
||||
@ini_set('default_charset', 'UTF-8');
|
||||
mb_internal_encoding('UTF-8');
|
||||
|
||||
$climate = new League\CLImate\CLImate;
|
||||
$climate->arguments->add([
|
||||
'environment' => [
|
||||
@@ -42,10 +53,6 @@ $environment = $climate->arguments->get('environment');
|
||||
$grav = Grav::instance(array('loader' => $autoload));
|
||||
$grav->setup($environment);
|
||||
|
||||
if (!ini_get('date.timezone')) {
|
||||
date_default_timezone_set('UTC');
|
||||
}
|
||||
|
||||
if (!file_exists(GRAV_ROOT . '/index.php')) {
|
||||
exit('FATAL: Must be run from ROOT directory of Grav!');
|
||||
}
|
||||
|
||||
@@ -32,6 +32,13 @@ if (!ini_get('date.timezone')) {
|
||||
date_default_timezone_set('UTC');
|
||||
}
|
||||
|
||||
// Set internal encoding.
|
||||
if (!\extension_loaded('mbstring')) {
|
||||
die("'mbstring' extension is not loaded. This is required for Grav to run correctly");
|
||||
}
|
||||
@ini_set('default_charset', 'UTF-8');
|
||||
mb_internal_encoding('UTF-8');
|
||||
|
||||
if (!file_exists(GRAV_ROOT . '/index.php')) {
|
||||
exit('FATAL: Must be run from ROOT directory of Grav!');
|
||||
}
|
||||
@@ -51,12 +58,7 @@ $environment = $climate->arguments->get('environment');
|
||||
|
||||
$grav = Grav::instance(array('loader' => $autoload));
|
||||
$grav->setup($environment);
|
||||
|
||||
$grav['config']->init();
|
||||
$grav['uri']->init();
|
||||
$grav['users'];
|
||||
$grav['plugins']->init();
|
||||
$grav['themes']->init();
|
||||
$grav->initializeCli();
|
||||
|
||||
$app = new Application('Grav Plugins Commands', GRAV_VERSION);
|
||||
$pattern = '([A-Z]\w+Command\.php)';
|
||||
|
||||
@@ -28,8 +28,6 @@
|
||||
"kodus/psr7-server": "*",
|
||||
"nyholm/psr7": "^1.0",
|
||||
"twig/twig": "~1.40",
|
||||
"erusev/parsedown": "1.6.4",
|
||||
"erusev/parsedown-extra": "~0.7",
|
||||
"symfony/yaml": "~4.2.0",
|
||||
"symfony/console": "~4.2.0",
|
||||
"symfony/event-dispatcher": "~4.2.0",
|
||||
@@ -42,9 +40,9 @@
|
||||
"matthiasmullie/minify": "^1.3",
|
||||
"monolog/monolog": "~1.0",
|
||||
"gregwar/image": "2.*",
|
||||
"donatj/phpuseragentparser": "~0.10",
|
||||
"donatj/phpuseragentparser": "~1.0",
|
||||
"pimple/pimple": "~3.2",
|
||||
"rockettheme/toolbox": "~1.4",
|
||||
"rockettheme/toolbox": "~1.4.0",
|
||||
"maximebf/debugbar": "~1.15",
|
||||
"league/climate": "^3.4",
|
||||
"antoligy/dom-string-iterators": "^1.0",
|
||||
@@ -52,7 +50,7 @@
|
||||
"composer/ca-bundle": "^1.0",
|
||||
"dragonmantank/cron-expression": "^1.2",
|
||||
"phive/twig-extensions-deferred": "^1.0",
|
||||
"willdurand/negotiation": "^2.3"
|
||||
"willdurand/negotiation": "2.x-dev"
|
||||
},
|
||||
"require-dev": {
|
||||
"codeception/codeception": "^2.4",
|
||||
@@ -86,7 +84,8 @@
|
||||
"Grav\\": "system/src/Grav"
|
||||
},
|
||||
"files": [
|
||||
"system/defines.php"
|
||||
"system/defines.php",
|
||||
"system/aliases.php"
|
||||
]
|
||||
},
|
||||
"archive": {
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
chown -R kevin:http .
|
||||
find . -type f -exec chmod 664 {} \;
|
||||
find ./bin -type f -exec chmod 775 {} \;
|
||||
find . -type d -exec chmod 775 {} \;
|
||||
find . -type d -exec chmod +s {} \;
|
||||
@@ -20,6 +20,16 @@ if (PHP_SAPI === 'cli-server' && !isset($_SERVER['PHP_CLI_ROUTER'])) {
|
||||
die("PHP webserver requires a router to run Grav, please use: <pre>php -S {$_SERVER['SERVER_NAME']}:{$_SERVER['SERVER_PORT']} system/router.php</pre>");
|
||||
}
|
||||
|
||||
// Set timezone to default, falls back to system if php.ini not set
|
||||
date_default_timezone_set(@date_default_timezone_get());
|
||||
|
||||
// Set internal encoding.
|
||||
if (!\extension_loaded('mbstring')) {
|
||||
die("'mbstring' extension is not loaded. This is required for Grav to run correctly");
|
||||
}
|
||||
@ini_set('default_charset', 'UTF-8');
|
||||
mb_internal_encoding('UTF-8');
|
||||
|
||||
// Ensure vendor libraries exist
|
||||
$autoload = __DIR__ . '/vendor/autoload.php';
|
||||
if (!is_file($autoload)) {
|
||||
@@ -32,15 +42,6 @@ $loader = require $autoload;
|
||||
use Grav\Common\Grav;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
|
||||
// Set timezone to default, falls back to system if php.ini not set
|
||||
date_default_timezone_set(@date_default_timezone_get());
|
||||
|
||||
// Set internal encoding if mbstring loaded
|
||||
if (!\extension_loaded('mbstring')) {
|
||||
die("'mbstring' extension is not loaded. This is required for Grav to run correctly");
|
||||
}
|
||||
mb_internal_encoding('UTF-8');
|
||||
|
||||
// Get the Grav instance
|
||||
$grav = Grav::instance(
|
||||
array(
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
/** Moved from non-namespaced classes to Grav Framework */
|
||||
class_alias(Grav\Framework\Parsedown\Parsedown::class, '\Parsedown');
|
||||
class_alias(Grav\Framework\Parsedown\ParsedownExtra::class, '\ParsedownExtra');
|
||||
@@ -241,13 +241,15 @@ form:
|
||||
type: commalist
|
||||
|
||||
pages.hide_empty_folders:
|
||||
type: selectize
|
||||
size: large
|
||||
label: PLUGIN_ADMIN.HIDE_EMPTY_FOLDERS
|
||||
help: PLUGIN_ADMIN.HIDE_EMPTY_FOLDERS_HELP
|
||||
classes: fancy
|
||||
validate:
|
||||
type: commalist
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.HIDE_EMPTY_FOLDERS
|
||||
help: PLUGIN_ADMIN.HIDE_EMPTY_FOLDERS_HELP
|
||||
highlight: 0
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
pages.url_taxonomy_filters:
|
||||
type: toggle
|
||||
@@ -513,6 +515,16 @@ form:
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
pages.markdown.valid_link_attributes:
|
||||
type: selectize
|
||||
size: large
|
||||
label: PLUGIN_ADMIN.VALID_LINK_ATTRIBUTES
|
||||
help: PLUGIN_ADMIN.VALID_LINK_ATTRIBUTES_HELP
|
||||
placeholder: "rel, target, id, class, classes"
|
||||
classes: fancy
|
||||
validate:
|
||||
type: commalist
|
||||
|
||||
caching:
|
||||
type: tab
|
||||
title: PLUGIN_ADMIN.CACHING
|
||||
@@ -1379,6 +1391,51 @@ form:
|
||||
label: PLUGIN_ADMIN.CUSTOM_BASE_URL
|
||||
help: PLUGIN_ADMIN.CUSTOM_BASE_URL_HELP
|
||||
|
||||
http_x_forwarded.protocol:
|
||||
type: toggle
|
||||
label: HTTP_X_FORWARDED_PROTO Enabled
|
||||
highlight: 1
|
||||
default: 1
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
http_x_forwarded.host:
|
||||
type: toggle
|
||||
label: HTTP_X_FORWARDED_HOST Enabled
|
||||
highlight: 0
|
||||
default: 0
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
http_x_forwarded.port:
|
||||
type: toggle
|
||||
label: HTTP_X_FORWARDED_PORT Enabled
|
||||
highlight: 1
|
||||
default: 1
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
http_x_forwarded.ip:
|
||||
type: toggle
|
||||
label: HTTP_X_FORWARDED IP Enabled
|
||||
highlight: 1
|
||||
default: 1
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
|
||||
accounts.type:
|
||||
type: hidden
|
||||
|
||||
|
||||
@@ -10,6 +10,11 @@ custom_base_url: '' # Set the base_url manually, e.
|
||||
username_regex: '^[a-z0-9_-]{3,16}$' # Only lowercase chars, digits, dashes, underscores. 3 - 16 chars
|
||||
pwd_regex: '(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}' # At least one number, one uppercase and lowercase letter, and be at least 8+ chars
|
||||
intl_enabled: true # Special logic for PHP International Extension (mod_intl)
|
||||
http_x_forwarded: # Configuration options for the various HTTP_X_FORWARD headers
|
||||
protocol: true
|
||||
host: false
|
||||
port: true
|
||||
ip: true
|
||||
|
||||
languages:
|
||||
supported: [] # List of languages supported. eg: [en, fr, de]
|
||||
@@ -54,6 +59,12 @@ pages:
|
||||
special_chars: # List of special characters to automatically convert to entities
|
||||
'>': 'gt'
|
||||
'<': 'lt'
|
||||
valid_link_attributes: # Valid attributes to pass through via markdown links
|
||||
- rel
|
||||
- target
|
||||
- id
|
||||
- class
|
||||
- classes
|
||||
types: [html,htm,xml,txt,json,rss,atom] # list of valid page types
|
||||
append_url_extension: '' # Append page's extension in Page urls (e.g. '.html' results in /path/page.html)
|
||||
expires: 604800 # Page expires time in seconds (604800 seconds = 7 days)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
// Some standard defines
|
||||
define('GRAV', true);
|
||||
define('GRAV_VERSION', '1.6.17');
|
||||
define('GRAV_VERSION', '1.6.28');
|
||||
define('GRAV_TESTING', false);
|
||||
define('DS', '/');
|
||||
|
||||
|
||||
@@ -2,4 +2,5 @@
|
||||
title: Not Found
|
||||
routable: false
|
||||
notfound: true
|
||||
expires: 0
|
||||
---
|
||||
|
||||
@@ -51,7 +51,11 @@ trait LegacyAssetsTrait
|
||||
// special case to handle old attributes being passed in
|
||||
if (isset($arguments['attributes'])) {
|
||||
$old_attributes = $arguments['attributes'];
|
||||
$arguments = array_merge($arguments, $old_attributes);
|
||||
if (is_array($old_attributes)) {
|
||||
$arguments = array_merge($arguments, $old_attributes);
|
||||
} else {
|
||||
$arguments['type'] = $old_attributes;
|
||||
}
|
||||
}
|
||||
unset($arguments['attributes']);
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
|
||||
namespace Grav\Common;
|
||||
|
||||
use function donatj\UserAgent\parse_user_agent;
|
||||
|
||||
/**
|
||||
* Internally uses the PhpUserAgent package https://github.com/donatj/PhpUserAgent
|
||||
*/
|
||||
|
||||
@@ -136,7 +136,7 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
|
||||
{
|
||||
$messages = $this->checkRequired($data, $rules);
|
||||
|
||||
foreach ($data as $key => $field) {
|
||||
foreach ($data as $key => $child) {
|
||||
$val = $rules[$key] ?? $rules['*'] ?? null;
|
||||
$rule = \is_string($val) ? $this->items[$val] : null;
|
||||
|
||||
@@ -147,10 +147,10 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
|
||||
continue;
|
||||
}
|
||||
|
||||
$messages += Validation::validate($field, $rule);
|
||||
} elseif (\is_array($field) && \is_array($val)) {
|
||||
$messages += Validation::validate($child, $rule);
|
||||
} elseif (\is_array($child) && \is_array($val)) {
|
||||
// Array has been defined in blueprints.
|
||||
$messages += $this->validateArray($field, $val);
|
||||
$messages += $this->validateArray($child, $val);
|
||||
} elseif (isset($rules['validation']) && $rules['validation'] === 'strict') {
|
||||
// Undefined/extra item.
|
||||
throw new \RuntimeException(sprintf('%s is not defined in blueprints', $key));
|
||||
@@ -232,8 +232,19 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
|
||||
continue;
|
||||
}
|
||||
if (is_array($value)) {
|
||||
// Special toggle handling for all the nested data.
|
||||
$toggle = $toggles[$key] ?? [];
|
||||
if (!is_array($toggle)) {
|
||||
if (!$toggle) {
|
||||
$data[$key] = null;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$toggle = [];
|
||||
}
|
||||
// Recursively fetch the items.
|
||||
$data[$key] = $this->processFormRecursive($data[$key] ?? null, $toggles[$key] ?? [], $value);
|
||||
$data[$key] = $this->processFormRecursive($data[$key] ?? null, $toggle, $value);
|
||||
} else {
|
||||
$field = $this->get($value);
|
||||
// Do not add the field if:
|
||||
|
||||
@@ -32,6 +32,12 @@ class Data implements DataInterface, \ArrayAccess, \Countable, \JsonSerializable
|
||||
/** @var File */
|
||||
protected $storage;
|
||||
|
||||
/** @var bool */
|
||||
private $missingValuesAsNull = false;
|
||||
|
||||
/** @var bool */
|
||||
private $keepEmptyValues = true;
|
||||
|
||||
/**
|
||||
* @param array $items
|
||||
* @param Blueprint|callable $blueprints
|
||||
@@ -42,6 +48,28 @@ class Data implements DataInterface, \ArrayAccess, \Countable, \JsonSerializable
|
||||
$this->blueprints = $blueprints;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $value
|
||||
* @return $this
|
||||
*/
|
||||
public function setKeepEmptyValues(bool $value)
|
||||
{
|
||||
$this->keepEmptyValues = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $value
|
||||
* @return $this
|
||||
*/
|
||||
public function setMissingValuesAsNull(bool $value)
|
||||
{
|
||||
$this->missingValuesAsNull = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value by using dot notation for nested arrays/objects.
|
||||
*
|
||||
@@ -202,8 +230,8 @@ class Data implements DataInterface, \ArrayAccess, \Countable, \JsonSerializable
|
||||
public function filter()
|
||||
{
|
||||
$args = func_get_args();
|
||||
$missingValuesAsNull = (bool)(array_shift($args) ?: false);
|
||||
$keepEmptyValues = (bool)(array_shift($args) ?: false);
|
||||
$missingValuesAsNull = (bool)(array_shift($args) ?? $this->missingValuesAsNull);
|
||||
$keepEmptyValues = (bool)(array_shift($args) ?? $this->keepEmptyValues);
|
||||
|
||||
$this->items = $this->blueprints()->filter($this->items, $missingValuesAsNull, $keepEmptyValues);
|
||||
|
||||
|
||||
@@ -166,9 +166,18 @@ class Validation
|
||||
return (string) $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @param array $params
|
||||
* @param array $field
|
||||
* @return string|null
|
||||
*/
|
||||
protected static function filterCheckbox($value, array $params, array $field)
|
||||
{
|
||||
return (bool) $value;
|
||||
$value = (string)$value;
|
||||
$field_value = (string)($field['value'] ?? '1');
|
||||
|
||||
return $value === $field_value ? $value : null;
|
||||
}
|
||||
|
||||
protected static function filterCommaList($value, array $params, array $field)
|
||||
|
||||
@@ -347,7 +347,7 @@ class Debugger
|
||||
*/
|
||||
public function deprecatedErrorHandler($errno, $errstr, $errfile, $errline)
|
||||
{
|
||||
if ($errno !== E_USER_DEPRECATED) {
|
||||
if ($errno !== E_USER_DEPRECATED && $errno !== E_DEPRECATED) {
|
||||
if ($this->errorHandler) {
|
||||
return \call_user_func($this->errorHandler, $errno, $errstr, $errfile, $errline);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,10 @@ abstract class Folder
|
||||
*/
|
||||
public static function lastModifiedFolder($path)
|
||||
{
|
||||
if (!file_exists($path)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$last_modified = 0;
|
||||
|
||||
/** @var UniformResourceLocator $locator */
|
||||
@@ -56,6 +60,10 @@ abstract class Folder
|
||||
*/
|
||||
public static function lastModifiedFile($path, $extensions = 'md|yaml')
|
||||
{
|
||||
if (!file_exists($path)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$last_modified = 0;
|
||||
|
||||
/** @var UniformResourceLocator $locator */
|
||||
@@ -92,21 +100,24 @@ abstract class Folder
|
||||
*/
|
||||
public static function hashAllFiles($path)
|
||||
{
|
||||
$flags = \RecursiveDirectoryIterator::SKIP_DOTS;
|
||||
$files = [];
|
||||
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
if ($locator->isStream($path)) {
|
||||
$directory = $locator->getRecursiveIterator($path, $flags);
|
||||
} else {
|
||||
$directory = new \RecursiveDirectoryIterator($path, $flags);
|
||||
}
|
||||
if (file_exists($path)) {
|
||||
$flags = \RecursiveDirectoryIterator::SKIP_DOTS;
|
||||
|
||||
$iterator = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::SELF_FIRST);
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
if ($locator->isStream($path)) {
|
||||
$directory = $locator->getRecursiveIterator($path, $flags);
|
||||
} else {
|
||||
$directory = new \RecursiveDirectoryIterator($path, $flags);
|
||||
}
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
$files[] = $file->getPathname() . '?'. $file->getMTime();
|
||||
$iterator = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::SELF_FIRST);
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
$files[] = $file->getPathname() . '?'. $file->getMTime();
|
||||
}
|
||||
}
|
||||
|
||||
return md5(serialize($files));
|
||||
@@ -199,6 +210,9 @@ abstract class Folder
|
||||
if ($path === false) {
|
||||
throw new \RuntimeException("Path doesn't exist.");
|
||||
}
|
||||
if (!file_exists($path)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$compare = isset($params['compare']) ? 'get' . $params['compare'] : null;
|
||||
$pattern = $params['pattern'] ?? null;
|
||||
@@ -494,4 +508,19 @@ abstract class Folder
|
||||
|
||||
return $include_target ? @rmdir($folder) : true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does a directory contain children
|
||||
*
|
||||
* @param string $directory
|
||||
* @return int|false
|
||||
*/
|
||||
public static function countChildren($directory) {
|
||||
if (!is_dir($directory)) {
|
||||
return false;
|
||||
}
|
||||
$directories = glob($directory . '/*', GLOB_ONLYDIR);
|
||||
|
||||
return count($directories);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace Grav\Common\GPM\Local;
|
||||
|
||||
use Grav\Common\Data\Data;
|
||||
use Grav\Common\GPM\Common\Package as BasePackage;
|
||||
use Grav\Framework\Parsedown\Parsedown;
|
||||
|
||||
class Package extends BasePackage
|
||||
{
|
||||
@@ -23,7 +24,7 @@ class Package extends BasePackage
|
||||
|
||||
$this->settings = $package->toArray();
|
||||
|
||||
$html_description = \Parsedown::instance()->line($this->__get('description'));
|
||||
$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));
|
||||
|
||||
@@ -36,7 +36,6 @@ class Response
|
||||
private static $defaults = [
|
||||
|
||||
'curl' => [
|
||||
CURLOPT_REFERER => 'Grav GPM',
|
||||
CURLOPT_USERAGENT => 'Grav GPM',
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
@@ -285,6 +284,7 @@ class Response
|
||||
$options['fopen']['notification'] = ['self', 'progress'];
|
||||
}
|
||||
|
||||
$options['fopen']['header'] = 'Referer: ' . Grav::instance()['uri']->rootUrl(true);
|
||||
if (isset($options['fopen']['ssl'])) {
|
||||
$ssl = $options['fopen']['ssl'];
|
||||
unset($options['fopen']['ssl']);
|
||||
@@ -367,6 +367,8 @@ class Response
|
||||
*/
|
||||
private static function curlExecFollow($ch, $options, $callback)
|
||||
{
|
||||
curl_setopt_array($ch, [ CURLOPT_REFERER => Grav::instance()['uri']->rootUrl(true) ]);
|
||||
|
||||
if ($callback) {
|
||||
curl_setopt_array(
|
||||
$ch,
|
||||
@@ -409,7 +411,7 @@ class Response
|
||||
} else {
|
||||
$code = (int)curl_getinfo($rch, CURLINFO_HTTP_CODE);
|
||||
if ($code === 301 || $code === 302 || $code === 303) {
|
||||
preg_match('/Location:(.*?)\n/', $header, $matches);
|
||||
preg_match('/(?:^|\n)Location:(.*?)\n/i', $header, $matches);
|
||||
$uri = trim(array_pop($matches));
|
||||
} else {
|
||||
$code = 0;
|
||||
|
||||
@@ -170,6 +170,29 @@ class Grav extends Container
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize CLI environment.
|
||||
*
|
||||
* Call after `$grav->setup($environment)`
|
||||
*
|
||||
* - Load configuration
|
||||
* - Disable debugger
|
||||
* - Set timezone, locale
|
||||
* - Load plugins
|
||||
* - Set Users type to be used in the site
|
||||
*
|
||||
* This method WILL NOT initialize assets, twig or pages.
|
||||
*
|
||||
* @param string|null $environment
|
||||
* @return $this
|
||||
*/
|
||||
public function initializeCli()
|
||||
{
|
||||
InitializeProcessor::initializeCli($this);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a request
|
||||
*/
|
||||
@@ -238,7 +261,7 @@ class Grav extends Container
|
||||
);
|
||||
|
||||
$default = function (ServerRequestInterface $request) {
|
||||
return new Response(404);
|
||||
return new Response(404, ['Expires' => 0, 'Cache-Control' => 'no-cache, no-store, must-revalidate'], 'Not Found');
|
||||
};
|
||||
|
||||
/** @var Debugger $debugger */
|
||||
@@ -293,7 +316,10 @@ class Grav extends Container
|
||||
/** @var Uri $uri */
|
||||
$uri = $this['uri'];
|
||||
|
||||
//Check for code in route
|
||||
// Clean route for redirect
|
||||
$route = preg_replace("#^\/[\\\/]+\/#", '/', $route);
|
||||
|
||||
// Check for code in route
|
||||
$regex = '/.*(\[(30[1-7])\])$/';
|
||||
preg_match($regex, $route, $matches);
|
||||
if ($matches) {
|
||||
@@ -439,11 +465,16 @@ class Grav extends Container
|
||||
* Used to call closures.
|
||||
*
|
||||
* Source: http://stackoverflow.com/questions/419804/closures-as-class-members
|
||||
*
|
||||
* @param string $method
|
||||
* @param array $args
|
||||
* @return
|
||||
*/
|
||||
public function __call($method, $args)
|
||||
{
|
||||
$closure = $this->{$method};
|
||||
\call_user_func_array($closure, $args);
|
||||
$closure = $this->{$method} ?? null;
|
||||
|
||||
return is_callable($closure) ? $closure(...$args) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/**
|
||||
* @package Grav\Common\Helpers
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
@@ -32,7 +32,7 @@ class Excerpts
|
||||
$excerpt = static::processLinkExcerpt($excerpt, $page, 'image');
|
||||
|
||||
$excerpt['element']['attributes']['src'] = $excerpt['element']['attributes']['href'];
|
||||
unset ($excerpt['element']['attributes']['href']);
|
||||
unset($excerpt['element']['attributes']['href']);
|
||||
|
||||
$excerpt = static::processImageExcerpt($excerpt, $page);
|
||||
|
||||
@@ -43,6 +43,26 @@ class Excerpts
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process Grav page link URL from HTML tag
|
||||
*
|
||||
* @param string $html HTML tag e.g. `<a href="../foo">Page Link</a>`
|
||||
* @param PageInterface|null $page Page, defaults to the current page object
|
||||
* @return string Returns final HTML string
|
||||
*/
|
||||
public static function processLinkHtml($html, PageInterface $page = null)
|
||||
{
|
||||
$excerpt = static::getExcerptFromHtml($html, 'a');
|
||||
|
||||
$original_href = $excerpt['element']['attributes']['href'];
|
||||
$excerpt = static::processLinkExcerpt($excerpt, $page, 'link');
|
||||
$excerpt['element']['attributes']['data-href'] = $original_href;
|
||||
|
||||
$html = static::getHtmlFromExcerpt($excerpt);
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an Excerpt array from a chunk of HTML
|
||||
*
|
||||
@@ -52,22 +72,35 @@ class Excerpts
|
||||
*/
|
||||
public static function getExcerptFromHtml($html, $tag)
|
||||
{
|
||||
$doc = new \DOMDocument();
|
||||
$doc->loadHTML($html);
|
||||
$images = $doc->getElementsByTagName($tag);
|
||||
$excerpt = null;
|
||||
$doc = new \DOMDocument('1.0', 'UTF-8');
|
||||
$internalErrors = libxml_use_internal_errors(true);
|
||||
$doc->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
|
||||
libxml_use_internal_errors($internalErrors);
|
||||
|
||||
foreach ($images as $image) {
|
||||
$elements = $doc->getElementsByTagName($tag);
|
||||
$excerpt = null;
|
||||
$inner = [];
|
||||
|
||||
/** @var \DOMElement $element */
|
||||
foreach ($elements as $element) {
|
||||
$attributes = [];
|
||||
foreach ($image->attributes as $name => $value) {
|
||||
foreach ($element->attributes as $name => $value) {
|
||||
$attributes[$name] = $value->value;
|
||||
}
|
||||
$excerpt = [
|
||||
'element' => [
|
||||
'name' => $image->tagName,
|
||||
'name' => $element->tagName,
|
||||
'attributes' => $attributes
|
||||
]
|
||||
];
|
||||
|
||||
foreach ($element->childNodes as $node) {
|
||||
$inner[] = $doc->saveHTML($node);
|
||||
}
|
||||
|
||||
$excerpt = array_merge_recursive($excerpt, ['element' => ['text' => implode('', $inner)]]);
|
||||
|
||||
|
||||
}
|
||||
|
||||
return $excerpt;
|
||||
@@ -95,7 +128,7 @@ class Excerpts
|
||||
|
||||
if (isset($element['text'])) {
|
||||
$html .= '>';
|
||||
$html .= $element['text'];
|
||||
$html .= is_array($element['text']) ? static::getHtmlFromExcerpt(['element' => $element['text']]) : $element['text'];
|
||||
$html .= '</'.$element['name'].'>';
|
||||
} else {
|
||||
$html .= ' />';
|
||||
|
||||
@@ -11,8 +11,9 @@ namespace Grav\Common\Markdown;
|
||||
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Page\Markdown\Excerpts;
|
||||
use Grav\Framework\Parsedown\Parsedown as ParsedownLib;
|
||||
|
||||
class Parsedown extends \Parsedown
|
||||
class Parsedown extends ParsedownLib
|
||||
{
|
||||
use ParsedownGravTrait;
|
||||
|
||||
|
||||
@@ -11,8 +11,9 @@ namespace Grav\Common\Markdown;
|
||||
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Page\Markdown\Excerpts;
|
||||
use Grav\Framework\Parsedown\ParsedownExtra as ParsedownExtraLib;
|
||||
|
||||
class ParsedownExtra extends \ParsedownExtra
|
||||
class ParsedownExtra extends ParsedownExtraLib
|
||||
{
|
||||
use ParsedownGravTrait;
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ class Excerpts
|
||||
);
|
||||
|
||||
// Valid attributes supported.
|
||||
$valid_attributes = ['rel', 'target', 'id', 'class', 'classes'];
|
||||
$valid_attributes = Grav::instance()['config']->get('system.pages.markdown.valid_link_attributes');
|
||||
|
||||
// Unless told to not process, go through actions.
|
||||
if (array_key_exists('noprocess', $actions)) {
|
||||
@@ -232,6 +232,7 @@ class Excerpts
|
||||
$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(
|
||||
|
||||
@@ -516,6 +516,15 @@ class ImageMedium extends Medium
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle this commonly used variant
|
||||
*/
|
||||
public function cropZoom()
|
||||
{
|
||||
$this->__call('zoomCrop', func_get_args());
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forward the call to the image processing method.
|
||||
*
|
||||
@@ -525,10 +534,6 @@ class ImageMedium extends Medium
|
||||
*/
|
||||
public function __call($method, $args)
|
||||
{
|
||||
if ($method === 'cropZoom') {
|
||||
$method = 'zoomCrop';
|
||||
}
|
||||
|
||||
if (!\in_array($method, self::$magic_actions, true)) {
|
||||
return parent::__call($method, $args);
|
||||
}
|
||||
|
||||
@@ -608,12 +608,12 @@ class Page implements PageInterface
|
||||
return $content;
|
||||
}
|
||||
|
||||
return mb_strimwidth($content, 0, $size, '...', 'utf-8');
|
||||
return mb_strimwidth($content, 0, $size, '…', 'UTF-8');
|
||||
}
|
||||
|
||||
$summary = Utils::truncateHtml($content, $size);
|
||||
|
||||
return html_entity_decode($summary);
|
||||
return html_entity_decode($summary, ENT_COMPAT | ENT_HTML401, 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -659,7 +659,7 @@ class Page implements PageInterface
|
||||
// Load cached content
|
||||
/** @var Cache $cache */
|
||||
$cache = Grav::instance()['cache'];
|
||||
$cache_id = md5('page' . $this->id());
|
||||
$cache_id = md5('page' . $this->getCacheKey());
|
||||
$content_obj = $cache->fetch($cache_id);
|
||||
|
||||
if (is_array($content_obj)) {
|
||||
@@ -865,7 +865,7 @@ class Page implements PageInterface
|
||||
public function cachePageContent()
|
||||
{
|
||||
$cache = Grav::instance()['cache'];
|
||||
$cache_id = md5('page' . $this->id());
|
||||
$cache_id = md5('page' . $this->getCacheKey());
|
||||
$cache->save($cache_id, ['content' => $this->content, 'content_meta' => $this->content_meta]);
|
||||
}
|
||||
|
||||
@@ -1200,7 +1200,7 @@ class Page implements PageInterface
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getCacheKey(): string
|
||||
public function getCacheKey(): string
|
||||
{
|
||||
return $this->id();
|
||||
}
|
||||
@@ -1694,7 +1694,7 @@ class Page implements PageInterface
|
||||
$metadata['generator'] = 'GravCMS';
|
||||
|
||||
// Get initial metadata for the page
|
||||
$metadata = array_merge($metadata, Grav::instance()['config']->get('site.metadata'));
|
||||
$metadata = array_merge($metadata, Grav::instance()['config']->get('site.metadata', []));
|
||||
|
||||
if (isset($this->header->metadata) && is_array($this->header->metadata)) {
|
||||
// Merge any site.metadata settings in with page metadata
|
||||
@@ -2009,6 +2009,10 @@ class Page implements PageInterface
|
||||
*/
|
||||
public function id($var = null)
|
||||
{
|
||||
if (null === $this->id) {
|
||||
// We need to set unique id to avoid potential cache conflicts between pages.
|
||||
$var = time() . md5($this->filePath());
|
||||
}
|
||||
if ($var !== null) {
|
||||
// store unique per language
|
||||
$active_lang = Grav::instance()['language']->getLanguage() ?: '';
|
||||
@@ -2824,7 +2828,7 @@ class Page implements PageInterface
|
||||
if ($pagination) {
|
||||
$params = $collection->params();
|
||||
|
||||
$limit = $params['limit'] ?? 0;
|
||||
$limit = (int)($params['limit'] ?? 0);
|
||||
$start = !empty($params['pagination']) ? ($uri->currentPage() - 1) * $limit : 0;
|
||||
|
||||
if ($limit && $collection->count() > $limit) {
|
||||
|
||||
@@ -95,6 +95,8 @@ class Pages
|
||||
|
||||
protected $initialized = false;
|
||||
|
||||
protected $active_lang;
|
||||
|
||||
/**
|
||||
* @var Types
|
||||
*/
|
||||
@@ -143,7 +145,7 @@ class Pages
|
||||
*/
|
||||
public function baseRoute($lang = null)
|
||||
{
|
||||
$key = $lang ?: 'default';
|
||||
$key = $lang ?: $this->active_lang ?: 'default';
|
||||
|
||||
if (!isset($this->baseRoute[$key])) {
|
||||
/** @var Language $language */
|
||||
@@ -236,6 +238,16 @@ class Pages
|
||||
$this->check_method = strtolower($method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset pages (used in search indexing etc).
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
$this->initialized = false;
|
||||
|
||||
$this->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Class initialization. Must be called before using this class.
|
||||
*/
|
||||
@@ -598,8 +610,8 @@ class Pages
|
||||
}
|
||||
|
||||
if (empty($blueprint->initialized)) {
|
||||
$this->grav->fireEvent('onBlueprintCreated', new Event(['blueprint' => $blueprint, 'type' => $type]));
|
||||
$blueprint->initialized = true;
|
||||
$this->grav->fireEvent('onBlueprintCreated', new Event(['blueprint' => $blueprint, 'type' => $type]));
|
||||
}
|
||||
|
||||
return $blueprint;
|
||||
@@ -958,6 +970,9 @@ class Pages
|
||||
|
||||
$pages_dir = $locator->findResource('page://');
|
||||
|
||||
// Set active language
|
||||
$this->active_lang = $language->getActive();
|
||||
|
||||
if ($config->get('system.cache.enabled')) {
|
||||
/** @var Cache $cache */
|
||||
$cache = $this->grav['cache'];
|
||||
@@ -982,17 +997,19 @@ class Pages
|
||||
|
||||
$this->pages_cache_id = md5($pages_dir . $hash . $language->getActive() . $config->checksum());
|
||||
|
||||
list($this->instances, $this->routes, $this->children, $taxonomy_map, $this->sort) = $cache->fetch($this->pages_cache_id);
|
||||
if (!$this->instances) {
|
||||
$cached = $cache->fetch($this->pages_cache_id);
|
||||
if ($cached) {
|
||||
$this->grav['debugger']->addMessage('Page cache hit.');
|
||||
|
||||
list($this->instances, $this->routes, $this->children, $taxonomy_map, $this->sort) = $cached;
|
||||
|
||||
// If pages was found in cache, set the taxonomy
|
||||
$taxonomy->taxonomy($taxonomy_map);
|
||||
} else {
|
||||
$this->grav['debugger']->addMessage('Page cache missed, rebuilding pages..');
|
||||
|
||||
// recurse pages and cache result
|
||||
$this->resetPages($pages_dir);
|
||||
|
||||
} else {
|
||||
// If pages was found in cache, set the taxonomy
|
||||
$this->grav['debugger']->addMessage('Page cache hit.');
|
||||
$taxonomy->taxonomy($taxonomy_map);
|
||||
}
|
||||
} else {
|
||||
$this->recurse($pages_dir);
|
||||
@@ -1261,14 +1278,13 @@ class Pages
|
||||
{
|
||||
$list = [];
|
||||
$header_default = null;
|
||||
$header_query = null;
|
||||
$header_query = [];
|
||||
|
||||
// do this header query work only once
|
||||
if (strpos($order_by, 'header.') === 0) {
|
||||
$header_query = explode('|', str_replace('header.', '', $order_by));
|
||||
if (isset($header_query[1])) {
|
||||
$header_default = $header_query[1];
|
||||
}
|
||||
$query = explode('|', str_replace('header.', '', $order_by), 2);
|
||||
$header_query = array_shift($query) ?? '';
|
||||
$header_default = array_shift($query);
|
||||
}
|
||||
|
||||
foreach ($pages as $key => $info) {
|
||||
@@ -1306,11 +1322,17 @@ class Pages
|
||||
case 'folder':
|
||||
$list[$key] = $child->folder();
|
||||
break;
|
||||
case (is_string($header_query[0])):
|
||||
$child_header = new Header((array)$child->header());
|
||||
$header_value = $child_header->get($header_query[0]);
|
||||
case 'manual':
|
||||
case 'default':
|
||||
default:
|
||||
if (is_string($header_query)) {
|
||||
$child_header = $child->header();
|
||||
if (!$child_header instanceof Header) {
|
||||
$child_header = new Header((array)$child_header);
|
||||
}
|
||||
$header_value = $child_header->get($header_query);
|
||||
if (is_array($header_value)) {
|
||||
$list[$key] = implode(',',$header_value);
|
||||
$list[$key] = implode(',', $header_value);
|
||||
} elseif ($header_value) {
|
||||
$list[$key] = $header_value;
|
||||
} else {
|
||||
@@ -1318,11 +1340,9 @@ class Pages
|
||||
}
|
||||
$sort_flags = $sort_flags ?: SORT_REGULAR;
|
||||
break;
|
||||
case 'manual':
|
||||
case 'default':
|
||||
default:
|
||||
$list[$key] = $key;
|
||||
$sort_flags = $sort_flags ?: SORT_REGULAR;
|
||||
}
|
||||
$list[$key] = $key;
|
||||
$sort_flags = $sort_flags ?: SORT_REGULAR;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -305,7 +305,7 @@ class Plugin implements EventSubscriberInterface, \ArrayAccess
|
||||
|
||||
// Create new config object and set it on the page object so it's cached for next time
|
||||
$page->modifyHeader($class_name_merged, new Data($header));
|
||||
} else if (isset($page_header->{$class_name_merged})) {
|
||||
} elseif (isset($page_header->{$class_name_merged})) {
|
||||
$merged = $page_header->{$class_name_merged};
|
||||
$header = $merged->toArray();
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
namespace Grav\Common\Processors;
|
||||
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Uri;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Framework\Session\Exceptions\SessionException;
|
||||
@@ -22,6 +23,22 @@ class InitializeProcessor extends ProcessorBase
|
||||
public $id = 'init';
|
||||
public $title = 'Initialize';
|
||||
|
||||
/** @var bool */
|
||||
private static $cli_initialized = false;
|
||||
|
||||
/**
|
||||
* @param Grav $grav
|
||||
*/
|
||||
public static function initializeCli(Grav $grav)
|
||||
{
|
||||
if (!static::$cli_initialized) {
|
||||
static::$cli_initialized = true;
|
||||
|
||||
$instance = new static($grav);
|
||||
$instance->processCli();
|
||||
}
|
||||
}
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
|
||||
{
|
||||
$this->startTimer();
|
||||
@@ -77,4 +94,35 @@ class InitializeProcessor extends ProcessorBase
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
|
||||
public function processCli(): void
|
||||
{
|
||||
// Load configuration.
|
||||
$this->container['config']->init();
|
||||
$this->container['plugins']->setup();
|
||||
|
||||
// Disable debugger.
|
||||
$this->container['debugger']->enabled(false);
|
||||
|
||||
// Set timezone, locale.
|
||||
/** @var Config $config */
|
||||
$config = $this->container['config'];
|
||||
$timezone = $config->get('system.timezone');
|
||||
if ($timezone) {
|
||||
date_default_timezone_set($timezone);
|
||||
}
|
||||
$this->container->setLocale();
|
||||
|
||||
// Load plugins.
|
||||
$this->container['plugins']->init();
|
||||
|
||||
// Initialize URI.
|
||||
/** @var Uri $uri */
|
||||
$uri = $this->container['uri'];
|
||||
$uri->init();
|
||||
|
||||
// Load accounts.
|
||||
// TODO: remove in 2.0.
|
||||
$this->container['accounts'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ class Cron
|
||||
'name_year' => 'année',
|
||||
'text_period' => 'Chaque %s',
|
||||
'text_mins' => 'à %s minutes',
|
||||
'text_time' => 'à %s:%s',
|
||||
'text_time' => 'à %02s:%02s',
|
||||
'text_dow' => 'le %s',
|
||||
'text_month' => 'de %s',
|
||||
'text_dom' => 'le %s',
|
||||
@@ -86,7 +86,7 @@ class Cron
|
||||
'name_year' => 'year',
|
||||
'text_period' => 'Every %s',
|
||||
'text_mins' => 'at %s minutes past the hour',
|
||||
'text_time' => 'at %s:%s',
|
||||
'text_time' => 'at %02s:%02s',
|
||||
'text_dow' => 'on %s',
|
||||
'text_month' => 'of %s',
|
||||
'text_dom' => 'on the %s',
|
||||
|
||||
@@ -26,6 +26,19 @@ class PagesServiceProvider implements ServiceProviderInterface
|
||||
return new Pages($c);
|
||||
};
|
||||
|
||||
if (\defined('GRAV_CLI')) {
|
||||
$container['page'] = static function ($c) {
|
||||
$path = $c['locator']->findResource('system://pages/notfound.md');
|
||||
$page = new Page();
|
||||
$page->init(new \SplFileInfo($path));
|
||||
$page->routable(false);
|
||||
|
||||
return $page;
|
||||
};
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$container['page'] = function ($c) {
|
||||
/** @var Grav $c */
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Twig
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Twig\Node;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
use Twig\Node\Node;
|
||||
|
||||
class TwigNodeCache extends Node
|
||||
{
|
||||
/**
|
||||
* @param string $key unique name for key
|
||||
* @param int $lifetime in seconds
|
||||
* @param Node $body
|
||||
* @param integer $lineno
|
||||
* @param string $tag
|
||||
*/
|
||||
public function __construct(string $key, int $lifetime, Node $body, $lineno, $tag = null)
|
||||
{
|
||||
parent::__construct(array('body' => $body), array( 'key' => $key, 'lifetime' => $lifetime), $lineno, $tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function compile(Compiler $compiler)
|
||||
{
|
||||
$boo = $this->getAttribute('key');
|
||||
$compiler
|
||||
->addDebugInfo($this)
|
||||
->write("\$cache = \\Grav\\Common\\Grav::instance()['cache'];\n")
|
||||
->write("\$key = \"twigcache-\" . \"" . $this->getAttribute('key') . "\";\n")
|
||||
->write("\$lifetime = " . $this->getAttribute('lifetime') . ";\n")
|
||||
->write("\$cache_body = \$cache->fetch(\$key);\n")
|
||||
->write("if (\$cache_body === false) {\n")
|
||||
->indent()
|
||||
->write("ob_start();\n")
|
||||
->indent()
|
||||
->subcompile($this->getNode('body'))
|
||||
->outdent()
|
||||
->write("\n")
|
||||
->write("\$cache_body = ob_get_clean();\n")
|
||||
->write("\$cache->save(\$key, \$cache_body, \$lifetime);\n")
|
||||
->outdent()
|
||||
->write("}\n")
|
||||
->write("echo \$cache_body;\n")
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Twig
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Twig\TokenParser;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Twig\Node\TwigNodeCache;
|
||||
use Twig\Token;
|
||||
use Twig\TokenParser\AbstractTokenParser;
|
||||
|
||||
/**
|
||||
* Adds ability to cache Twig between tags.
|
||||
*
|
||||
* {% cache 600 %}
|
||||
* {{ some_complex_work() }}
|
||||
* {% endcache %}
|
||||
*
|
||||
* Where the `600` is an optional lifetime in seconds
|
||||
*/
|
||||
class TwigTokenParserCache extends AbstractTokenParser
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function parse(Token $token)
|
||||
{
|
||||
$lineno = $token->getLine();
|
||||
$stream = $this->parser->getStream();
|
||||
$key = $this->parser->getVarName() . $lineno;
|
||||
$lifetime = Grav::instance()['cache']->getLifetime();
|
||||
|
||||
// Check for optional lifetime override
|
||||
if (!$stream->test(Token::BLOCK_END_TYPE)) {
|
||||
$lifetime_expr = $this->parser->getExpressionParser()->parseExpression();
|
||||
$lifetime = $lifetime_expr->getAttribute('value');
|
||||
}
|
||||
|
||||
$stream->expect(Token::BLOCK_END_TYPE);
|
||||
$body = $this->parser->subparse(array($this, 'decideCacheEnd'), true);
|
||||
$stream->expect(Token::BLOCK_END_TYPE);
|
||||
|
||||
return new TwigNodeCache($key, $lifetime, $body, $lineno, $this->getTag());
|
||||
}
|
||||
|
||||
/**
|
||||
* Decide if current token marks end of cache block.
|
||||
*
|
||||
* @param Token $token
|
||||
* @return bool
|
||||
*/
|
||||
public function decideCacheEnd(Token $token)
|
||||
{
|
||||
return $token->test('endcache');
|
||||
}
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getTag()
|
||||
{
|
||||
return 'cache';
|
||||
}
|
||||
}
|
||||
@@ -11,10 +11,12 @@ namespace Grav\Common\Twig;
|
||||
|
||||
use Cron\CronExpression;
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Data\Data;
|
||||
use Grav\Common\Debugger;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Language\Language;
|
||||
use Grav\Common\Page\Collection;
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Page\Media;
|
||||
use Grav\Common\Scheduler\Cron;
|
||||
use Grav\Common\Security;
|
||||
@@ -25,6 +27,7 @@ use Grav\Common\Twig\TokenParser\TwigTokenParserSwitch;
|
||||
use Grav\Common\Twig\TokenParser\TwigTokenParserThrow;
|
||||
use Grav\Common\Twig\TokenParser\TwigTokenParserTryCatch;
|
||||
use Grav\Common\Twig\TokenParser\TwigTokenParserMarkdown;
|
||||
use Grav\Common\Twig\TokenParser\TwigTokenParserCache;
|
||||
use Grav\Common\User\Interfaces\UserInterface;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Common\Yaml;
|
||||
@@ -167,13 +170,14 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
new \Twig_SimpleFunction('exif', [$this, 'exifFunc']),
|
||||
new \Twig_SimpleFunction('media_directory', [$this, 'mediaDirFunc']),
|
||||
new \Twig_SimpleFunction('body_class', [$this, 'bodyClassFunc']),
|
||||
new \Twig_SimpleFunction('theme_var', [$this, 'themeVarFunc']),
|
||||
new \Twig_SimpleFunction('header_var', [$this, 'pageHeaderVarFunc']),
|
||||
new \Twig_SimpleFunction('theme_var', [$this, 'themeVarFunc'], ['needs_context' => true]),
|
||||
new \Twig_SimpleFunction('header_var', [$this, 'pageHeaderVarFunc'], ['needs_context' => true]),
|
||||
new \Twig_SimpleFunction('read_file', [$this, 'readFileFunc']),
|
||||
new \Twig_SimpleFunction('nicenumber', [$this, 'niceNumberFunc']),
|
||||
new \Twig_SimpleFunction('nicefilesize', [$this, 'niceFilesizeFunc']),
|
||||
new \Twig_SimpleFunction('nicetime', [$this, 'nicetimeFunc']),
|
||||
new \Twig_SimpleFunction('cron', [$this, 'cronFunc']),
|
||||
new \Twig_SimpleFunction('svg_image', [$this, 'svgImageFunction']),
|
||||
new \Twig_SimpleFunction('xss', [$this, 'xssFunc']),
|
||||
|
||||
|
||||
@@ -201,6 +205,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
new TwigTokenParserStyle(),
|
||||
new TwigTokenParserMarkdown(),
|
||||
new TwigTokenParserSwitch(),
|
||||
new TwigTokenParserCache(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1055,7 +1060,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
*/
|
||||
public function jsonDecodeFilter($str, $assoc = false, $depth = 512, $options = 0)
|
||||
{
|
||||
return json_decode(html_entity_decode($str), $assoc, $depth, $options);
|
||||
return json_decode(html_entity_decode($str, ENT_COMPAT | ENT_HTML401, 'UTF-8'), $assoc, $depth, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1281,17 +1286,64 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
|
||||
/**
|
||||
* Get a theme variable
|
||||
* Will try to get the variable for the current page, if not found, it tries it's parent page on up to root.
|
||||
* If still not found, will use the theme's configuration value,
|
||||
* If still not found, will use the $default value passed in
|
||||
*
|
||||
* @param string $var
|
||||
* @param bool $default
|
||||
* @param $context Twig Context
|
||||
* @param string $var variable to be found (using dot notation)
|
||||
* @param null $default the default value to be used as last resort
|
||||
* @param null $page an optional page to use for the current page
|
||||
* @param bool $exists toggle to simply return the page where the variable is set, else null
|
||||
* @return string
|
||||
*/
|
||||
public function themeVarFunc($var, $default = null)
|
||||
public function themeVarFunc($context, $var, $default = null, $page = null, $exists = false)
|
||||
{
|
||||
$header = $this->grav['page']->header();
|
||||
$header_classes = $header->{$var} ?? null;
|
||||
$page = $page ?? $context['page'] ?? Grav::instance()['page'] ?? null;
|
||||
|
||||
return $header_classes ?: $this->config->get('theme.' . $var, $default);
|
||||
// Try to find var in the page headers
|
||||
if ($page instanceof PageInterface && $page->exists()) {
|
||||
// Loop over pages and look for header vars
|
||||
while ($page && !$page->root()) {
|
||||
$header = new Data((array)$page->header());
|
||||
$value = $header->get($var);
|
||||
if (isset($value)) {
|
||||
if ($exists) {
|
||||
return $page;
|
||||
} else {
|
||||
return $value;
|
||||
}
|
||||
|
||||
}
|
||||
$page = $page->parent();
|
||||
}
|
||||
}
|
||||
|
||||
if ($exists) {
|
||||
return false;
|
||||
} else {
|
||||
return Grav::instance()['config']->get('theme.' . $var, $default);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Look for a page header variable in an array of pages working its way through until a value is found
|
||||
*
|
||||
* @param $context
|
||||
* @param string $var the variable to look for in the page header
|
||||
* @param string|string[]|null $pages array of pages to check (current page upwards if not null)
|
||||
* @param bool $exists if true, return the page where the var is found, not the value
|
||||
* @return mixed
|
||||
* @deprecated 1.7 Use themeVarFunc() instead
|
||||
*/
|
||||
public function pageHeaderVarFunc($context, $var, $pages = null)
|
||||
{
|
||||
if (is_array($pages)) {
|
||||
$page = array_shift($pages);
|
||||
} else {
|
||||
$page = null;
|
||||
}
|
||||
return $this->themeVarFunc($context, $var, null, $page);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1319,41 +1371,6 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
return $body_classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Look for a page header variable in an array of pages working its way through until a value is found
|
||||
*
|
||||
* @param string $var
|
||||
* @param string|string[]|null $pages
|
||||
* @return mixed
|
||||
*/
|
||||
public function pageHeaderVarFunc($var, $pages = null)
|
||||
{
|
||||
if ($pages === null) {
|
||||
$pages = $this->grav['page'];
|
||||
}
|
||||
|
||||
// Make sure pages are an array
|
||||
if (!\is_array($pages)) {
|
||||
$pages = [$pages];
|
||||
}
|
||||
|
||||
// Loop over pages and look for header vars
|
||||
foreach ($pages as $page) {
|
||||
if (\is_string($page)) {
|
||||
$page = $this->grav['pages']->find($page);
|
||||
}
|
||||
|
||||
if ($page) {
|
||||
$header = $page->header();
|
||||
if (isset($header->{$var})) {
|
||||
return $header->{$var};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump/Encode data into YAML format
|
||||
*
|
||||
@@ -1442,4 +1459,41 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content of an SVG image and adds extra classes as needed
|
||||
*
|
||||
* @param $path
|
||||
* @param $classes
|
||||
* @return string|string[]|null
|
||||
*/
|
||||
public static function svgImageFunction($path, $classes)
|
||||
{
|
||||
$path = Utils::fullPath($path);
|
||||
|
||||
if (file_exists($path)) {
|
||||
$svg = file_get_contents($path);
|
||||
$classes = " inline-block $classes";
|
||||
$matched = false;
|
||||
|
||||
//Look for existing class
|
||||
$svg = preg_replace_callback('/^<svg.*?(class=\"(.*?)").*>/', function($matches) use ($classes, &$matched) {
|
||||
if (isset($matches[2])) {
|
||||
$new_classes = $matches[2] . $classes;
|
||||
$matched = true;
|
||||
return str_replace($matches[1], "class=\"$new_classes\"", $matches[0]);
|
||||
}
|
||||
}, $svg
|
||||
);
|
||||
|
||||
// no matches found just add the class
|
||||
if (!$matched) {
|
||||
$classes = trim($classes);
|
||||
$svg = str_replace('<svg ', "<svg class=\"$classes\" ", $svg);
|
||||
}
|
||||
|
||||
return $svg;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ class Uri
|
||||
|
||||
$this->url = $this->base . $this->uri;
|
||||
|
||||
$uri = str_replace(static::filterPath($this->root), '', $this->url);
|
||||
$uri = Utils::replaceFirstOccurrence(static::filterPath($this->root), '', $this->url);
|
||||
|
||||
// remove the setup.php based base if set:
|
||||
$setup_base = $grav['pages']->base();
|
||||
@@ -195,7 +195,7 @@ class Uri
|
||||
// set the new url
|
||||
$this->url = $this->root . $path;
|
||||
$this->path = static::cleanPath($path);
|
||||
$this->content_path = trim(str_replace($this->base, '', $this->path), '/');
|
||||
$this->content_path = trim(Utils::replaceFirstOccurrence($this->base, '', $this->path), '/');
|
||||
if ($this->content_path !== '') {
|
||||
$this->paths = explode('/', $this->content_path);
|
||||
}
|
||||
@@ -306,7 +306,7 @@ class Uri
|
||||
public function param($id)
|
||||
{
|
||||
if (isset($this->params[$id])) {
|
||||
return html_entity_decode(rawurldecode($this->params[$id]));
|
||||
return html_entity_decode(rawurldecode($this->params[$id]), ENT_COMPAT | ENT_HTML401, 'UTF-8');
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -340,7 +340,7 @@ class Uri
|
||||
return $this->url;
|
||||
}
|
||||
|
||||
$url = str_replace($this->base, '', rtrim($this->url, '/'));
|
||||
$url = Utils::replaceFirstOccurrence($this->base, '', rtrim($this->url, '/'));
|
||||
|
||||
return $url ?: '/';
|
||||
}
|
||||
@@ -489,7 +489,7 @@ class Uri
|
||||
return $this->uri;
|
||||
}
|
||||
|
||||
return str_replace($this->root_path, '', $this->uri);
|
||||
return Utils::replaceFirstOccurrence($this->root_path, '', $this->uri);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -531,7 +531,7 @@ class Uri
|
||||
return $this->root;
|
||||
}
|
||||
|
||||
return str_replace($this->base, '', $this->root);
|
||||
return Utils::replaceFirstOccurrence($this->base, '', $this->root);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -541,7 +541,9 @@ class Uri
|
||||
*/
|
||||
public function currentPage()
|
||||
{
|
||||
return $this->params['page'] ?? 1;
|
||||
$page = (int)($this->params['page'] ?? 1);
|
||||
|
||||
return max(1, $page);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -629,9 +631,9 @@ class Uri
|
||||
{
|
||||
if (getenv('HTTP_CLIENT_IP')) {
|
||||
$ip = getenv('HTTP_CLIENT_IP');
|
||||
} elseif (getenv('HTTP_X_FORWARDED_FOR')) {
|
||||
} elseif (getenv('HTTP_X_FORWARDED_FOR') && Grav::instance()['config']->get('system.http_x_forwarded.ip')) {
|
||||
$ip = getenv('HTTP_X_FORWARDED_FOR');
|
||||
} elseif (getenv('HTTP_X_FORWARDED')) {
|
||||
} elseif (getenv('HTTP_X_FORWARDED') && Grav::instance()['config']->get('system.http_x_forwarded.ip')) {
|
||||
$ip = getenv('HTTP_X_FORWARDED');
|
||||
} elseif (getenv('HTTP_FORWARDED_FOR')) {
|
||||
$ip = getenv('HTTP_FORWARDED_FOR');
|
||||
@@ -783,7 +785,7 @@ class Uri
|
||||
}
|
||||
|
||||
// special check to see if path checking is required.
|
||||
$just_path = str_replace($normalized_url, '', $normalized_path);
|
||||
$just_path = Utils::replaceFirstOccurrence($normalized_url, '', $normalized_path);
|
||||
if ($normalized_url === '/' || $just_path === $page->path()) {
|
||||
$url_path = $normalized_url;
|
||||
} else {
|
||||
@@ -852,7 +854,7 @@ class Uri
|
||||
}
|
||||
|
||||
// strip base from this path
|
||||
$target_path = str_replace($uri->rootUrl(), '', $target_path);
|
||||
$target_path = Utils::replaceFirstOccurrence($uri->rootUrl(), '', $target_path);
|
||||
|
||||
// set to / if root
|
||||
if (empty($target_path)) {
|
||||
@@ -877,7 +879,7 @@ class Uri
|
||||
|
||||
// Handle route only
|
||||
if ($route_only) {
|
||||
$url_path = str_replace(static::filterPath($base_url), '', $url_path);
|
||||
$url_path = Utils::replaceFirstOccurrence(static::filterPath($base_url), '', $url_path);
|
||||
}
|
||||
|
||||
// transform back to string/array as needed
|
||||
@@ -998,7 +1000,7 @@ class Uri
|
||||
}
|
||||
|
||||
// special check to see if path checking is required.
|
||||
$just_path = str_replace($normalized_url, '', $normalized_path);
|
||||
$just_path = Utils::replaceFirstOccurrence($normalized_url, '', $normalized_path);
|
||||
if ($just_path === $page->path()) {
|
||||
return $normalized_url;
|
||||
}
|
||||
@@ -1148,7 +1150,7 @@ class Uri
|
||||
protected function createFromEnvironment(array $env)
|
||||
{
|
||||
// Build scheme.
|
||||
if (isset($env['HTTP_X_FORWARDED_PROTO'])) {
|
||||
if (isset($env['HTTP_X_FORWARDED_PROTO']) && Grav::instance()['config']->get('system.http_x_forwarded.protocol')) {
|
||||
$this->scheme = $env['HTTP_X_FORWARDED_PROTO'];
|
||||
} elseif (isset($env['X-FORWARDED-PROTO'])) {
|
||||
$this->scheme = $env['X-FORWARDED-PROTO'];
|
||||
@@ -1166,11 +1168,14 @@ class Uri
|
||||
$this->password = $env['PHP_AUTH_PW'] ?? null;
|
||||
|
||||
// Build host.
|
||||
$hostname = 'localhost';
|
||||
if (isset($env['HTTP_HOST'])) {
|
||||
if (isset($env['HTTP_X_FORWARDED_HOST']) && Grav::instance()['config']->get('system.http_x_forwarded.host')) {
|
||||
$hostname = $env['HTTP_X_FORWARDED_HOST'];
|
||||
} else if (isset($env['HTTP_HOST'])) {
|
||||
$hostname = $env['HTTP_HOST'];
|
||||
} elseif (isset($env['SERVER_NAME'])) {
|
||||
$hostname = $env['SERVER_NAME'];
|
||||
} else {
|
||||
$hostname = 'localhost';
|
||||
}
|
||||
// Remove port from HTTP_HOST generated $hostname
|
||||
$hostname = Utils::substrToString($hostname, ':');
|
||||
@@ -1178,7 +1183,7 @@ class Uri
|
||||
$this->host = $this->validateHostname($hostname) ? $hostname : 'unknown';
|
||||
|
||||
// Build port.
|
||||
if (isset($env['HTTP_X_FORWARDED_PORT'])) {
|
||||
if (isset($env['HTTP_X_FORWARDED_PORT']) && Grav::instance()['config']->get('system.http_x_forwarded.port')) {
|
||||
$this->port = (int)$env['HTTP_X_FORWARDED_PORT'];
|
||||
} elseif (isset($env['X-FORWARDED-PORT'])) {
|
||||
$this->port = (int)$env['X-FORWARDED-PORT'];
|
||||
|
||||
@@ -129,6 +129,28 @@ abstract class Utils
|
||||
return rtrim($uri->rootUrl($domain), '/') . '/' . ($resource ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to find the full path to a file, be it a stream, a relative path, or
|
||||
* already a full path
|
||||
*
|
||||
* @param $path
|
||||
* @return string
|
||||
*/
|
||||
public static function fullPath($path)
|
||||
{
|
||||
$locator = Grav::instance()['locator'];
|
||||
|
||||
if ($locator->isStream($path)) {
|
||||
$path = $locator->findResource($path, true);
|
||||
} elseif (!Utils::startsWith($path, GRAV_ROOT)) {
|
||||
$base_url = Grav::instance()['base_url'];
|
||||
$path = GRAV_ROOT . '/' . ltrim(Utils::replaceFirstOccurrence($base_url, '', $path), '/');
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if the $haystack string starts with the substring $needle
|
||||
*
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
namespace Grav\Console;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Language\Language;
|
||||
use Grav\Common\Page\Page;
|
||||
use Grav\Common\Processors\InitializeProcessor;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
@@ -18,6 +22,13 @@ class ConsoleCommand extends Command
|
||||
{
|
||||
use ConsoleTrait;
|
||||
|
||||
/** @var bool */
|
||||
private $plugins_initialized = false;
|
||||
/** @var bool */
|
||||
private $themes_initialized = false;
|
||||
/** @var bool */
|
||||
private $pages_initialized = false;
|
||||
|
||||
/**
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
@@ -31,12 +42,140 @@ class ConsoleCommand extends Command
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Override with your implementation.
|
||||
*/
|
||||
protected function serve()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize Grav.
|
||||
*
|
||||
* - Load configuration
|
||||
* - Disable debugger
|
||||
* - Set timezone, locale
|
||||
* - Load plugins
|
||||
* - Set Users type to be used in the site
|
||||
*
|
||||
* Safe to be called multiple times.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
final protected function initializeGrav()
|
||||
{
|
||||
InitializeProcessor::initializeCli(Grav::instance());
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set language to be used in CLI.
|
||||
*
|
||||
* @param string|null $code
|
||||
*/
|
||||
final protected function setLanguage(string $code = null)
|
||||
{
|
||||
$this->initializeGrav();
|
||||
|
||||
$grav = Grav::instance();
|
||||
/** @var Language $language */
|
||||
$language = $grav['language'];
|
||||
if ($language->enabled()) {
|
||||
if ($code && $language->validate($code)) {
|
||||
$language->setActive($code);
|
||||
} else {
|
||||
$language->setActive($language->getDefault());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Properly initialize plugins.
|
||||
*
|
||||
* - call $this->initializeGrav()
|
||||
* - call onPluginsInitialized event
|
||||
*
|
||||
* Safe to be called multiple times.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
final protected function initializePlugins()
|
||||
{
|
||||
if (!$this->plugins_initialized) {
|
||||
$this->plugins_initialized = true;
|
||||
|
||||
$this->initializeGrav();
|
||||
|
||||
// Initialize plugins.
|
||||
$grav = Grav::instance();
|
||||
$grav->fireEvent('onPluginsInitialized');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Properly initialize themes.
|
||||
*
|
||||
* - call $this->initializePlugins()
|
||||
* - initialize theme (call onThemeInitialized event)
|
||||
*
|
||||
* Safe to be called multiple times.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
final protected function initializeThemes()
|
||||
{
|
||||
if (!$this->themes_initialized) {
|
||||
$this->themes_initialized = true;
|
||||
|
||||
$this->initializePlugins();
|
||||
|
||||
// Initialize themes.
|
||||
$grav = Grav::instance();
|
||||
$grav['themes']->init();
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Properly initialize pages.
|
||||
*
|
||||
* - call $this->initializeThemes()
|
||||
* - initialize assets (call onAssetsInitialized event)
|
||||
* - initialize twig (calls the twig events)
|
||||
* - initialize pages (calls onPagesInitialized event)
|
||||
*
|
||||
* Safe to be called multiple times.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
final protected function initializePages()
|
||||
{
|
||||
if (!$this->pages_initialized) {
|
||||
$this->pages_initialized = true;
|
||||
|
||||
$this->initializeThemes();
|
||||
|
||||
$grav = Grav::instance();
|
||||
|
||||
// Initialize assets.
|
||||
$grav['assets']->init();
|
||||
$grav->fireEvent('onAssetsInitialized');
|
||||
|
||||
// Initialize twig.
|
||||
$grav['twig']->init();
|
||||
|
||||
// Initialize pages.
|
||||
$pages = $grav['pages'];
|
||||
$pages->init();
|
||||
$grav->fireEvent('onPagesInitialized', new Event(['pages' => $pages]));
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function displayGPMRelease()
|
||||
{
|
||||
$this->output->writeln('');
|
||||
|
||||
@@ -177,7 +177,7 @@ class SelfupgradeCommand extends ConsoleCommand
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Package $package
|
||||
* @param array $package
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
|
||||
@@ -92,7 +92,7 @@ class CsvFormatter extends AbstractFormatter
|
||||
|
||||
if ($null_replace) {
|
||||
array_walk($csv_line, function(&$el) use ($null_replace) {
|
||||
$el = str_replace($null_replace, null, $el);
|
||||
$el = str_replace($null_replace, "\0", $el);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,532 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Framework\Parsedown
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Parsedown;
|
||||
|
||||
/*
|
||||
* Parsedown Extra
|
||||
* http://parsedown.org
|
||||
*
|
||||
* (c) Emanuil Rusev
|
||||
* http://erusev.com
|
||||
*
|
||||
* This file ported from officiall ParsedownExtra repo and kept for compatibility.
|
||||
*/
|
||||
|
||||
class ParsedownExtra extends Parsedown
|
||||
{
|
||||
# ~
|
||||
|
||||
const version = '0.7.0';
|
||||
|
||||
# ~
|
||||
|
||||
function __construct()
|
||||
{
|
||||
if (parent::version < '1.5.0')
|
||||
{
|
||||
throw new Exception('ParsedownExtra requires a later version of Parsedown');
|
||||
}
|
||||
|
||||
$this->BlockTypes[':'] []= 'DefinitionList';
|
||||
$this->BlockTypes['*'] []= 'Abbreviation';
|
||||
|
||||
# identify footnote definitions before reference definitions
|
||||
array_unshift($this->BlockTypes['['], 'Footnote');
|
||||
|
||||
# identify footnote markers before before links
|
||||
array_unshift($this->InlineTypes['['], 'FootnoteMarker');
|
||||
}
|
||||
|
||||
#
|
||||
# ~
|
||||
|
||||
function text($text)
|
||||
{
|
||||
$markup = parent::text($text);
|
||||
|
||||
# merge consecutive dl elements
|
||||
|
||||
$markup = preg_replace('/<\/dl>\s+<dl>\s+/', '', $markup);
|
||||
|
||||
# add footnotes
|
||||
|
||||
if (isset($this->DefinitionData['Footnote']))
|
||||
{
|
||||
$Element = $this->buildFootnoteElement();
|
||||
|
||||
$markup .= "\n" . $this->element($Element);
|
||||
}
|
||||
|
||||
return $markup;
|
||||
}
|
||||
|
||||
#
|
||||
# Blocks
|
||||
#
|
||||
|
||||
#
|
||||
# Abbreviation
|
||||
|
||||
protected function blockAbbreviation($Line)
|
||||
{
|
||||
if (preg_match('/^\*\[(.+?)\]:[ ]*(.+?)[ ]*$/', $Line['text'], $matches))
|
||||
{
|
||||
$this->DefinitionData['Abbreviation'][$matches[1]] = $matches[2];
|
||||
|
||||
$Block = array(
|
||||
'hidden' => true,
|
||||
);
|
||||
|
||||
return $Block;
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Footnote
|
||||
|
||||
protected function blockFootnote($Line)
|
||||
{
|
||||
if (preg_match('/^\[\^(.+?)\]:[ ]?(.*)$/', $Line['text'], $matches))
|
||||
{
|
||||
$Block = array(
|
||||
'label' => $matches[1],
|
||||
'text' => $matches[2],
|
||||
'hidden' => true,
|
||||
);
|
||||
|
||||
return $Block;
|
||||
}
|
||||
}
|
||||
|
||||
protected function blockFootnoteContinue($Line, $Block)
|
||||
{
|
||||
if ($Line['text'][0] === '[' and preg_match('/^\[\^(.+?)\]:/', $Line['text']))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($Block['interrupted']))
|
||||
{
|
||||
if ($Line['indent'] >= 4)
|
||||
{
|
||||
$Block['text'] .= "\n\n" . $Line['text'];
|
||||
|
||||
return $Block;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$Block['text'] .= "\n" . $Line['text'];
|
||||
|
||||
return $Block;
|
||||
}
|
||||
}
|
||||
|
||||
protected function blockFootnoteComplete($Block)
|
||||
{
|
||||
$this->DefinitionData['Footnote'][$Block['label']] = array(
|
||||
'text' => $Block['text'],
|
||||
'count' => null,
|
||||
'number' => null,
|
||||
);
|
||||
|
||||
return $Block;
|
||||
}
|
||||
|
||||
#
|
||||
# Definition List
|
||||
|
||||
protected function blockDefinitionList($Line, $Block)
|
||||
{
|
||||
if ( ! isset($Block) or isset($Block['type']))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$Element = array(
|
||||
'name' => 'dl',
|
||||
'handler' => 'elements',
|
||||
'text' => array(),
|
||||
);
|
||||
|
||||
$terms = explode("\n", $Block['element']['text']);
|
||||
|
||||
foreach ($terms as $term)
|
||||
{
|
||||
$Element['text'] []= array(
|
||||
'name' => 'dt',
|
||||
'handler' => 'line',
|
||||
'text' => $term,
|
||||
);
|
||||
}
|
||||
|
||||
$Block['element'] = $Element;
|
||||
|
||||
$Block = $this->addDdElement($Line, $Block);
|
||||
|
||||
return $Block;
|
||||
}
|
||||
|
||||
protected function blockDefinitionListContinue($Line, array $Block)
|
||||
{
|
||||
if ($Line['text'][0] === ':')
|
||||
{
|
||||
$Block = $this->addDdElement($Line, $Block);
|
||||
|
||||
return $Block;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isset($Block['interrupted']) and $Line['indent'] === 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($Block['interrupted']))
|
||||
{
|
||||
$Block['dd']['handler'] = 'text';
|
||||
$Block['dd']['text'] .= "\n\n";
|
||||
|
||||
unset($Block['interrupted']);
|
||||
}
|
||||
|
||||
$text = substr($Line['body'], min($Line['indent'], 4));
|
||||
|
||||
$Block['dd']['text'] .= "\n" . $text;
|
||||
|
||||
return $Block;
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Header
|
||||
|
||||
protected function blockHeader($Line)
|
||||
{
|
||||
$Block = parent::blockHeader($Line);
|
||||
|
||||
if ($Block !== null && preg_match('/[ #]*{('.$this->regexAttribute.'+)}[ ]*$/', $Block['element']['text'], $matches, PREG_OFFSET_CAPTURE))
|
||||
{
|
||||
$attributeString = $matches[1][0];
|
||||
|
||||
$Block['element']['attributes'] = $this->parseAttributeData($attributeString);
|
||||
|
||||
$Block['element']['text'] = substr($Block['element']['text'], 0, $matches[0][1]);
|
||||
}
|
||||
|
||||
return $Block;
|
||||
}
|
||||
|
||||
#
|
||||
# Markup
|
||||
|
||||
protected function blockMarkupComplete($Block)
|
||||
{
|
||||
if ( ! isset($Block['void']))
|
||||
{
|
||||
$Block['markup'] = $this->processTag($Block['markup']);
|
||||
}
|
||||
|
||||
return $Block;
|
||||
}
|
||||
|
||||
#
|
||||
# Setext
|
||||
|
||||
protected function blockSetextHeader($Line, array $Block = null)
|
||||
{
|
||||
$Block = parent::blockSetextHeader($Line, $Block);
|
||||
|
||||
if ($Block !== null && preg_match('/[ ]*{('.$this->regexAttribute.'+)}[ ]*$/', $Block['element']['text'], $matches, PREG_OFFSET_CAPTURE))
|
||||
{
|
||||
$attributeString = $matches[1][0];
|
||||
|
||||
$Block['element']['attributes'] = $this->parseAttributeData($attributeString);
|
||||
|
||||
$Block['element']['text'] = substr($Block['element']['text'], 0, $matches[0][1]);
|
||||
}
|
||||
|
||||
return $Block;
|
||||
}
|
||||
|
||||
#
|
||||
# Inline Elements
|
||||
#
|
||||
|
||||
#
|
||||
# Footnote Marker
|
||||
|
||||
protected function inlineFootnoteMarker($Excerpt)
|
||||
{
|
||||
if (preg_match('/^\[\^(.+?)\]/', $Excerpt['text'], $matches))
|
||||
{
|
||||
$name = $matches[1];
|
||||
|
||||
if ( ! isset($this->DefinitionData['Footnote'][$name]))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$this->DefinitionData['Footnote'][$name]['count'] ++;
|
||||
|
||||
if ( ! isset($this->DefinitionData['Footnote'][$name]['number']))
|
||||
{
|
||||
$this->DefinitionData['Footnote'][$name]['number'] = ++ $this->footnoteCount; # » &
|
||||
}
|
||||
|
||||
$Element = array(
|
||||
'name' => 'sup',
|
||||
'attributes' => array('id' => 'fnref'.$this->DefinitionData['Footnote'][$name]['count'].':'.$name),
|
||||
'handler' => 'element',
|
||||
'text' => array(
|
||||
'name' => 'a',
|
||||
'attributes' => array('href' => '#fn:'.$name, 'class' => 'footnote-ref'),
|
||||
'text' => $this->DefinitionData['Footnote'][$name]['number'],
|
||||
),
|
||||
);
|
||||
|
||||
return array(
|
||||
'extent' => strlen($matches[0]),
|
||||
'element' => $Element,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private $footnoteCount = 0;
|
||||
|
||||
#
|
||||
# Link
|
||||
|
||||
protected function inlineLink($Excerpt)
|
||||
{
|
||||
$Link = parent::inlineLink($Excerpt);
|
||||
|
||||
$remainder = $Link !== null ? substr($Excerpt['text'], $Link['extent']) : '';
|
||||
|
||||
if (preg_match('/^[ ]*{('.$this->regexAttribute.'+)}/', $remainder, $matches))
|
||||
{
|
||||
$Link['element']['attributes'] += $this->parseAttributeData($matches[1]);
|
||||
|
||||
$Link['extent'] += strlen($matches[0]);
|
||||
}
|
||||
|
||||
return $Link;
|
||||
}
|
||||
|
||||
#
|
||||
# ~
|
||||
#
|
||||
|
||||
protected function unmarkedText($text)
|
||||
{
|
||||
$text = parent::unmarkedText($text);
|
||||
|
||||
if (isset($this->DefinitionData['Abbreviation']))
|
||||
{
|
||||
foreach ($this->DefinitionData['Abbreviation'] as $abbreviation => $meaning)
|
||||
{
|
||||
$pattern = '/\b'.preg_quote($abbreviation, '/').'\b/';
|
||||
|
||||
$text = preg_replace($pattern, '<abbr title="'.$meaning.'">'.$abbreviation.'</abbr>', $text);
|
||||
}
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
#
|
||||
# Util Methods
|
||||
#
|
||||
|
||||
protected function addDdElement(array $Line, array $Block)
|
||||
{
|
||||
$text = substr($Line['text'], 1);
|
||||
$text = trim($text);
|
||||
|
||||
unset($Block['dd']);
|
||||
|
||||
$Block['dd'] = array(
|
||||
'name' => 'dd',
|
||||
'handler' => 'line',
|
||||
'text' => $text,
|
||||
);
|
||||
|
||||
if (isset($Block['interrupted']))
|
||||
{
|
||||
$Block['dd']['handler'] = 'text';
|
||||
|
||||
unset($Block['interrupted']);
|
||||
}
|
||||
|
||||
$Block['element']['text'] []= & $Block['dd'];
|
||||
|
||||
return $Block;
|
||||
}
|
||||
|
||||
protected function buildFootnoteElement()
|
||||
{
|
||||
$Element = array(
|
||||
'name' => 'div',
|
||||
'attributes' => array('class' => 'footnotes'),
|
||||
'handler' => 'elements',
|
||||
'text' => array(
|
||||
array(
|
||||
'name' => 'hr',
|
||||
),
|
||||
array(
|
||||
'name' => 'ol',
|
||||
'handler' => 'elements',
|
||||
'text' => array(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
uasort($this->DefinitionData['Footnote'], 'self::sortFootnotes');
|
||||
|
||||
foreach ($this->DefinitionData['Footnote'] as $definitionId => $DefinitionData)
|
||||
{
|
||||
if ( ! isset($DefinitionData['number']))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$text = $DefinitionData['text'];
|
||||
|
||||
$text = parent::text($text);
|
||||
|
||||
$numbers = range(1, $DefinitionData['count']);
|
||||
|
||||
$backLinksMarkup = '';
|
||||
|
||||
foreach ($numbers as $number)
|
||||
{
|
||||
$backLinksMarkup .= ' <a href="#fnref'.$number.':'.$definitionId.'" rev="footnote" class="footnote-backref">↩</a>';
|
||||
}
|
||||
|
||||
$backLinksMarkup = substr($backLinksMarkup, 1);
|
||||
|
||||
if (substr($text, - 4) === '</p>')
|
||||
{
|
||||
$backLinksMarkup = ' '.$backLinksMarkup;
|
||||
|
||||
$text = substr_replace($text, $backLinksMarkup.'</p>', - 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
$text .= "\n".'<p>'.$backLinksMarkup.'</p>';
|
||||
}
|
||||
|
||||
$Element['text'][1]['text'] []= array(
|
||||
'name' => 'li',
|
||||
'attributes' => array('id' => 'fn:'.$definitionId),
|
||||
'text' => "\n".$text."\n",
|
||||
);
|
||||
}
|
||||
|
||||
return $Element;
|
||||
}
|
||||
|
||||
# ~
|
||||
|
||||
protected function parseAttributeData($attributeString)
|
||||
{
|
||||
$Data = array();
|
||||
|
||||
$attributes = preg_split('/[ ]+/', $attributeString, - 1, PREG_SPLIT_NO_EMPTY);
|
||||
|
||||
foreach ($attributes as $attribute)
|
||||
{
|
||||
if ($attribute[0] === '#')
|
||||
{
|
||||
$Data['id'] = substr($attribute, 1);
|
||||
}
|
||||
else # "."
|
||||
{
|
||||
$classes []= substr($attribute, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($classes))
|
||||
{
|
||||
$Data['class'] = implode(' ', $classes);
|
||||
}
|
||||
|
||||
return $Data;
|
||||
}
|
||||
|
||||
# ~
|
||||
|
||||
protected function processTag($elementMarkup) # recursive
|
||||
{
|
||||
# http://stackoverflow.com/q/1148928/200145
|
||||
libxml_use_internal_errors(true);
|
||||
|
||||
$DOMDocument = new \DOMDocument;
|
||||
|
||||
# http://stackoverflow.com/q/11309194/200145
|
||||
$elementMarkup = mb_convert_encoding($elementMarkup, 'HTML-ENTITIES', 'UTF-8');
|
||||
|
||||
# http://stackoverflow.com/q/4879946/200145
|
||||
$DOMDocument->loadHTML($elementMarkup);
|
||||
$DOMDocument->removeChild($DOMDocument->doctype);
|
||||
$DOMDocument->replaceChild($DOMDocument->firstChild->firstChild->firstChild, $DOMDocument->firstChild);
|
||||
|
||||
$elementText = '';
|
||||
|
||||
if ($DOMDocument->documentElement->getAttribute('markdown') === '1')
|
||||
{
|
||||
foreach ($DOMDocument->documentElement->childNodes as $Node)
|
||||
{
|
||||
$elementText .= $DOMDocument->saveHTML($Node);
|
||||
}
|
||||
|
||||
$DOMDocument->documentElement->removeAttribute('markdown');
|
||||
|
||||
$elementText = "\n".$this->text($elementText)."\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach ($DOMDocument->documentElement->childNodes as $Node)
|
||||
{
|
||||
$nodeMarkup = $DOMDocument->saveHTML($Node);
|
||||
|
||||
if ($Node instanceof \DOMElement and ! in_array($Node->nodeName, $this->textLevelElements))
|
||||
{
|
||||
$elementText .= $this->processTag($nodeMarkup);
|
||||
}
|
||||
else
|
||||
{
|
||||
$elementText .= $nodeMarkup;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# because we don't want for markup to get encoded
|
||||
$DOMDocument->documentElement->nodeValue = 'placeholder\x1A';
|
||||
|
||||
$markup = $DOMDocument->saveHTML($DOMDocument->documentElement);
|
||||
$markup = str_replace('placeholder\x1A', $elementText, $markup);
|
||||
|
||||
return $markup;
|
||||
}
|
||||
|
||||
# ~
|
||||
|
||||
protected function sortFootnotes($A, $B) # callback
|
||||
{
|
||||
return $A['number'] - $B['number'];
|
||||
}
|
||||
|
||||
#
|
||||
# Fields
|
||||
#
|
||||
|
||||
protected $regexAttribute = '(?:[#.][-\w]+[ ]*)';
|
||||
}
|
||||
@@ -286,7 +286,7 @@ class Route
|
||||
$url .= '?' . $this->getUriQuery();
|
||||
}
|
||||
|
||||
return $url;
|
||||
return rtrim($url,'/');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -344,9 +344,8 @@ class Route
|
||||
$parts[] = $this->language;
|
||||
}
|
||||
|
||||
if ($this->route !== '') {
|
||||
$parts[] = $this->extension ? $this->route . '.' . $this->extension : $this->route;
|
||||
}
|
||||
$parts[] = $this->extension ? $this->route . '.' . $this->extension : $this->route;
|
||||
|
||||
|
||||
if ($this->gravParams) {
|
||||
$parts[] = RouteFactory::buildParams($this->gravParams);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
namespace Grav\Framework\Session;
|
||||
|
||||
use Grav\Common\User\Interfaces\UserInterface;
|
||||
use Grav\Framework\Session\Exceptions\SessionException;
|
||||
|
||||
/**
|
||||
@@ -17,16 +18,13 @@ use Grav\Framework\Session\Exceptions\SessionException;
|
||||
*/
|
||||
class Session implements SessionInterface
|
||||
{
|
||||
protected $options;
|
||||
/** @var array */
|
||||
protected $options = [];
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
/** @var bool */
|
||||
protected $started = false;
|
||||
|
||||
/**
|
||||
* @var Session
|
||||
*/
|
||||
/** @var Session */
|
||||
protected static $instance;
|
||||
|
||||
/**
|
||||
@@ -178,9 +176,13 @@ class Session implements SessionInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
$sessionName = session_name();
|
||||
$sessionExists = isset($_COOKIE[$sessionName]);
|
||||
|
||||
// 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()]);
|
||||
if ($sessionExists && !preg_match('/^[-,a-zA-Z0-9]{1,128}$/', $_COOKIE[$sessionName])) {
|
||||
unset($_COOKIE[$sessionName]);
|
||||
$sessionExists = false;
|
||||
}
|
||||
|
||||
$options = $this->options;
|
||||
@@ -197,24 +199,28 @@ class Session implements SessionInterface
|
||||
throw new SessionException('Failed to start session: ' . $error, 500);
|
||||
}
|
||||
|
||||
if ($user && !$user->isValid()) {
|
||||
$this->clear();
|
||||
throw new SessionException('User Invalid', 500);
|
||||
$this->started = true;
|
||||
|
||||
if ($user && (!$user instanceof UserInterface || !$user->isValid())) {
|
||||
$this->invalidate();
|
||||
|
||||
throw new SessionException('Invalid User object, session destroyed.', 500);
|
||||
}
|
||||
|
||||
$params = session_get_cookie_params();
|
||||
// Extend the lifetime of the session.
|
||||
if ($sessionExists) {
|
||||
$params = session_get_cookie_params();
|
||||
|
||||
setcookie(
|
||||
session_name(),
|
||||
session_id(),
|
||||
time() + $params['lifetime'],
|
||||
$params['path'],
|
||||
$params['domain'],
|
||||
$params['secure'],
|
||||
$params['httponly']
|
||||
);
|
||||
|
||||
$this->started = true;
|
||||
setcookie(
|
||||
$sessionName,
|
||||
session_id(),
|
||||
time() + $params['lifetime'],
|
||||
$params['path'],
|
||||
$params['domain'],
|
||||
$params['secure'],
|
||||
$params['httponly']
|
||||
);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
enabled: false
|
||||
built_in_css: true
|
||||
name: 'Santa Claus'
|
||||
title: 'Present Giver'
|
||||
show_title: true
|
||||
description: 'Santa Claus, Saint Nicholas, Saint Nick, Father Christmas, Kris Kringle, Santy, or simply Santa is a figure with legendary, historical and folkloric origins who, in many Western cultures, is said to bring gifts to the homes of good children on 24 December, the night before Christmas Day. The modern figure of Santa Claus is derived from the British figure of Father Christmas, the Dutch figure of Sinterklaas, and Saint Nicholas, the historical Greek bishop and gift-giver of Myra. During the Christianization of Germanic Europe, this figure may also have absorbed elements of the god Odin, who was associated with the Germanic pagan midwinter event of Yule and led the Wild Hunt, a ghostly procession through the sky'
|
||||
picture_src:
|
||||
user/plugins/aboutme/assets/avatars/santa.jpg:
|
||||
name: santa.jpg
|
||||
type: image/jpeg
|
||||
size: 43391
|
||||
path: user/plugins/aboutme/assets/avatars/santa.jpg
|
||||
gravatar:
|
||||
enabled: false
|
||||
email: example@test.com
|
||||
size: 100
|
||||
social_pages:
|
||||
enabled: true
|
||||
use_font_awesome: false
|
||||
pages:
|
||||
facebook:
|
||||
icon_type: b
|
||||
icon: facebook
|
||||
title: Facebook
|
||||
position: 1
|
||||
twitter:
|
||||
icon_type: b
|
||||
icon: twitter
|
||||
title: Twitter
|
||||
position: 2
|
||||
github:
|
||||
icon_type: b
|
||||
icon: github
|
||||
title: GitHub
|
||||
position: 3
|
||||
linkedin:
|
||||
icon_type: b
|
||||
icon: linkedin
|
||||
title: LinkedIn
|
||||
position: 4
|
||||
instagram:
|
||||
icon_type: b
|
||||
icon: instagram
|
||||
title: Instagram
|
||||
position: 5
|
||||
@@ -0,0 +1,2 @@
|
||||
enabled: true
|
||||
show_samples: true
|
||||
@@ -1,13 +1,15 @@
|
||||
enabled: true
|
||||
built_in_css: true
|
||||
built_in_js: true
|
||||
display_button: true
|
||||
min_query_length: 3
|
||||
enabled: '1'
|
||||
built_in_css: '1'
|
||||
built_in_js: '1'
|
||||
display_button: '1'
|
||||
min_query_length: '3'
|
||||
route: /search
|
||||
search_content: raw
|
||||
template: simplesearch_results
|
||||
filters:
|
||||
category: ''
|
||||
filter_combinator: or
|
||||
ignore_accented_characters: false
|
||||
ignore_accented_characters: '0'
|
||||
order:
|
||||
by: title
|
||||
dir: desc
|
||||
dir: asc
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
enabled: true
|
||||
route: /sitemap
|
||||
changefreq: daily
|
||||
priority: !!float 1
|
||||
ignores: null
|
||||
@@ -1,5 +1,5 @@
|
||||
absolute_urls: false
|
||||
timezone: Europe/London
|
||||
timezone: Europe/Paris
|
||||
param_sep: ':'
|
||||
wrapped_site: false
|
||||
reverse_proxy_setup: false
|
||||
@@ -9,15 +9,13 @@ username_regex: '^[a-z0-9_-]{3,16}$'
|
||||
pwd_regex: '(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}'
|
||||
intl_enabled: true
|
||||
languages:
|
||||
supported:
|
||||
- en
|
||||
default_lang: en
|
||||
default_lang: fr
|
||||
include_default_lang: false
|
||||
pages_fallback_only: false
|
||||
translations: false
|
||||
translations_fallback: false
|
||||
session_store_active: false
|
||||
http_accept_language: true
|
||||
http_accept_language: false
|
||||
override_locale: false
|
||||
home:
|
||||
alias: /home
|
||||
|
||||
@@ -4,7 +4,7 @@ date: '08-08-2018 18:04'
|
||||
date_end: '24-08-2018 18:05'
|
||||
lieux: '93 Baker Street'
|
||||
artistes: ' Olivia Bax'
|
||||
title: 'at large, '
|
||||
title: '‘at large'', '
|
||||
media_order: 'Olivia Bax, Boulder (with handle), 2015.jpg,Detail of Olivia Bax, Hot Spot, 2018.jpg,Olivia Bax, Love Handles, 2017.jpg,Olivia Bax, Pump, 2016.jpg,Olivia Bax, Slot _ Groove, 2015.jpg,Olivia Bax, Rumble, 2018.jpg,Installation Olivia Bax.jpg,Detail ''Footloose'', 2017.jpg,Olivia Bax, Footloose, 2017.jpg'
|
||||
show_sidebar: false
|
||||
aura:
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
date: '22-03-2019 16:06'
|
||||
date_end: '03-05-2019 12:38'
|
||||
lieux: '12th Floor'
|
||||
title: 'Cicatrices, '
|
||||
artistes: ' Muriel Abadie, Adriano Amaral, Jean-marie Appriou, Srijon Chowdhury, Isaac Lythgoe, John Miserendino, Maïa Régis, Cajsa von Zeipel'
|
||||
title: '‘Cicatrices'', '
|
||||
media_order: 'Installation Isaac Lythgoe and Muriel Abadie.jpg,Jean-Marie Appriou, Shrimp, 2018.jpg,Muriel Abadie, Masked Monkey, 2018.jpg,John Miserendino, Mamie and Marco, 2018.jpg,Adriano Amaral, Untitled, 2018.jpg,John Miserendino, L’origine du monde, 2018.jpg,Isaac Lythgoe, grip the wheel, look straight ahead, 2016.jpg,Details of Adriano Amaral, Untitled, 2018.jpg,Adriano Amaral, Untitled, 2018(2).jpg,Casja von Zeipel ''When?'', 2019.jpg,Adriano Amaral, untitled, 2018, Maïa Regis, Ramon Reyna, 2018.jpg,Details of Cajsa von Zeipel, When? and Why?, 2019.jpg,Cajsa von Zeipel, When? and Why?, 2019.jpg,Adriano Amaral, Untitled, 2018.jpg'
|
||||
show_sidebar: false
|
||||
aura:
|
||||
|
||||
@@ -4,7 +4,7 @@ date: '24-10-2019 17:21'
|
||||
date_end: '22-11-2019 17:21'
|
||||
lieux: '12th floor'
|
||||
artistes: ' Dillwyn Smith'
|
||||
title: 'Light Cages, '
|
||||
title: '‘Light Cages'', '
|
||||
media_order: 'Installation 1 ''Light Cages'', Dillwyn Smith.jpg,Dillwyn Smith, ''Turning Poison into Medicine V'', 2018.jpg,Dillwyn Smith, ''Full Transparency II'', 2019, and ''Light Cage'', 2018.jpg,Dillwyn Smith, ''Sensitised Ground'', 2018.jpg,Installation 2 ''Light Cages'', Dillwyn Smith.jpg,Installation 3 ''Light Cages'', Dillwyn Smith.jpg,Dillwyn Smith, ''Full Transparency'', 2018.jpg,Dillwyn Smith, ''Hermit Song'', 2018.jpg,Dillwyn Smith, ''Working Class Hero'', 2018, and ''Praise'', 2018.jpg,Dillwyn Smith, ''Working Class Hero'', 2018.jpg'
|
||||
show_sidebar: false
|
||||
aura:
|
||||
|
||||
@@ -5,7 +5,7 @@ date_end: '20-12-2019 00:00'
|
||||
display_time: false
|
||||
lieux: '12th floor'
|
||||
artistes: 'Mania Akbari, Douglas White '
|
||||
title: '‘Mr. Majidi and the Electricity Box'' '
|
||||
title: '‘Mr. Majidi and the Electricity Box'','
|
||||
media_order: 'Installation Mania Akbari and Douglas White I.jpg,Geometric Resistance, Mania Akbari and Douglas White, 2019.jpg,Detail Mr. Majidi series, Mania Akbari and Douglas White, 2019 I.jpg,Detail Mr. Majidi series, Mania Akbari and Douglas White, 2019 II.jpg,Mr. Majidi series and House of Sin I, Mania Akbari and Douglas White, 2019.jpg,House of Sin I, Mania Akbari and Douglas White, 2019.jpg,House of Sin II, Mania Akbari and Douglas White, 2019.jpg,Installation Mania Akbari and Douglas White II.jpg,House of Sin III, Mania Akbari and Douglas White, 2019.jpg,Installation Mania Akbari and Douglas White III.jpg,Mr. Majidi series, Mania Akbari and Douglas White, 2019.jpg,Lubion, Mania Akbari and Douglas White, 2019 I.jpg,Lubion, Mania Akbari and Douglas White, 2019 II.jpg,Lubion box, Mania Akbari and Douglas White, 2019.jpg,Detail Lubion box, Mania Akbari and Douglas White, 2019.jpg'
|
||||
show_sidebar: false
|
||||
aura:
|
||||
|
||||
@@ -3,7 +3,7 @@ date: '05-09-2019 16:27'
|
||||
date_end: '11-10-2019 16:27'
|
||||
lieux: '12th floor'
|
||||
artistes: ' Nour El Saleh'
|
||||
title: 'Exquisite Farces, '
|
||||
title: '‘Exquisite Farces'', '
|
||||
media_order: '(Not so) Musical Chairs, 2018_ Wrong Side of the Table, 2019_ The Puppeteer of Karakas, 2019.jpeg,Wrong Side of the Table, 2019.jpg,Detail of Wrong Side of the Table, 2019.jpg,A Grand Jester, 2019.jpeg,Dance Monkey, Dance!, 2019.jpg,Inhabited Props in a Box, 2018.jpeg,Detail of Inhabited Props in a Box, 2018.jpg,Four Times as Big, Twice as Small, 2018.jpg,Detail of Four Times as Big, Twice as Small, 2018.jpg'
|
||||
show_sidebar: false
|
||||
aura:
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
date: '23-05-2019 16:33'
|
||||
date_end: '26-07-2019 16:33'
|
||||
lieux: Phillips
|
||||
title: 'Parfum d''épines,'
|
||||
artistes: ' PHILLIPS X VO CURATIONS'
|
||||
title: '‘Parfum d''épines'','
|
||||
media_order: 'Installation Daiga Grantina and Robert Brambora.jpg,Installation Jean-Marie Appriou and Claude Bellegarde.jpg,Daiga Grantina, Li Li, 2018 and Victor Man, Flowers with Skeleton and Bear Wrestling, 2012.jpg,Installation Jean-Marie Appriou, Muriel Abadie and Claude Bellegarde.jpg,Installation Oda Jaune, Pedro Wirz and Sonya Derviz.jpg,Robert Brambora, Untitled, 2019, Untitled, 2019, and 9 to 5 (2), 2018.jpg,Oda Jaune, Fleurs, 2015.jpg,Victor Man, Flowers with Skeleton and Bear Wrestling, 2012.jpg,Daiga Grantina, Yolk Bott, 2017.jpg'
|
||||
show_sidebar: false
|
||||
aura:
|
||||
@@ -11,3 +11,19 @@ aura:
|
||||
---
|
||||
|
||||
Phillips Auction House and V.O Curations are pleased to present a novel reading of Charles Baudelaire's collection of poems _Les Fleurs du Mal_ through the spectrum of contemporary art, exploring the seminal concept of the double postulation that has protruded beyond the literary universe of the French poet. The exhibition features the works of 9 contemporary artists that play upon the duality of the Baudelairian ethos and its fascination for antithetical pairings, the atemporal resonance of what the poet refers to as the "two inseparable abysses of the human soul". Parfum d'épines was supported by Parcours 2019 and Outset Contemporary Art Fund.
|
||||
|
||||
--
|
||||
|
||||
Charles Baudelaire a bâti son univers poétique dans l'atmosphère parisienne des années 1850, une période pendant laquelle la misère coexistait avec la plus grande des sophistications, où la ville de Paris a connu de drastiques transformations urbaines. Flânant le long des avenues Haussmanniennes éclairées aux becs de gaz, le poète regrette le passé. Pourtant c'est cette modernité émergente, son dynamisme et sa vulgarité qui l'ont conduit à développer une nouvelle forme de poésie. Fervent catholique, auteur de scandaleux blasphèmes qui lui ont valu d'être condamné et censuré, Charles Baudelaire a fait de la dualité son axiome fondamental, étant lui-même divisé entre la quête du Beau et l'inévitable abysse du péché. Puisant dans la tendresse et la haine, le parfum et les épines, Les Fleurs du Mal - recueil publié en 1857 - sont telle une prouesse de la Beauté inextricablement liée au Mal. La cohésion de ces polarités permet de faire sens dans des directions opposées sans pour autant les disjoindre. En considérant la paire antithétique comme un tout, Baudelaire forge la notion de double postulation, cœur de son univers littéraire. L'exposition Parfum d’épines cherche à en révéler l'écho dans le domaine de l'art contemporain.
|
||||
|
||||
Les sculptures abstraites et composites de Daiga Grantina ressemblent à des organes palpitants, des pans de chairs dont le plasma et les fluides vitaux auraient été drainés. Elles s’apparentent à une version plus asséchée des viscères du "ventre putride" de la Charogne. Tulle, coton, mousse et silicone : les tissus ondulés se déversent dans l'espace et déploient un univers de signification riche suggérant à la fois le végétal, le médical et le délicat. Ces plis voluptueux sont semblables à des amphores elles-mêmes ressemblant à un intestin. On entend résonner les vers de Baudelaire dans lequel le souvenir d'un cadavre devient une source de création esthétique: " et le ciel regardait la carcasse superbe comme une fleur s'épanouir ". De la même façon, le bouquet d'entrailles d'Oda Jaune rapproche le floral de l'intestinal. Les organes sanglants qu'elle représente sont plus réalistes que ceux de Daiga Grantina en ce qu'ils possèdent encore leurs fluides vitaux qui permettent au cadavre de fleurir chez Baudelaire, à une photosynthèse corporelle d'advenir. Aussi, après l'extraction de ces fleurs de sang, le bouquet de viscères, est maintenu en vie extra-corporellement. Sous le prisme baudelairien se laisse entrevoir tout un imaginaire de l'amour dévorant et des "amours décomposés".
|
||||
|
||||
Dans le bouquet de Victor Man, réminiscence des fleurs maladives du poète, la vie semble se cacher sous un masque mortuaire: les fleurs de chardon apparaissent fanées alors qu'elles sont en pleine floraison. Au lieu d'être parées de leur naturelle teinte améthyste, elles portent le gris. La fleur de chardon, utilisée comme un aphrodisiaque à l'époque Élisabéthaine, couplée avec le squelette, évoque les memento mori, ces peintures illustrant la locution latine " souviens toi que tu vas mourir ". Elles réunissent les symboles de deux pulsions majeures, à savoir l'Eros et le Thanatos, que Baudelaire convoque quand, dans Femmes Damnées (Delphine et Hippolyte), il trouve sur un sein "la fraîcheur des tombeaux !".
|
||||
Les profils aplatis de Robert Brambora ne privilégient pas le rendu sculptural du modèle mais plutôt leur être émotionnel. Ainsi, ce qui est normalement intime se trouve dévoilé, l'extérieur et l'intérieur se confondent en des paysages de soleils et de nuages. Si l'on établit une référence avec les Correspondances de Baudelaire, l'œuvre de Brambora nous fait voir le monde naturel comme "un temple où de vivants piliers laissent parfois sortir de confuses paroles". Dans ce monde, véritable " forêts de symboles ", l'humain est étroitement lié à la flore et à la faune.
|
||||
|
||||
La fascination de Pedro Wirz pour la métamorphose des amphibiens - de leur apparence à leur habitat, passant de l'œuf à la grenouille, de l'eau à la terre – imprègne sa production artistique. Marqué par les mythes et les rituels brésiliens auxquels il a été exposé dans l'enfance, l'artiste traite l'animal avec une dimension spirituelle et l'élève, peut-être, au rang d'alter ego. En plus de cette notion de dédoublement, on trouve également dans ses œuvres, le rapport d'inversion et le devenir autre explicité par la grenouille moulée en cire d'abeille.
|
||||
|
||||
En employant l'aluminium, Jean-Marie Appriou transforme un matériau industriel en matière organique, créant son propre microenvironnement ; cyprès, chauve-souris et serpent peuplent ce monde métallique. Un certain mysticisme se trouve entremêlé à une ténébreuse perception. Le cyprès symbolise à la fois la mort et l'aspiration ascétique et le serpent expulsé du Paradis nous rappelle l'indolente tentatrice du Serpent qui Danse. Après avoir été confiné aux enfers, une chauve-souris baudelairienne s'élance et surgit dans la lumière cherchant éperdument l'Espoir et sera finalement incapable de quitter sa grotte. Si la chauve-souris de Jean-Marie Appriou se bat pour s'élever, l'oiseau de Muriel Abadie choit vertigineusement comme l'Albatros de Baudelaire. À travers l'énergie conférée par la forme esquissée, Muriel Abadie a transposé le dynamisme de l'oiseau, sa liberté et sa mobilité à l'âme du poète qui mène à la fois une existence terrestre et céleste. Chutant vers le sol, peut-être même déjà retenu par lui, le vol est interrompu et la maison vide ressemble à une cage. Entre l'ascension et la chute, cygne ou albatros, le poète n'est jamais aussi éloigné des Hommes que quand il est parmi eux. La figure fantomatique de Sonya Derviz quant à elle, reflète la dualité de la condition humaine: multitude de facettes en une seule entité.
|
||||
|
||||
Les achromes de Claude Bellegarde encerclent cet inhérent jeu de doubles à travers l'emploi du blanc, une non-couleur ayant le potentiel d'être néant et origine, une blancheur féconde, un point zéro de l'espace qui n'est plus soumis au despotisme de la perspective. Dans le travail de l'artiste, les plis, les larmes et les rides affirment leur corporéité et donnent l'impression d'une matière travaillée vivante tel un papier froissé que Baudelaire assimile à une compression du cœur.
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ date: '05-08-2018 16:37'
|
||||
date_end: '18-08-2018 16:37'
|
||||
lieux: '93 Baker Street '
|
||||
artistes: ' Sonya Derviz'
|
||||
title: 'Pas de Corps, '
|
||||
title: '‘Pas de Corps'', '
|
||||
media_order: 'Sonya Derviz, Dancers II, 2018.jpg'
|
||||
show_sidebar: false
|
||||
aura:
|
||||
|
||||
@@ -3,7 +3,7 @@ date: '03-10-2018 17:49'
|
||||
date_end: '19-10-2018 17:49'
|
||||
lieux: '93 Baker Street '
|
||||
artistes: ' Sara Berman'
|
||||
title: 'Matter out of Place,'
|
||||
title: '‘Matter out of Place'','
|
||||
media_order: 'Sara Berman, Lint Sweater #40, 2018.jpg,Sara Berman, Lint Sweater #37, 2018.jpg,Installation 1 Sara Berman.jpg,Installation 2 Sara Berman.jpg,Sara Berman, Lint Sweater #38, 2018.jpg'
|
||||
show_sidebar: false
|
||||
aura:
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
date: '03-07-2018 16:39'
|
||||
date_end: '13-07-2018 16:40'
|
||||
lieux: '93 Baker Street '
|
||||
title: 'URBAN WAVES, '
|
||||
artistes: ' Andrei Costache, Richie Culver and Morgan Ward'
|
||||
title: '‘URBAN WAVES'', '
|
||||
media_order: 'Andrei Costache, Tarantula, 2018.jpg'
|
||||
show_sidebar: false
|
||||
aura:
|
||||
|
||||
|
Before Width: | Height: | Size: 4.6 MiB |
@@ -1,13 +1,28 @@
|
||||
---
|
||||
topH: true
|
||||
topH: false
|
||||
date: '13-02-2020 10:31'
|
||||
date_end: '28-03-2020 10:31'
|
||||
lieux: '12th floor'
|
||||
artistes: 'Sola Olulode'
|
||||
title: '‘Where The Ocean Meets The Beach'' '
|
||||
title: '‘Where The Ocean Meets The Beach'','
|
||||
media_order: 'Installation. Sola Olulode. In the Middle. The Feels and A Perfect Summer''s Day.jpeg,Sola Olulode. The Feels and A Perfect Summer''s Day. 2019.jpeg,Sola Olulode. The Feels. 2019.jpeg,Sola Olulode. A Perfect Summer''s Day. 2019.jpeg,Sola Olulode. Gals Pals. 2019.jpg,Sola Olulode. Close To You. 2019.jpeg,Installation. Sola Olulode. I Could Just Lay All Day With You and Laying in the Grass.jpeg,Sola Olulode. I Could Just Lay All Day With You. 2019.jpg,Sola Olulode. Laying in The Grass. 2019.jpg,Installation. Sola Olulode. Laying in the Grass. Entwined and Eternal Light.jpeg,Installation. Sola Olulode. Safety and Entwined.jpeg,Sola Olulode. Safety. 2019.jpeg,Sola Olulode. Entwined. 2020.jpeg,Sola Olulode. Eternal Light. 2020.jpeg,Sola Olulode. Waves. 2020.jpeg'
|
||||
show_sidebar: false
|
||||
aura:
|
||||
pagetype: website
|
||||
---
|
||||
|
||||
Sola Olulode's end of residency solo show ‘_Where the Ocean Meets the Beach_' will take place at our 12th floor gallery from 13th February until 28th March. Olulode has been working on a new series of paintings in which narratives of queer and Black identity culminate in the representation of romantic relationships an their complexities. Taken from artist Travis Alabanza's poem _The Sea_, the exhibition title refers to Alabanza’s feeling on gender fluidity as a boundless place similar to 'where the ocean meets the beach'. Analogously to Alabanza’s sentiment, Olulode renders the fluidity of emotions that chronicle the falling in love.
|
||||
_Where the Ocean Meets the Beach_ is Sola Olulode’s end of residency show at V.O Curations, a three-month period dedicated to Olulode’s research into representations of QTIBPOC (Queer Trans Intersex Black People & People of Colour) and romance. The artist presents a new series of paintings where narratives of Black queer womxn culminate in the representation of romantic relationships and their complexities, directly drawing upon Olulode’s experiences. Focusing on love, Olulode delves into the intimacy, complicity of dating and the impassioned ‘honeymoon’ phase.
|
||||
|
||||
Taken from artist Travis Alabanza's poem _The Sea_, the exhibition title refers to Alabanza’s feeling on gender fluidity as a boundless place similar to ‘where the ocean meets the beach’. Analogously to the poet’s sentiment, Olulode renders the fluidity of emotions that chronicles the process of falling in love. The constant malleable shift and undefinable identity, a central theme of Alabanza’s poem, enhances and sets the stage for Olulode’s characters to tell the untold, but timeless, stories that remain crystallised in calming moments of reflection and joy.
|
||||
|
||||
By creating a space that encapsulates the lovers, Olulode transports them into a placid and tranquil bubble that transcends reality. Exploring love as a protective barrier, a refuge from the misrepresentation and the confinement within a heteronormative ideal, that both denies and depreciates queer relationships. Reflecting the intimate and complex relationships of Black queer womxn, be they romantic or deeply platonic, Olulode questions the dynamics of the same existence of these relationships. The lovers’ genders are undefined and devoid of eroticity, straying away from a grazing patriarchal gaze of male desire and traditional heterosexual relational aesthetics. The figures are intrinsically naive and exude an aura of innocence, parting from references to eroticisim and crude sexuality, which queer relationships often become victim to. The perspective offered is delicate, creeping softly into a space, where only the deeply profound bond remains.
|
||||
|
||||
_Safety_ (2019) represents Olulode’s depiction of a safe and intimate space for the lovers resting under the duvet covers; inundated by a veil of indigo, the painting recalls Olulode’s experimentation with the Adire technique, a dyed textile made by the Yoruba people in Nigeria. Dyeing textiles and the use of melted wax directly references the artist’s Nigerian roots, echoing and informing her oeuvre through the lens of her background and history.
|
||||
|
||||
From the deeper tones that evoke the atmosphere and intimacy of nighttime, to the warmer hues that veil daily interactions between the lovers, indigo and turmeric envelop the characters and almost blend with them, becoming both part and background for them to exist. _A Perfect Summer’s Day_ (2019) amalgamates the lovers in a honey- glazed lens, where they lay imperturbable, suspended in a dimension without time nor space.
|
||||
|
||||
Olulode emphasises the undefinability of the characters and their gender, hair and clothing remaining the only signifier of personal identity. Curly afros, twists, locs with thick deep black lines and cornrows: the appearance of detailed hairstyles allows for more agency and reality to each figure. The intricate details are delicately interwoven and applied, either sewn or drawn upon, generating unique patterns that become an identifiable detail within her works. The lovers in _Good Vibrations _(2020) are distinct from other figures as their clothing becomes a prominent part of their depiction, with polka dots and pinstripes gently laying down on their bodies and raising them from the background where they emerge from.
|
||||
|
||||
_British-Nigerian artist Sola Olulode (b. 1996, London) graduated from the University of Brighton in 2018. After successfully completing a residency at Lewisham Art House, she was recently shortlisted for the Evening Standard Art Prize, and featured in V&A’s 2020 exhibition ‘In the Palm of Your Hands’ . Olulode has been developing a narrative investigating queerness and black identity through the lens of social interactions. _
|
||||
|
||||
Accessibility entrance from 39 York Road. For further assistance please call +447789813313.
|
||||
|
Before Width: | Height: | Size: 3.0 MiB |
|
Before Width: | Height: | Size: 3.2 MiB |
|
Before Width: | Height: | Size: 2.5 MiB |
|
Before Width: | Height: | Size: 2.3 MiB |
|
Before Width: | Height: | Size: 2.8 MiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 3.2 MiB |
|
Before Width: | Height: | Size: 3.5 MiB |
|
Before Width: | Height: | Size: 4.6 MiB |
|
Before Width: | Height: | Size: 3.0 MiB |
|
Before Width: | Height: | Size: 3.2 MiB |
|
Before Width: | Height: | Size: 3.8 MiB |
|
Before Width: | Height: | Size: 5.2 MiB |
@@ -1,14 +1,22 @@
|
||||
---
|
||||
topH: true
|
||||
topH: false
|
||||
date: '09-01-2020 17:50'
|
||||
date_end: '01-02-2020 17:50'
|
||||
lieux: '12th floor'
|
||||
artistes: 'Frances Gibson, Alexandre Canonico, André Figueiredo da Silva, Josephine Baker'
|
||||
title: '''Wreck Or Ruin'''
|
||||
media_order: 'Josephine Baker and Alexandré Canonico.jpeg,Alexandre Canonico Through 2020.jpeg,Alexandre Canonico Through (detail) 2020.jpeg,Josephine Baker Abrasion Coast 2020.jpeg,Josephine Baker Abrasion Coast (detail) 2020.jpeg,Josephine Baker Waterfall 2020.jpeg,Josephine Baker Waterfall (detail) 2020.jpeg,Installation Frances Gibson Alexandre Canonico André Figueiredo da Silva.jpeg,Frances Gibson A Pound of Flower 2020.jpeg,Frances Gibson 40 Elephants 2020.jpeg,Alexandre Canonico Dig #7 2019.jpeg,André Figueiredo da Silva Pinning Down 2020.jpeg,André Figueiredo da Silva Pinning Down (detail) 2020.jpeg'
|
||||
title: '‘Wreck Or Ruin’,'
|
||||
media_order: 'Installation. Alexandre Canonico and Jospehine Baker.jpeg,Josephine Baker. Abrasion Coast. 2020.jpeg,Josephine Baker. Abrasion Coast (detail). 2020.jpeg,Installation. Alexandre Canonico and Josephine Baker.jpeg,Josephine Baker. Waterfall. 2020.jpeg,Josephine Baker. Waterfall (detail). 2020.jpeg,Alexandre Canonico. Through. 2020.jpeg,Alexandre Canonico. Through (detail). 2020.jpeg,Installation. Frances Gibson. Alexandre Canonico. André Figueiredo da Silva.jpeg,Frances Gibson. A Pound of Flower. 2020.jpeg,Frances Gibson. 40 Elephants. 2020.jpeg,Frances Gibson. 40 Elephants (detail). 2020.jpeg,Alexandre Canonico. Dig #7. 2019.jpeg,André Figueiredo da Silva. Pinning Down. 2020.jpeg'
|
||||
show_sidebar: false
|
||||
aura:
|
||||
pagetype: website
|
||||
---
|
||||
|
||||
We are thrilled to host ‘Wreck or Ruin’, a collaborative project of sculptural works that has organically developed between André Figueiredo da Silva, Alexandre Canonico, Josephine Baker and Frances Gibson at 12th floor, reflecting upon the impending demolition of Tower Building.
|
||||
‘Wreck or Ruin’ is a collaborative project that has organically developed between André Figueiredo da Silva, Alexandre Canonico, Josephine Baker and Frances Gibson at 12th floor, reflecting upon the impending demolition of Tower Building.
|
||||
|
||||
As a studio resident in the Tower Building, Figueiredo da Silva incorporates architectural elements from the site: corners, walls, columns. Pink sculptural foamed columns act as traces of a building that will cease to exist. As the foam cannot sustain anything in its usual form, Figueiredo da Silva’s structure relies on carefully depressurised footballs that require a specific maintenance, similar to that of the building. By misusing the materials and turning them into something they are not meant to be, the artist makes an allusion to the building's present state of instability.
|
||||
|
||||
Using materials as a starting point, Canonico has taken a playful approach to create sculptures from commonly found objects. Metal rods and hoses sit together, sustained by the interplay between their different material qualities to bridge the space between floor and ceiling. While Canonico’s works are not site-specific, they rely on the ‘negative space’ surrounding them. The sculptures embrace the air they encapsulate.
|
||||
|
||||
Artist Josephine Baker has created relief works that mimic the historical production of nature and landscape as images or ‘grand designs’ of humancentric relations. Their diagrammatic and almost pixelated motifs, made out of building and industrial materials, appear to illustrate elemental processes such as water cycles and the movements of the earth. Her works further comment on the lack of distinction between human-made and natural disasters in today's world.
|
||||
|
||||
Finally, Gibson’s work takes the form of a memorial of forty small clay elephants and a sculpture of a pound symbol made of carnations. The small sculptures reference the infamous all-women crime syndicate ‘The Forty Elephants’ (based in Elephant and Castle), who conducted the largest shoplifting operation in Britain during their activity from the 1870s to 1950s. Gibson’s work for the project is informed by a tenuous chain of events and research based on the history of the building.
|
||||
@@ -83,6 +83,6 @@ form:
|
||||
reset: true
|
||||
---
|
||||
|
||||
We accept applications of artists from all practices. Our studios can be used individually or shared with others; applications are for one person at a time, so even if you are applying as a group, please fill out individual applications and specify in _notes_ who you wish to share with. Our studio prices start from £100pm and are offered inline with _Greater London Authority's affordability guide (2014, 2019)_. All prices are inclusive of utilities (inc. wifi). Facilities are accessible 24/7 with communal areas and shared kitchens available.
|
||||
We accept applications of artists from all practices. Our studios can be used individually or shared with others; applications are for one person at a time, even if you apply as a group, please fill out individual applications and specify in _notes_ who you wish to share with. Our studio prices start from £100pm and are offered inline with _Greater London Authority's affordability guide (2014, 2019)_. All prices are inclusive of utilities. Facilities are accessible 24/7 with communal areas and shared kitchens available.
|
||||
|
||||
Please allow us a week to get back to you, you will receive a response via email. Feel free to contact [studio@vocurations.com](mailto:studio@vocurations.com) for any further questions, or to organise a site visit!
|
||||
Feel free to contact [studio@vocurations.com](mailto:studio@vocurations.com) for any further questions, or to organise a site visit.
|
||||
|
||||
|
Before Width: | Height: | Size: 113 KiB |
@@ -1,11 +1,12 @@
|
||||
---
|
||||
title: About
|
||||
media_order: 'VO Curations - 12th Floor.jpg'
|
||||
media_order: 'Anousha Payne.jpg,SJM_VO_SonnyShanti-9451.jpg,SJM_Vo-9154.jpg,Portrait Azadeh and Salman.jpg,_MG_2961 (2).jpg'
|
||||
visible: false
|
||||
aura:
|
||||
pagetype: website
|
||||
---
|
||||
|
||||
V.O Studios offers affordable workspaces to artists based in London. The group reflects the diversity of artists and practices in the city, and form an integral part of V.O’s community-at-large. All studios are located in Zone 1 Central London, with the view to ease access and visibility for each artist. ‘Open Days’ are scheduled throughout the year to offer artists ongoing opportunities to explore new dialogues with interested members of the local community, as well as the wider public. V.O ensures their artist community their ongoing support through advising on professional development, mentoring, presentation, and access to workshop and exhibition opportunities. V.O Studios are currently located in Waterloo, SE1 and 242 Marylebone Road, NW1, with more upcoming spaces from spring 2020!
|
||||
V.O Studios offers affordable workspaces to artists based in London. The group reflects the diversity of artists and practices in the city, and form an integral part of V.O’s community-at-large. All studios are located in Zone 1 Central London, with the view to ease access and visibility for each artist. ‘Open Days’ are scheduled throughout the year to offer artists ongoing opportunities to explore new dialogues with interested members of the local community, as well as the wider public. V.O ensures their artist community their ongoing support through advising on professional development, mentoring, presentation, and access to workshop and exhibition opportunities. V.O Studios are currently located in 242 Marylebone Road, NW1, with more upcoming spaces from Autumn 2020.
|
||||
_V.O Studios is a not-for-profit initiative launched October 2019._
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ show_sidebar: false
|
||||
content:
|
||||
items:
|
||||
- '@self.children'
|
||||
limit: 5
|
||||
limit: 999
|
||||
order:
|
||||
by: date
|
||||
dir: desc
|
||||
|
||||
|
Before Width: | Height: | Size: 2.7 MiB |
@@ -3,11 +3,11 @@ residence: true
|
||||
date: '12-12-2019 13:22'
|
||||
date_end: '27-02-2020 13:22'
|
||||
title: 'Emma Prempeh '
|
||||
media_order: 20190611_090033694_iOS.jpg
|
||||
media_order: 'Example Vo.jpg'
|
||||
published: true
|
||||
show_sidebar: false
|
||||
aura:
|
||||
pagetype: website
|
||||
---
|
||||
|
||||
Recent graduate from Goldsmiths University, Emma Prempeh is currently an artist-in-residence at V.O Curations, completing a new series of work which will be exhibited with V.O Curations in Spring 2020.
|
||||
Artist Emma Prempeh (b.1996, London) is a fine art graduate from Goldsmiths University and is currently attending an MA in Painting at the Royal College of Art. Family and generational continuity is often the subject of Prempeh’s paintings, as relational ties are explored and questioned, through the depiction of her mother and grandmother and their experiences. The search of spirituality enables Prempeh to analyse existential questions that are projected upon her reality; the fear of death, memory, ancestral ties. After completing a 2-month residency at V.O Curations, Prempeh has debuted with solo show ‘The Faces of Love’ (13.10-14.11.2020) held at V.O Studios, 242 Marylebone Road.
|
||||
|
||||
|
Before Width: | Height: | Size: 537 KiB |
@@ -1,12 +1,11 @@
|
||||
---
|
||||
date: '14-11-2019 14:54'
|
||||
date_end: '14-02-2020 14:54'
|
||||
title: 'Sola Olulode '
|
||||
media_order: 'Sola Olulode at V.O Curations.JPG'
|
||||
date: '14-11-2019 10:34'
|
||||
date_end: '14-02-2020 10:34'
|
||||
title: 'Sola Olulode'
|
||||
media_order: 'Sola Olulode at VO Curations.jpg'
|
||||
show_sidebar: false
|
||||
aura:
|
||||
pagetype: website
|
||||
date_start: '14-11-2019 10:46'
|
||||
---
|
||||
|
||||
British-Nigerian artist Sola Olulode is currently completing a three-month residency at V.O Curations working on a new series of paintings where narratives of queer and Black identity culminate in the representation of romantic relationships and their complexities. The artist's end of residency solo show 'Where the ocean meets the beach' will take place at V.O's 12th floor gallery from 13 February to 28 March.
|
||||
British-Nigerian artist Sola Olulode (b. 1996, London) is a fine art graduate from the University of Brighton. Olulode explores the relations of QTIBPOC (Queer Trans Intersex Black People & People of Colour) and black identity through the lens of social interactions, working with wax and textile dyes taken from Yoruba tradition. Sola Olulode has been in a 3-month residency at V.O Curations, culminating in the solo show ‘Where the Ocean Meets the Beach’ (13.02-28.03-2020) at V.O Curations, 12th Floor.
|
||||
|
Before Width: | Height: | Size: 1.6 MiB |
@@ -1,11 +0,0 @@
|
||||
---
|
||||
date_start: '05-06-2019 11:05'
|
||||
date_end: '05-09-2019 19:43'
|
||||
title: 'Nour El Saleh'
|
||||
media_order: 'Nour El Saleh, Detail ''Four Times as Big, Twice as Small'' 2018.jpeg'
|
||||
date: '05-06-2019 19:43'
|
||||
show_sidebar: false
|
||||
aura:
|
||||
pagetype: website
|
||||
---
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 'In residence'
|
||||
media_order: 'Emma Prempeh Studio Photo 2.JPG'
|
||||
redirect: /residence/about
|
||||
show_sidebar: false
|
||||
content:
|
||||
|
||||
@@ -14,4 +14,3 @@ content:
|
||||
aura:
|
||||
pagetype: website
|
||||
---
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
---
|
||||
title: '‘Phillips x VO Curations Presents Perfume Of Thorns’'
|
||||
date_event: '23-05-2019 19:30'
|
||||
date: '23-05-2019 19:30'
|
||||
aura:
|
||||
pagetype: website
|
||||
date_event: '23-05-2019 19:30'
|
||||
---
|
||||
|
||||
[‘Phillips x VO Curations Presents Perfume Of Thorns’, Outset Contemporary](https://outset.org.uk/supported-projects/phillips-x-vo-curations-presents-perfume-of-thorns/)
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
title: 'Sam Cornish on Olivia Bax'
|
||||
date_event: '16-08-2018 17:31'
|
||||
date: '16-08-2018 17:31'
|
||||
aura:
|
||||
pagetype: website
|
||||
class: small
|
||||
---
|
||||
|
||||
['Sam Cornish on Olivia Bax', Instantloveland](https://instantloveland.com/wp/2018/08/16/sam-cornish-on-olivia-bax/)
|
||||
@@ -1,9 +0,0 @@
|
||||
---
|
||||
title: 'Paintings by Sola Olulode explore the ''honeymoon'' phase of romance'
|
||||
date_event: '16-01-2020 15:38'
|
||||
date: '16-01-2020 15:38'
|
||||
aura:
|
||||
pagetype: website
|
||||
---
|
||||
|
||||
['Paintings by Sola Olulode explore the ''honeymoon'' phase of romance', Creative Boom](https://www.creativeboom.com/inspiration/paintings-by-sola-olulode-explore-the-honeymoon-phase-of-romance/)
|
||||
|
Before Width: | Height: | Size: 543 KiB |
@@ -1,11 +0,0 @@
|
||||
---
|
||||
title: 'Adriano Amaral, Et Al., ''Euphoria'', Tick Tack, Antwerpen, Belgium'
|
||||
media_order: 'Adriano Amaral, Untitled, 2018.jpg'
|
||||
date: '09-11-2019 14:49'
|
||||
date_end: '21-12-2019 14:50'
|
||||
url_news: 'https://www.ticktack.be/exhibitions/euphoria-curated-by-domenico-de-chirico'
|
||||
published: true
|
||||
aura:
|
||||
pagetype: website
|
||||
---
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
---
|
||||
title: 'Daiga Grantina, ''What Eats Around Itself'', New Museum, New York, USA'
|
||||
media_order: 'Daiga Grantina, ''What Eats Around Itself'', New Museum.png'
|
||||
date: '21-01-2020 14:41'
|
||||
date_end: '05-09-2020 14:41'
|
||||
url_news: 'https://www.newmuseum.org/exhibitions/view/daiga-grantina-what-eats-around-itself'
|
||||
published: true
|
||||
aura:
|
||||
pagetype: website
|
||||
---
|
||||
|
||||
|
Before Width: | Height: | Size: 45 KiB |
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: 'Isaac Lythgoe, ''Railway Spine'', Super Dakota, Brussels, Belgium'
|
||||
media_order: 'Isaac Lythgoe ''Railway Spine''.jpg'
|
||||
media_order: 'Screenshot 2020-01-23 at 16.50.04.png'
|
||||
date: '09-01-2020 14:20'
|
||||
date_end: '25-02-2020 14:21'
|
||||
url_news: 'http://www.superdakota.com/exhibitions/isaac-lythgoe-railway-spine'
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
---
|
||||
title: 'Jean-Marie Appriou, Biennale de Lyon, Lyon, France'
|
||||
date: '18-09-2019 19:52'
|
||||
date_end: '05-01-2020 19:52'
|
||||
url_news: 'https://www.biennaledelyon.com/en/artistes/jean-marie-appriou-2/'
|
||||
published: true
|
||||
aura:
|
||||
pagetype: website
|
||||
show_sidebar: false
|
||||
---
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title: 'Jean-Marie Appriou, ''The Horses'', Central Park, New York, USA'
|
||||
media_order: 'Jean-Marie Appriou _The Horses_, Central Park, New York, USA .jpg'
|
||||
date: '11-09-2019 19:55'
|
||||
date_end: '15-12-2020 19:55'
|
||||
date_end: '30-08-2020 19:55'
|
||||
url_news: 'https://www.publicartfund.org/view/exhibitions/6708_jean-marie_appriou_the_horses'
|
||||
published: false
|
||||
aura:
|
||||
|
||||