mirror of
https://github.com/getgrav/grav.git
synced 2026-02-22 14:38:13 +01:00
Merge branch 'release/1.7.27'
This commit is contained in:
2
.github/workflows/tests.yaml
vendored
2
.github/workflows/tests.yaml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php: [ 8.0, 7.4, 7.3]
|
||||
php: [ 8.1, 8.0, 7.4, 7.3]
|
||||
os: [ubuntu-latest]
|
||||
|
||||
steps:
|
||||
|
||||
19
CHANGELOG.md
19
CHANGELOG.md
@@ -1,3 +1,22 @@
|
||||
# v1.7.27
|
||||
## 01/12/2022
|
||||
|
||||
1. [](#new)
|
||||
* Support for `YubiKey OTP` 2-Factor authenticator
|
||||
* Added support for generic `assets.link()` for external references. No pipeline support
|
||||
* Added support for `assets.addJsModule()` with full pipeline support
|
||||
* Added `Utils::getExtensionsByMime()` method to get all the registered extensions for the specific mime type
|
||||
* Added `Media::getRoute()` and `Media::getRawRoute()` methods to get page route if available
|
||||
* Added `Medium::getAlternatives()` to be able to list all the retina sizes
|
||||
2. [](#improved)
|
||||
* Improved `Utils::download()` method to allow overrides on download name, mime and expires header
|
||||
* Improved `onPageFallBackUrl` event
|
||||
* Reorganized the Asset system configuration blueprint for clarity
|
||||
3. [](#bugfix)
|
||||
* Fixed CLI `--env` and `--lang` options having no effect if they aren't added before all the other options
|
||||
* Fixed scaled image medium filename when using non-existing retina file
|
||||
* Fixed an issue with JS `imports` and pipelining Assets
|
||||
|
||||
# v1.7.26.1
|
||||
## 01/04/2022
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ Grav is a **Fast**, **Simple**, and **Flexible**, file-based Web-platform. Ther
|
||||
|
||||
The underlying architecture of Grav is designed to use well-established and _best-in-class_ technologies to ensure that Grav is simple to use and easy to extend. Some of these key technologies include:
|
||||
|
||||
* [Twig Templating](https://twig.sensiolabs.org/): for powerful control of the user interface
|
||||
* [Twig Templating](https://twig.symfony.com/): for powerful control of the user interface
|
||||
* [Markdown](https://en.wikipedia.org/wiki/Markdown): for easy content creation
|
||||
* [YAML](https://yaml.org): for simple configuration
|
||||
* [Parsedown](https://parsedown.org/): for fast Markdown and Markdown Extra support
|
||||
|
||||
@@ -888,9 +888,45 @@ form:
|
||||
title: PLUGIN_ADMIN.ASSETS
|
||||
|
||||
fields:
|
||||
assets_section:
|
||||
general_config_section:
|
||||
type: section
|
||||
title: PLUGIN_ADMIN.ASSETS
|
||||
title: PLUGIN_ADMIN.GENERAL_CONFIG
|
||||
underline: true
|
||||
|
||||
assets.enable_asset_timestamp:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.ENABLED_TIMESTAMPS_ON_ASSETS
|
||||
help: PLUGIN_ADMIN.ENABLED_TIMESTAMPS_ON_ASSETS_HELP
|
||||
highlight: 0
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
assets.enable_asset_sri:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.ENABLED_SRI_ON_ASSETS
|
||||
help: PLUGIN_ADMIN.ENABLED_SRI_ON_ASSETS_HELP
|
||||
highlight: 0
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
assets.collections:
|
||||
type: multilevel
|
||||
label: PLUGIN_ADMIN.COLLECTIONS
|
||||
placeholder_key: collection_name
|
||||
placeholder_value: collection_path
|
||||
validate:
|
||||
type: array
|
||||
|
||||
|
||||
css_assets_section:
|
||||
type: section
|
||||
title: PLUGIN_ADMIN.CSS_ASSETS
|
||||
underline: true
|
||||
|
||||
assets.css_pipeline:
|
||||
@@ -959,6 +995,11 @@ form:
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
js_assets_section:
|
||||
type: section
|
||||
title: PLUGIN_ADMIN.JS_ASSETS
|
||||
underline: true
|
||||
|
||||
assets.js_pipeline:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.JAVASCRIPT_PIPELINE
|
||||
@@ -1003,10 +1044,15 @@ form:
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
assets.enable_asset_timestamp:
|
||||
js_module_assets_section:
|
||||
type: section
|
||||
title: PLUGIN_ADMIN.JS_MODULE_ASSETS
|
||||
underline: true
|
||||
|
||||
assets.js_module_pipeline:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.ENABLED_TIMESTAMPS_ON_ASSETS
|
||||
help: PLUGIN_ADMIN.ENABLED_TIMESTAMPS_ON_ASSETS_HELP
|
||||
label: PLUGIN_ADMIN.JAVASCRIPT_MODULE_PIPELINE
|
||||
help: PLUGIN_ADMIN.JAVASCRIPT_MODULE_PIPELINE_HELP
|
||||
highlight: 0
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
@@ -1014,24 +1060,29 @@ form:
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
assets.enable_asset_sri:
|
||||
assets.js_module_pipeline_include_externals:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.ENABLED_SRI_ON_ASSETS
|
||||
help: PLUGIN_ADMIN.ENABLED_SRI_ON_ASSETS_HELP
|
||||
highlight: 0
|
||||
label: PLUGIN_ADMIN.JAVASCRIPT_MODULE_PIPELINE_INCLUDE_EXTERNALS
|
||||
help: PLUGIN_ADMIN.JAVASCRIPT_MODULE_PIPELINE_INCLUDE_EXTERNALS_HELP
|
||||
highlight: 1
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
assets.collections:
|
||||
type: multilevel
|
||||
label: PLUGIN_ADMIN.COLLECTIONS
|
||||
placeholder_key: collection_name
|
||||
placeholder_value: collection_path
|
||||
assets.js_module_pipeline_before_excludes:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.JAVASCRIPT_MODULE_PIPELINE_BEFORE_EXCLUDES
|
||||
help: PLUGIN_ADMIN.JAVASCRIPT_MODULE_PIPELINE_BEFORE_EXCLUDES_HELP
|
||||
highlight: 1
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
validate:
|
||||
type: array
|
||||
type: bool
|
||||
|
||||
|
||||
|
||||
errors:
|
||||
type: tab
|
||||
|
||||
@@ -107,6 +107,12 @@ form:
|
||||
label: PLUGIN_ADMIN.2FA_SECRET
|
||||
sublabel: PLUGIN_ADMIN.2FA_SECRET_HELP
|
||||
|
||||
yubikey_id:
|
||||
type: text
|
||||
label: PLUGIN_ADMIN.YUBIKEY_ID
|
||||
description: PLUGIN_ADMIN.YUBIKEY_HELP
|
||||
size: small
|
||||
maxlength: 12
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -127,6 +127,9 @@ assets: # Configuration for Assets Mana
|
||||
js_pipeline: false # The JS pipeline is the unification of multiple JS resources into one file
|
||||
js_pipeline_include_externals: true # Include external URLs in the pipeline by default
|
||||
js_pipeline_before_excludes: true # Render the pipeline before any excluded files
|
||||
js_module_pipeline: false # The JS Module pipeline is the unification of multiple JS Module resources into one file
|
||||
js_module_pipeline_include_externals: true # Include external URLs in the pipeline by default
|
||||
js_module_pipeline_before_excludes: true # Render the pipeline before any excluded files
|
||||
js_minify: true # Minify the JS during pipelining
|
||||
enable_asset_timestamp: false # Enable asset timestamps
|
||||
enable_asset_sri: false # Enable asset SRI
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
// Some standard defines
|
||||
define('GRAV', true);
|
||||
define('GRAV_VERSION', '1.7.26.1');
|
||||
define('GRAV_VERSION', '1.7.27');
|
||||
define('GRAV_SCHEMA', '1.7.0_2020-11-20_1');
|
||||
define('GRAV_TESTING', false);
|
||||
|
||||
|
||||
@@ -30,14 +30,21 @@ class Assets extends PropertyObject
|
||||
use TestingAssetsTrait;
|
||||
use LegacyAssetsTrait;
|
||||
|
||||
const LINK = 'link';
|
||||
const CSS = 'css';
|
||||
const JS = 'js';
|
||||
const JS_MODULE = 'js_module';
|
||||
const LINK_COLLECTION = 'assets_link';
|
||||
const CSS_COLLECTION = 'assets_css';
|
||||
const JS_COLLECTION = 'assets_js';
|
||||
const JS_MODULE_COLLECTION = 'assets_js_module';
|
||||
const LINK_TYPE = Assets\Link::class;
|
||||
const CSS_TYPE = Assets\Css::class;
|
||||
const JS_TYPE = Assets\Js::class;
|
||||
const JS_MODULE_TYPE = Assets\JsModule::class;
|
||||
const INLINE_CSS_TYPE = Assets\InlineCss::class;
|
||||
const INLINE_JS_TYPE = Assets\InlineJs::class;
|
||||
const INLINE_JS_MODULE_TYPE = Assets\InlineJsModule::class;
|
||||
|
||||
/** @const Regex to match CSS and JavaScript files */
|
||||
const DEFAULT_REGEX = '/.\.(css|js)$/i';
|
||||
@@ -48,15 +55,24 @@ class Assets extends PropertyObject
|
||||
/** @const Regex to match JavaScript files */
|
||||
const JS_REGEX = '/.\.js$/i';
|
||||
|
||||
/** @const Regex to match JavaScriptModyle files */
|
||||
const JS_MODULE_REGEX = '/.\.mjs$/i';
|
||||
|
||||
/** @var string */
|
||||
protected $assets_dir;
|
||||
/** @var string */
|
||||
protected $assets_url;
|
||||
|
||||
/** @var array */
|
||||
protected $assets_link = [];
|
||||
/** @var array */
|
||||
protected $assets_css = [];
|
||||
/** @var array */
|
||||
protected $assets_js = [];
|
||||
/** @var array */
|
||||
protected $assets_js_module = [];
|
||||
|
||||
|
||||
|
||||
// Following variables come from the configuration:
|
||||
/** @var bool */
|
||||
@@ -66,19 +82,17 @@ class Assets extends PropertyObject
|
||||
/** @var bool */
|
||||
protected $css_pipeline_before_excludes;
|
||||
/** @var bool */
|
||||
protected $inlinecss_pipeline_include_externals;
|
||||
/** @var bool */
|
||||
protected $inlinecss_pipeline_before_excludes;
|
||||
/** @var bool */
|
||||
protected $js_pipeline;
|
||||
/** @var bool */
|
||||
protected $js_pipeline_include_externals;
|
||||
/** @var bool */
|
||||
protected $js_pipeline_before_excludes;
|
||||
/** @var bool */
|
||||
protected $inlinejs_pipeline_include_externals;
|
||||
protected $js_module_pipeline;
|
||||
/** @var bool */
|
||||
protected $inlinejs_pipeline_before_excludes;
|
||||
protected $js_module_pipeline_include_externals;
|
||||
/** @var bool */
|
||||
protected $js_module_pipeline_before_excludes;
|
||||
/** @var array */
|
||||
protected $pipeline_options = [];
|
||||
|
||||
@@ -193,6 +207,8 @@ class Assets extends PropertyObject
|
||||
call_user_func_array([$this, 'addCss'], $args);
|
||||
} elseif ($extension === 'js') {
|
||||
call_user_func_array([$this, 'addJs'], $args);
|
||||
} elseif ($extension === 'mjs') {
|
||||
call_user_func_array([$this, 'addJsModule'], $args);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -222,7 +238,7 @@ class Assets extends PropertyObject
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (($type === $this::CSS_TYPE || $type === $this::JS_TYPE) && isset($this->collections[$asset])) {
|
||||
if ($this->isValidType($type) && isset($this->collections[$asset])) {
|
||||
$this->addType($collection, $type, $this->collections[$asset], $options);
|
||||
return $this;
|
||||
}
|
||||
@@ -230,7 +246,9 @@ class Assets extends PropertyObject
|
||||
// If pipeline disabled, set to position if provided, else after
|
||||
if (isset($options['pipeline'])) {
|
||||
if ($options['pipeline'] === false) {
|
||||
$exclude_type = ($type === $this::JS_TYPE || $type === $this::INLINE_JS_TYPE) ? $this::JS : $this::CSS;
|
||||
|
||||
$exclude_type = $this->getBaseType($type);
|
||||
|
||||
$excludes = strtolower($exclude_type . '_pipeline_before_excludes');
|
||||
if ($this->{$excludes}) {
|
||||
$default = 'after';
|
||||
@@ -269,6 +287,16 @@ class Assets extends PropertyObject
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a CSS asset or a collection of assets.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addLink($asset)
|
||||
{
|
||||
return $this->addType($this::LINK_COLLECTION, $this::LINK_TYPE, $asset, $this->unifyLegacyArguments(func_get_args(), $this::LINK_TYPE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a CSS asset or a collection of assets.
|
||||
*
|
||||
@@ -309,6 +337,25 @@ class Assets extends PropertyObject
|
||||
return $this->addType($this::JS_COLLECTION, $this::INLINE_JS_TYPE, $asset, $this->unifyLegacyArguments(func_get_args(), $this::INLINE_JS_TYPE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a JS asset or a collection of assets.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addJsModule($asset)
|
||||
{
|
||||
return $this->addType($this::JS_MODULE_COLLECTION, $this::JS_MODULE_TYPE, $asset, $this->unifyLegacyArguments(func_get_args(), $this::JS_MODULE_TYPE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an Inline JS asset or a collection of assets.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addInlineJsModule($asset)
|
||||
{
|
||||
return $this->addType($this::JS_MODULE_COLLECTION, $this::INLINE_JS_MODULE_TYPE, $asset, $this->unifyLegacyArguments(func_get_args(), $this::INLINE_JS_MODULE_TYPE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add/replace collection.
|
||||
@@ -400,7 +447,7 @@ class Assets extends PropertyObject
|
||||
$after_assets = $this->filterAssets($group_assets, 'position', 'after', true);
|
||||
|
||||
// Pipeline
|
||||
if ($this->{$pipeline_enabled}) {
|
||||
if ($this->{$pipeline_enabled} ?? false) {
|
||||
$options = array_merge($this->pipeline_options, ['timestamp' => $this->timestamp]);
|
||||
|
||||
$pipeline = new Pipeline($options);
|
||||
@@ -432,9 +479,29 @@ class Assets extends PropertyObject
|
||||
* @param array $attributes
|
||||
* @return string
|
||||
*/
|
||||
public function css($group = 'head', $attributes = [])
|
||||
public function css($group = 'head', $attributes = [], $include_link = true)
|
||||
{
|
||||
return $this->render('css', $group, $attributes);
|
||||
$output = '';
|
||||
|
||||
if ($include_link) {
|
||||
$output = $this->link($group, $attributes);
|
||||
}
|
||||
|
||||
$output .= $this->render(self::CSS, $group, $attributes);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the CSS link tags.
|
||||
*
|
||||
* @param string $group name of the group
|
||||
* @param array $attributes
|
||||
* @return string
|
||||
*/
|
||||
public function link($group = 'head', $attributes = [])
|
||||
{
|
||||
return $this->render(self::LINK, $group, $attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -444,8 +511,58 @@ class Assets extends PropertyObject
|
||||
* @param array $attributes
|
||||
* @return string
|
||||
*/
|
||||
public function js($group = 'head', $attributes = [])
|
||||
public function js($group = 'head', $attributes = [], $include_js_module = true)
|
||||
{
|
||||
return $this->render('js', $group, $attributes);
|
||||
$output = $this->render(self::JS, $group, $attributes);
|
||||
|
||||
if ($include_js_module) {
|
||||
$output .= $this->jsModule($group, $attributes);
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the Javascript Modules tags
|
||||
*
|
||||
* @param $group
|
||||
* @param $attributes
|
||||
* @return string
|
||||
*/
|
||||
public function jsModule($group = 'head', $attributes = [])
|
||||
{
|
||||
return $this->render(self::JS_MODULE, $group, $attributes);
|
||||
}
|
||||
|
||||
public function all($group = 'head', $attributes = [])
|
||||
{
|
||||
$output = $this->css($group, $attributes, false);
|
||||
$output .= $this->link($group, $attributes);
|
||||
$output .= $this->js($group, $attributes, false);
|
||||
$output .= $this->jsModule($group, $attributes);
|
||||
return $output;
|
||||
}
|
||||
|
||||
protected function isValidType($type)
|
||||
{
|
||||
return in_array($type, [self::CSS_TYPE, self::JS_TYPE, self::JS_MODULE_TYPE]);
|
||||
}
|
||||
|
||||
protected function getBaseType($type)
|
||||
{
|
||||
switch ($type) {
|
||||
case $this::JS_TYPE:
|
||||
case $this::INLINE_JS_TYPE:
|
||||
$base_type = $this::JS;
|
||||
break;
|
||||
case $this::JS_MODULE_TYPE:
|
||||
case $this::INLINE_JS_MODULE_TYPE:
|
||||
$base_type = $this::JS_MODULE;
|
||||
break;
|
||||
default:
|
||||
$base_type = $this::CSS;
|
||||
}
|
||||
|
||||
return $base_type;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,9 @@ abstract class BaseAsset extends PropertyObject
|
||||
{
|
||||
use AssetUtilsTrait;
|
||||
|
||||
protected const CSS_ASSET = true;
|
||||
protected const JS_ASSET = false;
|
||||
protected const CSS_ASSET = 1;
|
||||
protected const JS_ASSET = 2;
|
||||
protected const JS_MODULE_ASSET = 3;
|
||||
|
||||
/** @var string|false */
|
||||
protected $asset;
|
||||
@@ -69,7 +70,7 @@ abstract class BaseAsset extends PropertyObject
|
||||
* @param array $elements
|
||||
* @param string|null $key
|
||||
*/
|
||||
public function __construct(array $elements = [], $key = null)
|
||||
public function __construct(array $elements = [], ?string $key = null)
|
||||
{
|
||||
$base_config = [
|
||||
'group' => 'head',
|
||||
|
||||
@@ -22,7 +22,7 @@ class Css extends BaseAsset
|
||||
* @param array $elements
|
||||
* @param string|null $key
|
||||
*/
|
||||
public function __construct(array $elements = [], $key = null)
|
||||
public function __construct(array $elements = [], ?string $key = null)
|
||||
{
|
||||
$base_options = [
|
||||
'asset_type' => 'css',
|
||||
|
||||
@@ -22,7 +22,7 @@ class InlineCss extends BaseAsset
|
||||
* @param array $elements
|
||||
* @param string|null $key
|
||||
*/
|
||||
public function __construct(array $elements = [], $key = null)
|
||||
public function __construct(array $elements = [], ?string $key = null)
|
||||
{
|
||||
$base_options = [
|
||||
'asset_type' => 'css',
|
||||
|
||||
@@ -22,7 +22,7 @@ class InlineJs extends BaseAsset
|
||||
* @param array $elements
|
||||
* @param string|null $key
|
||||
*/
|
||||
public function __construct(array $elements = [], $key = null)
|
||||
public function __construct(array $elements = [], ?string $key = null)
|
||||
{
|
||||
$base_options = [
|
||||
'asset_type' => 'js',
|
||||
|
||||
46
system/src/Grav/Common/Assets/InlineJsModule.php
Normal file
46
system/src/Grav/Common/Assets/InlineJsModule.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Assets
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Assets;
|
||||
|
||||
use Grav\Common\Utils;
|
||||
|
||||
/**
|
||||
* Class InlineJs
|
||||
* @package Grav\Common\Assets
|
||||
*/
|
||||
class InlineJsModule extends BaseAsset
|
||||
{
|
||||
/**
|
||||
* InlineJs constructor.
|
||||
* @param array $elements
|
||||
* @param string|null $key
|
||||
*/
|
||||
public function __construct(array $elements = [], ?string $key = null)
|
||||
{
|
||||
$base_options = [
|
||||
'asset_type' => 'js_module',
|
||||
'attributes' => ['type' => 'module'],
|
||||
'position' => 'after'
|
||||
];
|
||||
|
||||
$merged_attributes = Utils::arrayMergeRecursiveUnique($base_options, $elements);
|
||||
|
||||
parent::__construct($merged_attributes, $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
return '<script' . $this->renderAttributes(). ">\n" . trim($this->asset) . "\n</script>\n";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,7 +22,7 @@ class Js extends BaseAsset
|
||||
* @param array $elements
|
||||
* @param string|null $key
|
||||
*/
|
||||
public function __construct(array $elements = [], $key = null)
|
||||
public function __construct(array $elements = [], ?string $key = null)
|
||||
{
|
||||
$base_options = [
|
||||
'asset_type' => 'js',
|
||||
|
||||
49
system/src/Grav/Common/Assets/JsModule.php
Normal file
49
system/src/Grav/Common/Assets/JsModule.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Assets
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Assets;
|
||||
|
||||
use Grav\Common\Utils;
|
||||
|
||||
/**
|
||||
* Class Js
|
||||
* @package Grav\Common\Assets
|
||||
*/
|
||||
class JsModule extends BaseAsset
|
||||
{
|
||||
/**
|
||||
* Js constructor.
|
||||
* @param array $elements
|
||||
* @param string|null $key
|
||||
*/
|
||||
public function __construct(array $elements = [], ?string $key = null)
|
||||
{
|
||||
$base_options = [
|
||||
'asset_type' => 'js_module',
|
||||
'attributes' => ['type' => 'module']
|
||||
];
|
||||
|
||||
$merged_attributes = Utils::arrayMergeRecursiveUnique($base_options, $elements);
|
||||
|
||||
parent::__construct($merged_attributes, $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
if (isset($this->attributes['loading']) && $this->attributes['loading'] === 'inline') {
|
||||
$buffer = $this->gatherLinks([$this], self::JS_MODULE_ASSET);
|
||||
return '<script' . $this->renderAttributes() . ">\n" . trim($buffer) . "\n</script>\n";
|
||||
}
|
||||
|
||||
return '<script src="' . trim($this->asset) . $this->renderQueryString() . '"' . $this->renderAttributes() . $this->integrityHash($this->asset) . "></script>\n";
|
||||
}
|
||||
}
|
||||
43
system/src/Grav/Common/Assets/Link.php
Normal file
43
system/src/Grav/Common/Assets/Link.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Assets
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Assets;
|
||||
|
||||
use Grav\Common\Utils;
|
||||
|
||||
/**
|
||||
* Class Link
|
||||
* @package Grav\Common\Assets
|
||||
*/
|
||||
class Link extends BaseAsset
|
||||
{
|
||||
/**
|
||||
* Css constructor.
|
||||
* @param array $elements
|
||||
* @param string|null $key
|
||||
*/
|
||||
public function __construct(array $elements = [], ?string $key = null)
|
||||
{
|
||||
$base_options = [
|
||||
'asset_type' => 'link',
|
||||
];
|
||||
|
||||
$merged_attributes = Utils::arrayMergeRecursiveUnique($base_options, $elements);
|
||||
|
||||
parent::__construct($merged_attributes, $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
return '<link href="' . trim($this->asset) . $this->renderQueryString() . '"' . $this->renderAttributes() . $this->integrityHash($this->asset) . ">\n";
|
||||
}
|
||||
}
|
||||
@@ -29,12 +29,16 @@ class Pipeline extends PropertyObject
|
||||
{
|
||||
use AssetUtilsTrait;
|
||||
|
||||
protected const CSS_ASSET = true;
|
||||
protected const JS_ASSET = false;
|
||||
protected const CSS_ASSET = 1;
|
||||
protected const JS_ASSET = 2;
|
||||
protected const JS_MODULE_ASSET = 3;
|
||||
|
||||
/** @const Regex to match CSS urls */
|
||||
protected const CSS_URL_REGEX = '{url\(([\'\"]?)(.*?)\1\)}';
|
||||
|
||||
/** @const Regex to match JS imports */
|
||||
protected const JS_IMPORT_REGEX = '{import.+from\s?[\'|\"](.+?)[\'|\"]}';
|
||||
|
||||
/** @const Regex to match CSS sourcemap comments */
|
||||
protected const CSS_SOURCEMAP_REGEX = '{\/\*# (.*?) \*\/}';
|
||||
|
||||
@@ -169,7 +173,7 @@ class Pipeline extends PropertyObject
|
||||
* @param array $attributes
|
||||
* @return bool|string URL or generated content if available, else false
|
||||
*/
|
||||
public function renderJs($assets, $group, $attributes = [])
|
||||
public function renderJs($assets, $group, $attributes = [], $type = self::JS_ASSET)
|
||||
{
|
||||
// temporary list of assets to pipeline
|
||||
$inline_group = false;
|
||||
@@ -198,7 +202,7 @@ class Pipeline extends PropertyObject
|
||||
}
|
||||
|
||||
// Concatenate files
|
||||
$buffer = $this->gatherLinks($assets, self::JS_ASSET);
|
||||
$buffer = $this->gatherLinks($assets, $type);
|
||||
|
||||
// Minify if required
|
||||
if ($this->shouldMinify('js')) {
|
||||
@@ -223,6 +227,19 @@ class Pipeline extends PropertyObject
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minify and concatenate JS files.
|
||||
*
|
||||
* @param array $assets
|
||||
* @param string $group
|
||||
* @param array $attributes
|
||||
* @return bool|string URL or generated content if available, else false
|
||||
*/
|
||||
public function renderJs_Module($assets, $group, $attributes = [])
|
||||
{
|
||||
$attributes['type'] = 'module';
|
||||
return $this->renderJs($assets, $group, $attributes, self::JS_MODULE_ASSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds relative CSS urls() and rewrites the URL with an absolute one
|
||||
@@ -262,6 +279,42 @@ class Pipeline extends PropertyObject
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds relative JS urls() and rewrites the URL with an absolute one
|
||||
*
|
||||
* @param string $file the css source file
|
||||
* @param string $dir , $local relative path to the css file
|
||||
* @param bool $local is this a local or remote asset
|
||||
* @return string
|
||||
*/
|
||||
protected function jsRewrite($file, $dir, $local)
|
||||
{
|
||||
// Find any js import elements, grab the URLs and calculate an absolute path
|
||||
// Then replace the old url with the new one
|
||||
$file = (string)preg_replace_callback(self::JS_IMPORT_REGEX, function ($matches) use ($dir, $local) {
|
||||
|
||||
$old_url = $matches[1];
|
||||
|
||||
// Ensure link is not rooted to web server, a data URL, or to a remote host
|
||||
if (preg_match(self::FIRST_FORWARDSLASH_REGEX, $old_url) || $this->isRemoteLink($old_url)) {
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
// clean leading /
|
||||
$old_url = Utils::normalizePath($dir . '/' . $old_url);
|
||||
$old_url = str_replace('/./', '/', $old_url);
|
||||
if (preg_match(self::FIRST_FORWARDSLASH_REGEX, $old_url)) {
|
||||
$old_url = ltrim($old_url, '/');
|
||||
}
|
||||
|
||||
$new_url = ($local ? $this->base_url : '') . $old_url;
|
||||
|
||||
return str_replace($matches[1], $new_url, $matches[0]);
|
||||
}, $file);
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
* @return bool
|
||||
|
||||
@@ -62,13 +62,13 @@ trait AssetUtilsTrait
|
||||
* Download and concatenate the content of several links.
|
||||
*
|
||||
* @param array $assets
|
||||
* @param bool $css
|
||||
* @param int $type
|
||||
* @return string
|
||||
*/
|
||||
protected function gatherLinks(array $assets, $css = true)
|
||||
protected function gatherLinks(array $assets, int $type = self::CSS_ASSET): string
|
||||
{
|
||||
$buffer = '';
|
||||
foreach ($assets as $id => $asset) {
|
||||
foreach ($assets as $asset) {
|
||||
$local = true;
|
||||
|
||||
$link = $asset->getAsset();
|
||||
@@ -100,21 +100,25 @@ trait AssetUtilsTrait
|
||||
}
|
||||
|
||||
// Double check last character being
|
||||
if (!$css) {
|
||||
if ($type === self::CSS_ASSET) {
|
||||
$file = rtrim($file, ' ;') . ';';
|
||||
}
|
||||
|
||||
// If this is CSS + the file is local + rewrite enabled
|
||||
if ($css && $this->css_rewrite) {
|
||||
if ($type === self::CSS_ASSET && $this->css_rewrite) {
|
||||
$file = $this->cssRewrite($file, $relative_dir, $local);
|
||||
}
|
||||
|
||||
if ($type === self::JS_MODULE_ASSET) {
|
||||
$file = $this->jsRewrite($file, $relative_dir, $local);
|
||||
}
|
||||
|
||||
$file = rtrim($file) . PHP_EOL;
|
||||
$buffer .= $file;
|
||||
}
|
||||
|
||||
// Pull out @imports and move to top
|
||||
if ($css) {
|
||||
if ($type === self::CSS_ASSET) {
|
||||
$buffer = $this->moveImports($buffer);
|
||||
}
|
||||
|
||||
|
||||
@@ -285,6 +285,15 @@ trait TestingAssetsTrait
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Add JavaScript Module files
|
||||
if ($pattern === self::JS_MODULE_REGEX) {
|
||||
foreach ($files as $file) {
|
||||
$this->addJsModule($file);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Unknown pattern.
|
||||
foreach ($files as $asset) {
|
||||
$this->add($asset);
|
||||
|
||||
@@ -729,18 +729,36 @@ class Grav extends Container
|
||||
*/
|
||||
public function fallbackUrl($path)
|
||||
{
|
||||
$this->fireEvent('onPageFallBackUrl');
|
||||
|
||||
/** @var Uri $uri */
|
||||
$uri = $this['uri'];
|
||||
|
||||
/** @var Config $config */
|
||||
$config = $this['config'];
|
||||
|
||||
$path_parts = pathinfo($path);
|
||||
|
||||
/** @var Pages $pages */
|
||||
$pages = $this['pages'];
|
||||
$page = $pages->find($path_parts['dirname'], true);
|
||||
|
||||
$uri_extension = strtolower($uri->extension() ?? '');
|
||||
$fallback_types = $config->get('system.media.allowed_fallback_types', null);
|
||||
$fallback_types = $config->get('system.media.allowed_fallback_types');
|
||||
$supported_types = $config->get('media.types');
|
||||
|
||||
$parsed_url = parse_url(rawurldecode($uri->basename()));
|
||||
$media_file = $parsed_url['path'];
|
||||
|
||||
$event = new Event([
|
||||
'uri' => $uri,
|
||||
'page' => &$page,
|
||||
'filename' => &$media_file,
|
||||
'extension' => $uri_extension,
|
||||
'allowed_fallback_types' => &$fallback_types,
|
||||
'media_types' => &$supported_types
|
||||
]);
|
||||
|
||||
$this->fireEvent('onPageFallBackUrl', $event);
|
||||
|
||||
// Check whitelist first, then ensure extension is a valid media type
|
||||
if (!empty($fallback_types) && !in_array($uri_extension, $fallback_types, true)) {
|
||||
return false;
|
||||
@@ -749,16 +767,8 @@ class Grav extends Container
|
||||
return false;
|
||||
}
|
||||
|
||||
$path_parts = pathinfo($path);
|
||||
|
||||
/** @var Pages $pages */
|
||||
$pages = $this['pages'];
|
||||
$page = $pages->find($path_parts['dirname'], true);
|
||||
|
||||
if ($page) {
|
||||
$media = $page->media()->all();
|
||||
$parsed_url = parse_url(rawurldecode($uri->basename()));
|
||||
$media_file = $parsed_url['path'];
|
||||
|
||||
// if this is a media object, try actions first
|
||||
if (isset($media[$media_file])) {
|
||||
|
||||
@@ -63,6 +63,14 @@ interface MediaObjectInterface extends \Grav\Framework\Media\Interfaces\MediaObj
|
||||
*/
|
||||
public function addAlternative($ratio, MediaObjectInterface $alternative);
|
||||
|
||||
/**
|
||||
* Get list of image alternatives. Includes the current media image as well.
|
||||
*
|
||||
* @param bool $withDerived If true, include generated images as well. If false, only return existing files.
|
||||
* @return array
|
||||
*/
|
||||
public function getAlternatives(bool $withDerived = true): array;
|
||||
|
||||
/**
|
||||
* Return string representation of the object (html).
|
||||
*
|
||||
|
||||
@@ -128,11 +128,29 @@ trait MediaObjectTrait
|
||||
}
|
||||
|
||||
$alternative->set('ratio', $ratio);
|
||||
$width = $alternative->get('width');
|
||||
$width = $alternative->get('width', 0);
|
||||
|
||||
$this->alternatives[$width] = $alternative;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $withDerived
|
||||
* @return array
|
||||
*/
|
||||
public function getAlternatives(bool $withDerived = true): array
|
||||
{
|
||||
$alternatives = [];
|
||||
foreach ($this->alternatives + [$this->get('width', 0) => $this] as $size => $alternative) {
|
||||
if ($withDerived || $alternative->filename === basename($alternative->filepath)) {
|
||||
$alternatives[$size] = $alternative;
|
||||
}
|
||||
}
|
||||
|
||||
ksort($alternatives, SORT_NUMERIC);
|
||||
|
||||
return $alternatives;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return string representation of the object (html).
|
||||
*
|
||||
|
||||
@@ -60,6 +60,46 @@ class Media extends AbstractMedia
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return raw route to the page.
|
||||
*
|
||||
* @return string|null Route to the page or null if media isn't for a page.
|
||||
*/
|
||||
public function getRawRoute(): ?string
|
||||
{
|
||||
$path = $this->getPath();
|
||||
if ($path) {
|
||||
/** @var Pages $pages */
|
||||
$pages = $this->getGrav()['pages'];
|
||||
$page = $pages->get($path);
|
||||
if ($page) {
|
||||
return $page->rawRoute();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return page route.
|
||||
*
|
||||
* @return string|null Route to the page or null if media isn't for a page.
|
||||
*/
|
||||
public function getRoute(): ?string
|
||||
{
|
||||
$path = $this->getPath();
|
||||
if ($path) {
|
||||
/** @var Pages $pages */
|
||||
$pages = $this->getGrav()['pages'];
|
||||
$page = $pages->get($path);
|
||||
if ($page) {
|
||||
return $page->route();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $offset
|
||||
* @return bool
|
||||
|
||||
@@ -23,6 +23,8 @@ use Grav\Common\Media\Traits\MediaObjectTrait;
|
||||
* @package Grav\Common\Page\Medium
|
||||
*
|
||||
* @property string $filepath
|
||||
* @property string $filename
|
||||
* @property string $basename
|
||||
* @property string $mime
|
||||
* @property int $size
|
||||
* @property int $modified
|
||||
|
||||
@@ -193,7 +193,7 @@ class MediumFactory
|
||||
$height = $medium->get('height') * $ratio;
|
||||
|
||||
$prev_basename = $medium->get('basename');
|
||||
$basename = str_replace('@'.$from.'x', '@'.$to.'x', $prev_basename);
|
||||
$basename = str_replace('@' . $from . 'x', $to !== 1 ? '@' . $to . 'x' : '', $prev_basename);
|
||||
|
||||
$debug = $medium->get('debug');
|
||||
$medium->set('debug', false);
|
||||
@@ -208,6 +208,8 @@ class MediumFactory
|
||||
|
||||
$medium = self::fromFile($file);
|
||||
if ($medium) {
|
||||
$medium->set('basename', $basename);
|
||||
$medium->set('filename', $basename . '.' . $medium->extension);
|
||||
$medium->set('size', $size);
|
||||
}
|
||||
|
||||
|
||||
@@ -37,4 +37,12 @@ class StaticImageMedium extends Medium implements ImageMediaInterface
|
||||
|
||||
return ['name' => 'img', 'attributes' => $attributes];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function higherQualityAlternative()
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -653,16 +653,17 @@ abstract class Utils
|
||||
* @param bool $force_download as opposed to letting browser choose if to download or render
|
||||
* @param int $sec Throttling, try 0.1 for some speed throttling of downloads
|
||||
* @param int $bytes Size of chunks to send in bytes. Default is 1024
|
||||
* @param array $options Extra options: [mime, download_name, expires]
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function download($file, $force_download = true, $sec = 0, $bytes = 1024)
|
||||
public static function download($file, $force_download = true, $sec = 0, $bytes = 1024, array $options = [])
|
||||
{
|
||||
if (file_exists($file)) {
|
||||
// fire download event
|
||||
Grav::instance()->fireEvent('onBeforeDownload', new Event(['file' => $file]));
|
||||
Grav::instance()->fireEvent('onBeforeDownload', new Event(['file' => $file, 'options' => &$options]));
|
||||
|
||||
$file_parts = pathinfo($file);
|
||||
$mimetype = static::getMimeByExtension($file_parts['extension']);
|
||||
$mimetype = $options['mime'] ?? static::getMimeByExtension($file_parts['extension']);
|
||||
$size = filesize($file); // File size
|
||||
|
||||
// clean all buffers
|
||||
@@ -680,7 +681,7 @@ abstract class Utils
|
||||
|
||||
if ($force_download) {
|
||||
// output the regular HTTP headers
|
||||
header('Content-Disposition: attachment; filename="' . $file_parts['basename'] . '"');
|
||||
header('Content-Disposition: attachment; filename="' . ($options['download_name'] ?? $file_parts['basename']) . '"');
|
||||
}
|
||||
|
||||
// multipart-download and download resuming support
|
||||
@@ -704,7 +705,7 @@ abstract class Utils
|
||||
header('Content-Length: ' . $size);
|
||||
|
||||
if (Grav::instance()['config']->get('system.cache.enabled')) {
|
||||
$expires = Grav::instance()['config']->get('system.pages.expires');
|
||||
$expires = $options['expires'] ?? Grav::instance()['config']->get('system.pages.expires');
|
||||
if ($expires > 0) {
|
||||
$expires_date = gmdate('D, d M Y H:i:s T', time() + $expires);
|
||||
header('Cache-Control: max-age=' . $expires);
|
||||
@@ -830,6 +831,31 @@ abstract class Utils
|
||||
return $mimetypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all extensions for given mimetype. The first extension is the default one.
|
||||
*
|
||||
* @param string $mime Mime type (eg 'image/jpeg')
|
||||
* @return string[] List of extensions eg. ['jpg', 'jpe', 'jpeg']
|
||||
*/
|
||||
public static function getExtensionsByMime($mime)
|
||||
{
|
||||
$mime = strtolower($mime);
|
||||
|
||||
$media_types = (array)Grav::instance()['config']->get('media.types');
|
||||
|
||||
$list = [];
|
||||
foreach ($media_types as $extension => $type) {
|
||||
if ($extension === '' || $extension === 'defaults') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($type['mime']) && $type['mime'] === $mime) {
|
||||
$list[] = $extension;
|
||||
}
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mimetype based on filename extension
|
||||
|
||||
@@ -10,11 +10,14 @@
|
||||
namespace Grav\Console\Application;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Symfony\Component\Console\ConsoleEvents;
|
||||
use Symfony\Component\Console\Event\ConsoleCommandEvent;
|
||||
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
|
||||
use Symfony\Component\Console\Input\InputDefinition;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
|
||||
/**
|
||||
* Class GpmApplication
|
||||
@@ -29,19 +32,48 @@ class Application extends \Symfony\Component\Console\Application
|
||||
/** @var bool */
|
||||
protected $initialized = false;
|
||||
|
||||
/**
|
||||
* PluginApplication constructor.
|
||||
* @param string $name
|
||||
* @param string $version
|
||||
*/
|
||||
public function __construct(string $name = 'UNKNOWN', string $version = 'UNKNOWN')
|
||||
{
|
||||
parent::__construct($name, $version);
|
||||
|
||||
// Add listener to prepare environment.
|
||||
$dispatcher = new EventDispatcher();
|
||||
$dispatcher->addListener(ConsoleEvents::COMMAND, [$this, 'prepareEnvironment']);
|
||||
|
||||
$this->setDispatcher($dispatcher);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InputInterface $input
|
||||
* @return string|null
|
||||
*/
|
||||
public function getCommandName(InputInterface $input): ?string
|
||||
{
|
||||
$this->environment = $input->getOption('env');
|
||||
$this->language = $input->getOption('lang') ?? $this->language;
|
||||
if ($input->hasParameterOption('--env', true)) {
|
||||
$this->environment = $input->getParameterOption('--env');
|
||||
}
|
||||
if ($input->hasParameterOption('--lang', true)) {
|
||||
$this->language = $input->getParameterOption('--lang');
|
||||
}
|
||||
|
||||
$this->init();
|
||||
|
||||
return parent::getCommandName($input);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ConsoleCommandEvent $event
|
||||
* @return void
|
||||
*/
|
||||
public function prepareEnvironment(ConsoleCommandEvent $event): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
@@ -58,7 +90,7 @@ class Application extends \Symfony\Component\Console\Application
|
||||
}
|
||||
|
||||
/**
|
||||
* Add global a --env option.
|
||||
* Add global --env and --lang options.
|
||||
*
|
||||
* @return InputDefinition
|
||||
*/
|
||||
@@ -67,16 +99,16 @@ class Application extends \Symfony\Component\Console\Application
|
||||
$inputDefinition = parent::getDefaultInputDefinition();
|
||||
$inputDefinition->addOption(
|
||||
new InputOption(
|
||||
'env',
|
||||
null,
|
||||
'--env',
|
||||
'',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
'Use environment configuration (defaults to localhost)'
|
||||
)
|
||||
);
|
||||
$inputDefinition->addOption(
|
||||
new InputOption(
|
||||
'lang',
|
||||
null,
|
||||
'--lang',
|
||||
'',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
'Language to be used (defaults to en)'
|
||||
)
|
||||
|
||||
@@ -77,8 +77,6 @@ class SchedulerCommand extends GravCommand
|
||||
$scheduler = $grav['scheduler'];
|
||||
$grav->fireEvent('onSchedulerInitialized', new Event(['scheduler' => $scheduler]));
|
||||
|
||||
$this->setHelp('foo');
|
||||
|
||||
$input = $this->getInput();
|
||||
$io = $this->getIO();
|
||||
$error = 0;
|
||||
|
||||
@@ -29,6 +29,7 @@ assets:
|
||||
css_minify: true
|
||||
css_rewrite: true
|
||||
js_pipeline: false
|
||||
js_module_pipeline: false
|
||||
js_minify: true
|
||||
|
||||
errors:
|
||||
|
||||
Reference in New Issue
Block a user