updated core to 1.7.15
This commit is contained in:
258
system/src/Grav/Common/Assets/BaseAsset.php
Normal file
258
system/src/Grav/Common/Assets/BaseAsset.php
Normal file
@@ -0,0 +1,258 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Assets
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Assets;
|
||||
|
||||
use Grav\Common\Assets\Traits\AssetUtilsTrait;
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Uri;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Framework\Object\PropertyObject;
|
||||
use SplFileInfo;
|
||||
|
||||
/**
|
||||
* Class BaseAsset
|
||||
* @package Grav\Common\Assets
|
||||
*/
|
||||
abstract class BaseAsset extends PropertyObject
|
||||
{
|
||||
use AssetUtilsTrait;
|
||||
|
||||
protected const CSS_ASSET = true;
|
||||
protected const JS_ASSET = false;
|
||||
|
||||
/** @var string|false */
|
||||
protected $asset;
|
||||
/** @var string */
|
||||
protected $asset_type;
|
||||
/** @var int */
|
||||
protected $order;
|
||||
/** @var string */
|
||||
protected $group;
|
||||
/** @var string */
|
||||
protected $position;
|
||||
/** @var int */
|
||||
protected $priority;
|
||||
/** @var array */
|
||||
protected $attributes = [];
|
||||
|
||||
/** @var string */
|
||||
protected $timestamp;
|
||||
/** @var int|false */
|
||||
protected $modified;
|
||||
/** @var bool */
|
||||
protected $remote;
|
||||
/** @var string */
|
||||
protected $query = '';
|
||||
|
||||
// Private Bits
|
||||
/** @var bool */
|
||||
private $css_rewrite = false;
|
||||
/** @var bool */
|
||||
private $css_minify = false;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
abstract function render();
|
||||
|
||||
/**
|
||||
* BaseAsset constructor.
|
||||
* @param array $elements
|
||||
* @param string|null $key
|
||||
*/
|
||||
public function __construct(array $elements = [], $key = null)
|
||||
{
|
||||
$base_config = [
|
||||
'group' => 'head',
|
||||
'position' => 'pipeline',
|
||||
'priority' => 10,
|
||||
'modified' => null,
|
||||
'asset' => null
|
||||
];
|
||||
|
||||
// Merge base defaults
|
||||
$elements = array_merge($base_config, $elements);
|
||||
|
||||
parent::__construct($elements, $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|false $asset
|
||||
* @param array $options
|
||||
* @return $this|false
|
||||
*/
|
||||
public function init($asset, $options)
|
||||
{
|
||||
$config = Grav::instance()['config'];
|
||||
$uri = Grav::instance()['uri'];
|
||||
|
||||
// set attributes
|
||||
foreach ($options as $key => $value) {
|
||||
if ($this->hasProperty($key)) {
|
||||
$this->setProperty($key, $value);
|
||||
} else {
|
||||
$this->attributes[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
// Force priority to be an int
|
||||
$this->priority = (int) $this->priority;
|
||||
|
||||
// Do some special stuff for CSS/JS (not inline)
|
||||
if (!Utils::startsWith($this->getType(), 'inline')) {
|
||||
$this->base_url = rtrim($uri->rootUrl($config->get('system.absolute_urls')), '/') . '/';
|
||||
$this->remote = static::isRemoteLink($asset);
|
||||
|
||||
// Move this to render?
|
||||
if (!$this->remote) {
|
||||
$asset_parts = parse_url($asset);
|
||||
if (isset($asset_parts['query'])) {
|
||||
$this->query = $asset_parts['query'];
|
||||
unset($asset_parts['query']);
|
||||
$asset = Uri::buildUrl($asset_parts);
|
||||
}
|
||||
|
||||
$locator = Grav::instance()['locator'];
|
||||
|
||||
if ($locator->isStream($asset)) {
|
||||
$path = $locator->findResource($asset, true);
|
||||
} else {
|
||||
$path = GRAV_WEBROOT . $asset;
|
||||
}
|
||||
|
||||
// If local file is missing return
|
||||
if ($path === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$file = new SplFileInfo($path);
|
||||
|
||||
$asset = $this->buildLocalLink($file->getPathname());
|
||||
|
||||
$this->modified = $file->isFile() ? $file->getMTime() : false;
|
||||
}
|
||||
}
|
||||
|
||||
$this->asset = $asset;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|false
|
||||
*/
|
||||
public function getAsset()
|
||||
{
|
||||
return $this->asset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function getRemote()
|
||||
{
|
||||
return $this->remote;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $position
|
||||
* @return $this
|
||||
*/
|
||||
public function setPosition($position)
|
||||
{
|
||||
$this->position = $position;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive asset location and return the SRI integrity hash
|
||||
*
|
||||
* @param string $input
|
||||
* @return string
|
||||
*/
|
||||
public static function integrityHash($input)
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
|
||||
$assetsConfig = $grav['config']->get('system.assets');
|
||||
|
||||
if ( !empty($assetsConfig['enable_asset_sri']) && $assetsConfig['enable_asset_sri'] )
|
||||
{
|
||||
$dataToHash = file_get_contents( GRAV_WEBROOT . $input);
|
||||
|
||||
$hash = hash('sha256', $dataToHash, true);
|
||||
$hash_base64 = base64_encode($hash);
|
||||
return ' integrity="sha256-' . $hash_base64 . '"';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* Get the last modification time of asset
|
||||
*
|
||||
* @param string $asset the asset string reference
|
||||
*
|
||||
* @return string the last modifcation time or false on error
|
||||
*/
|
||||
// protected function getLastModificationTime($asset)
|
||||
// {
|
||||
// $file = GRAV_WEBROOT . $asset;
|
||||
// if (Grav::instance()['locator']->isStream($asset)) {
|
||||
// $file = $this->buildLocalLink($asset, true);
|
||||
// }
|
||||
//
|
||||
// return file_exists($file) ? filemtime($file) : false;
|
||||
// }
|
||||
|
||||
/**
|
||||
*
|
||||
* Build local links including grav asset shortcodes
|
||||
*
|
||||
* @param string $asset the asset string reference
|
||||
*
|
||||
* @return string|false the final link url to the asset
|
||||
*/
|
||||
protected function buildLocalLink($asset)
|
||||
{
|
||||
if ($asset) {
|
||||
return $this->base_url . ltrim(Utils::replaceFirstOccurrence(GRAV_WEBROOT, '', $asset), '/');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Implements JsonSerializable interface.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return ['type' => $this->getType(), 'elements' => $this->getElements()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Placeholder for AssetUtilsTrait method
|
||||
*
|
||||
* @param string $file
|
||||
* @param string $dir
|
||||
* @param bool $local
|
||||
* @return string
|
||||
*/
|
||||
protected function cssRewrite($file, $dir, $local)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
52
system/src/Grav/Common/Assets/Css.php
Normal file
52
system/src/Grav/Common/Assets/Css.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Assets
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Assets;
|
||||
|
||||
use Grav\Common\Utils;
|
||||
|
||||
/**
|
||||
* Class Css
|
||||
* @package Grav\Common\Assets
|
||||
*/
|
||||
class Css extends BaseAsset
|
||||
{
|
||||
/**
|
||||
* Css constructor.
|
||||
* @param array $elements
|
||||
* @param string|null $key
|
||||
*/
|
||||
public function __construct(array $elements = [], $key = null)
|
||||
{
|
||||
$base_options = [
|
||||
'asset_type' => 'css',
|
||||
'attributes' => [
|
||||
'type' => 'text/css',
|
||||
'rel' => 'stylesheet'
|
||||
]
|
||||
];
|
||||
|
||||
$merged_attributes = Utils::arrayMergeRecursiveUnique($base_options, $elements);
|
||||
|
||||
parent::__construct($merged_attributes, $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
if (isset($this->attributes['loading']) && $this->attributes['loading'] === 'inline') {
|
||||
$buffer = $this->gatherLinks([$this], self::CSS_ASSET);
|
||||
return "<style>\n" . trim($buffer) . "\n</style>\n";
|
||||
}
|
||||
|
||||
return '<link href="' . trim($this->asset) . $this->renderQueryString() . '"' . $this->renderAttributes() . $this->integrityHash($this->asset) . ">\n";
|
||||
}
|
||||
}
|
||||
44
system/src/Grav/Common/Assets/InlineCss.php
Normal file
44
system/src/Grav/Common/Assets/InlineCss.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Assets
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Assets;
|
||||
|
||||
use Grav\Common\Utils;
|
||||
|
||||
/**
|
||||
* Class InlineCss
|
||||
* @package Grav\Common\Assets
|
||||
*/
|
||||
class InlineCss extends BaseAsset
|
||||
{
|
||||
/**
|
||||
* InlineCss constructor.
|
||||
* @param array $elements
|
||||
* @param string|null $key
|
||||
*/
|
||||
public function __construct(array $elements = [], $key = null)
|
||||
{
|
||||
$base_options = [
|
||||
'asset_type' => 'css',
|
||||
'position' => 'after'
|
||||
];
|
||||
|
||||
$merged_attributes = Utils::arrayMergeRecursiveUnique($base_options, $elements);
|
||||
|
||||
parent::__construct($merged_attributes, $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
return '<style' . $this->renderAttributes(). ">\n" . trim($this->asset) . "\n</style>\n";
|
||||
}
|
||||
}
|
||||
44
system/src/Grav/Common/Assets/InlineJs.php
Normal file
44
system/src/Grav/Common/Assets/InlineJs.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Assets
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2021 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 InlineJs extends BaseAsset
|
||||
{
|
||||
/**
|
||||
* InlineJs constructor.
|
||||
* @param array $elements
|
||||
* @param string|null $key
|
||||
*/
|
||||
public function __construct(array $elements = [], $key = null)
|
||||
{
|
||||
$base_options = [
|
||||
'asset_type' => 'js',
|
||||
'position' => 'after'
|
||||
];
|
||||
|
||||
$merged_attributes = Utils::arrayMergeRecursiveUnique($base_options, $elements);
|
||||
|
||||
parent::__construct($merged_attributes, $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
return '<script' . $this->renderAttributes(). ">\n" . trim($this->asset) . "\n</script>\n";
|
||||
}
|
||||
}
|
||||
48
system/src/Grav/Common/Assets/Js.php
Normal file
48
system/src/Grav/Common/Assets/Js.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Assets
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2021 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 Js extends BaseAsset
|
||||
{
|
||||
/**
|
||||
* Js constructor.
|
||||
* @param array $elements
|
||||
* @param string|null $key
|
||||
*/
|
||||
public function __construct(array $elements = [], $key = null)
|
||||
{
|
||||
$base_options = [
|
||||
'asset_type' => 'js',
|
||||
];
|
||||
|
||||
$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_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";
|
||||
}
|
||||
}
|
||||
280
system/src/Grav/Common/Assets/Pipeline.php
Normal file
280
system/src/Grav/Common/Assets/Pipeline.php
Normal file
@@ -0,0 +1,280 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Assets
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Assets;
|
||||
|
||||
use Grav\Common\Assets\BaseAsset;
|
||||
use Grav\Common\Assets\Traits\AssetUtilsTrait;
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Uri;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Framework\Object\PropertyObject;
|
||||
use MatthiasMullie\Minify\CSS;
|
||||
use MatthiasMullie\Minify\JS;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
use function array_key_exists;
|
||||
|
||||
/**
|
||||
* Class Pipeline
|
||||
* @package Grav\Common\Assets
|
||||
*/
|
||||
class Pipeline extends PropertyObject
|
||||
{
|
||||
use AssetUtilsTrait;
|
||||
|
||||
protected const CSS_ASSET = true;
|
||||
protected const JS_ASSET = false;
|
||||
|
||||
/** @const Regex to match CSS urls */
|
||||
protected const CSS_URL_REGEX = '{url\(([\'\"]?)(.*?)\1\)}';
|
||||
|
||||
/** @const Regex to match CSS sourcemap comments */
|
||||
protected const CSS_SOURCEMAP_REGEX = '{\/\*# (.*?) \*\/}';
|
||||
|
||||
protected const FIRST_FORWARDSLASH_REGEX = '{^\/{1}\w}';
|
||||
|
||||
// Following variables come from the configuration:
|
||||
/** @var bool */
|
||||
protected $css_minify = false;
|
||||
/** @var bool */
|
||||
protected $css_minify_windows = false;
|
||||
/** @var bool */
|
||||
protected $css_rewrite = false;
|
||||
/** @var bool */
|
||||
protected $css_pipeline_include_externals = true;
|
||||
/** @var bool */
|
||||
protected $js_minify = false;
|
||||
/** @var bool */
|
||||
protected $js_minify_windows = false;
|
||||
/** @var bool */
|
||||
protected $js_pipeline_include_externals = true;
|
||||
|
||||
/** @var string */
|
||||
protected $assets_dir;
|
||||
/** @var string */
|
||||
protected $assets_url;
|
||||
/** @var string */
|
||||
protected $timestamp;
|
||||
/** @var array */
|
||||
protected $attributes;
|
||||
/** @var string */
|
||||
protected $query = '';
|
||||
/** @var string */
|
||||
protected $asset;
|
||||
|
||||
/**
|
||||
* Pipeline constructor.
|
||||
* @param array $elements
|
||||
* @param string|null $key
|
||||
*/
|
||||
public function __construct(array $elements = [], ?string $key = null)
|
||||
{
|
||||
parent::__construct($elements, $key);
|
||||
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
|
||||
/** @var Config $config */
|
||||
$config = Grav::instance()['config'];
|
||||
|
||||
/** @var Uri $uri */
|
||||
$uri = Grav::instance()['uri'];
|
||||
|
||||
$this->base_url = rtrim($uri->rootUrl($config->get('system.absolute_urls')), '/') . '/';
|
||||
$this->assets_dir = $locator->findResource('asset://') . DS;
|
||||
$this->assets_url = $locator->findResource('asset://', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Minify and concatenate CSS
|
||||
*
|
||||
* @param array $assets
|
||||
* @param string $group
|
||||
* @param array $attributes
|
||||
* @return bool|string URL or generated content if available, else false
|
||||
*/
|
||||
public function renderCss($assets, $group, $attributes = [])
|
||||
{
|
||||
// temporary list of assets to pipeline
|
||||
$inline_group = false;
|
||||
|
||||
if (array_key_exists('loading', $attributes) && $attributes['loading'] === 'inline') {
|
||||
$inline_group = true;
|
||||
unset($attributes['loading']);
|
||||
}
|
||||
|
||||
// Store Attributes
|
||||
$this->attributes = array_merge(['type' => 'text/css', 'rel' => 'stylesheet'], $attributes);
|
||||
|
||||
// Compute uid based on assets and timestamp
|
||||
$json_assets = json_encode($assets);
|
||||
$uid = md5($json_assets . $this->css_minify . $this->css_rewrite . $group);
|
||||
$file = $uid . '.css';
|
||||
$relative_path = "{$this->base_url}{$this->assets_url}/{$file}";
|
||||
|
||||
$buffer = null;
|
||||
|
||||
if (file_exists($this->assets_dir . $file)) {
|
||||
$buffer = file_get_contents($this->assets_dir . $file) . "\n";
|
||||
} else {
|
||||
//if nothing found get out of here!
|
||||
if (empty($assets)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Concatenate files
|
||||
$buffer = $this->gatherLinks($assets, self::CSS_ASSET);
|
||||
|
||||
// Minify if required
|
||||
if ($this->shouldMinify('css')) {
|
||||
$minifier = new CSS();
|
||||
$minifier->add($buffer);
|
||||
$buffer = $minifier->minify();
|
||||
}
|
||||
|
||||
// Write file
|
||||
if (trim($buffer) !== '') {
|
||||
file_put_contents($this->assets_dir . $file, $buffer);
|
||||
}
|
||||
}
|
||||
|
||||
if ($inline_group) {
|
||||
$output = "<style>\n" . $buffer . "\n</style>\n";
|
||||
} else {
|
||||
$this->asset = $relative_path;
|
||||
$output = '<link href="' . $relative_path . $this->renderQueryString() . '"' . $this->renderAttributes() . BaseAsset::integrityHash($this->asset) . ">\n";
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minify and concatenate JS files.
|
||||
*
|
||||
* @param array $assets
|
||||
* @param string $group
|
||||
* @param array $attributes
|
||||
* @return bool|string URL or generated content if available, else false
|
||||
*/
|
||||
public function renderJs($assets, $group, $attributes = [])
|
||||
{
|
||||
// temporary list of assets to pipeline
|
||||
$inline_group = false;
|
||||
|
||||
if (array_key_exists('loading', $attributes) && $attributes['loading'] === 'inline') {
|
||||
$inline_group = true;
|
||||
unset($attributes['loading']);
|
||||
}
|
||||
|
||||
// Store Attributes
|
||||
$this->attributes = $attributes;
|
||||
|
||||
// Compute uid based on assets and timestamp
|
||||
$json_assets = json_encode($assets);
|
||||
$uid = md5($json_assets . $this->js_minify . $group);
|
||||
$file = $uid . '.js';
|
||||
$relative_path = "{$this->base_url}{$this->assets_url}/{$file}";
|
||||
|
||||
$buffer = null;
|
||||
|
||||
if (file_exists($this->assets_dir . $file)) {
|
||||
$buffer = file_get_contents($this->assets_dir . $file) . "\n";
|
||||
} else {
|
||||
//if nothing found get out of here!
|
||||
if (empty($assets)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Concatenate files
|
||||
$buffer = $this->gatherLinks($assets, self::JS_ASSET);
|
||||
|
||||
// Minify if required
|
||||
if ($this->shouldMinify('js')) {
|
||||
$minifier = new JS();
|
||||
$minifier->add($buffer);
|
||||
$buffer = $minifier->minify();
|
||||
}
|
||||
|
||||
// Write file
|
||||
if (trim($buffer) !== '') {
|
||||
file_put_contents($this->assets_dir . $file, $buffer);
|
||||
}
|
||||
}
|
||||
|
||||
if ($inline_group) {
|
||||
$output = '<script' . $this->renderAttributes(). ">\n" . $buffer . "\n</script>\n";
|
||||
} else {
|
||||
$this->asset = $relative_path;
|
||||
$output = '<script src="' . $relative_path . $this->renderQueryString() . '"' . $this->renderAttributes() . BaseAsset::integrityHash($this->asset) . "></script>\n";
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds relative CSS urls() and rewrites the URL with an absolute one
|
||||
*
|
||||
* @param string $file the css source file
|
||||
* @param string $dir , $local relative path to the css file
|
||||
* @param bool $local is this a local or remote asset
|
||||
* @return string
|
||||
*/
|
||||
protected function cssRewrite($file, $dir, $local)
|
||||
{
|
||||
// Strip any sourcemap comments
|
||||
$file = preg_replace(self::CSS_SOURCEMAP_REGEX, '', $file);
|
||||
|
||||
// Find any css url() elements, grab the URLs and calculate an absolute path
|
||||
// Then replace the old url with the new one
|
||||
$file = (string)preg_replace_callback(self::CSS_URL_REGEX, function ($matches) use ($dir, $local) {
|
||||
|
||||
$old_url = $matches[2];
|
||||
|
||||
// Ensure link is not rooted to web server, a data URL, or to a remote host
|
||||
if (preg_match(self::FIRST_FORWARDSLASH_REGEX, $old_url) || Utils::startsWith($old_url, 'data:') || $this->isRemoteLink($old_url)) {
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
// clean leading /
|
||||
$old_url = Utils::normalizePath($dir . '/' . $old_url);
|
||||
if (preg_match(self::FIRST_FORWARDSLASH_REGEX, $old_url)) {
|
||||
$old_url = ltrim($old_url, '/');
|
||||
}
|
||||
|
||||
$new_url = ($local ? $this->base_url: '') . $old_url;
|
||||
|
||||
return str_replace($matches[2], $new_url, $matches[0]);
|
||||
}, $file);
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
* @return bool
|
||||
*/
|
||||
private function shouldMinify($type = 'css')
|
||||
{
|
||||
$check = $type . '_minify';
|
||||
$win_check = $type . '_minify_windows';
|
||||
|
||||
$minify = (bool) $this->$check;
|
||||
|
||||
// If this is a Windows server, and minify_windows is false (default value) skip the
|
||||
// minification process because it will cause Apache to die/crash due to insufficient
|
||||
// ThreadStackSize in httpd.conf - See: https://bugs.php.net/bug.php?id=47689
|
||||
if (stripos(php_uname('s'), 'WIN') === 0 && !$this->{$win_check}) {
|
||||
$minify = false;
|
||||
}
|
||||
|
||||
return $minify;
|
||||
}
|
||||
}
|
||||
208
system/src/Grav/Common/Assets/Traits/AssetUtilsTrait.php
Normal file
208
system/src/Grav/Common/Assets/Traits/AssetUtilsTrait.php
Normal file
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Assets\Traits
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Assets\Traits;
|
||||
|
||||
use Closure;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Utils;
|
||||
use function dirname;
|
||||
use function in_array;
|
||||
use function is_array;
|
||||
|
||||
/**
|
||||
* Trait AssetUtilsTrait
|
||||
* @package Grav\Common\Assets\Traits
|
||||
*/
|
||||
trait AssetUtilsTrait
|
||||
{
|
||||
/**
|
||||
* @var Closure|null
|
||||
*
|
||||
* Closure used by the pipeline to fetch assets.
|
||||
*
|
||||
* Useful when file_get_contents() function is not available in your PHP
|
||||
* installation or when you want to apply any kind of preprocessing to
|
||||
* your assets before they get pipelined.
|
||||
*
|
||||
* The closure will receive as the only parameter a string with the path/URL of the asset and
|
||||
* it should return the content of the asset file as a string.
|
||||
*/
|
||||
protected $fetch_command;
|
||||
|
||||
/** @var string */
|
||||
protected $base_url;
|
||||
|
||||
/**
|
||||
* Determine whether a link is local or remote.
|
||||
* Understands both "http://" and "https://" as well as protocol agnostic links "//"
|
||||
*
|
||||
* @param string $link
|
||||
* @return bool
|
||||
*/
|
||||
public static function isRemoteLink($link)
|
||||
{
|
||||
$base = Grav::instance()['uri']->rootUrl(true);
|
||||
|
||||
// Sanity check for local URLs with absolute URL's enabled
|
||||
if (Utils::startsWith($link, $base)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (0 === strpos($link, 'http://') || 0 === strpos($link, 'https://') || 0 === strpos($link, '//'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Download and concatenate the content of several links.
|
||||
*
|
||||
* @param array $assets
|
||||
* @param bool $css
|
||||
* @return string
|
||||
*/
|
||||
protected function gatherLinks(array $assets, $css = true)
|
||||
{
|
||||
$buffer = '';
|
||||
|
||||
|
||||
foreach ($assets as $id => $asset) {
|
||||
$local = true;
|
||||
|
||||
$link = $asset->getAsset();
|
||||
$relative_path = $link;
|
||||
|
||||
if (static::isRemoteLink($link)) {
|
||||
$local = false;
|
||||
if (0 === strpos($link, '//')) {
|
||||
$link = 'http:' . $link;
|
||||
}
|
||||
$relative_dir = dirname($relative_path);
|
||||
} else {
|
||||
// Fix to remove relative dir if grav is in one
|
||||
if (($this->base_url !== '/') && Utils::startsWith($relative_path, $this->base_url)) {
|
||||
$base_url = '#' . preg_quote($this->base_url, '#') . '#';
|
||||
$relative_path = ltrim(preg_replace($base_url, '/', $link, 1), '/');
|
||||
}
|
||||
|
||||
$relative_dir = dirname($relative_path);
|
||||
$link = GRAV_ROOT . '/' . $relative_path;
|
||||
}
|
||||
|
||||
// TODO: looks like this is not being used.
|
||||
$file = $this->fetch_command instanceof Closure ? @$this->fetch_command->__invoke($link) : @file_get_contents($link);
|
||||
|
||||
// No file found, skip it...
|
||||
if ($file === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Double check last character being
|
||||
if (!$css) {
|
||||
$file = rtrim($file, ' ;') . ';';
|
||||
}
|
||||
|
||||
// If this is CSS + the file is local + rewrite enabled
|
||||
if ($css && $this->css_rewrite) {
|
||||
$file = $this->cssRewrite($file, $relative_dir, $local);
|
||||
}
|
||||
|
||||
$file = rtrim($file) . PHP_EOL;
|
||||
$buffer .= $file;
|
||||
}
|
||||
|
||||
// Pull out @imports and move to top
|
||||
if ($css) {
|
||||
$buffer = $this->moveImports($buffer);
|
||||
}
|
||||
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves @import statements to the top of the file per the CSS specification
|
||||
*
|
||||
* @param string $file the file containing the combined CSS files
|
||||
* @return string the modified file with any @imports at the top of the file
|
||||
*/
|
||||
protected function moveImports($file)
|
||||
{
|
||||
$regex = '{@import.*?["\']([^"\']+)["\'].*?;}';
|
||||
|
||||
$imports = [];
|
||||
|
||||
$file = (string)preg_replace_callback($regex, function ($matches) use (&$imports) {
|
||||
$imports[] = $matches[0];
|
||||
|
||||
return '';
|
||||
}, $file);
|
||||
|
||||
return implode("\n", $imports) . "\n\n" . $file;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Build an HTML attribute string from an array.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function renderAttributes()
|
||||
{
|
||||
$html = '';
|
||||
$no_key = ['loading'];
|
||||
|
||||
foreach ($this->attributes as $key => $value) {
|
||||
if (is_numeric($key)) {
|
||||
$key = $value;
|
||||
}
|
||||
if (is_array($value)) {
|
||||
$value = implode(' ', $value);
|
||||
}
|
||||
|
||||
if (in_array($key, $no_key, true)) {
|
||||
$element = htmlentities($value, ENT_QUOTES, 'UTF-8', false);
|
||||
} else {
|
||||
$element = $key . '="' . htmlentities($value, ENT_QUOTES, 'UTF-8', false) . '"';
|
||||
}
|
||||
|
||||
$html .= ' ' . $element;
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render Querystring
|
||||
*
|
||||
* @param string|null $asset
|
||||
* @return string
|
||||
*/
|
||||
protected function renderQueryString($asset = null)
|
||||
{
|
||||
$querystring = '';
|
||||
|
||||
$asset = $asset ?? $this->asset;
|
||||
|
||||
if (!empty($this->query)) {
|
||||
if (Utils::contains($asset, '?')) {
|
||||
$querystring .= '&' . $this->query;
|
||||
} else {
|
||||
$querystring .= '?' . $this->query;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->timestamp) {
|
||||
if (Utils::contains($asset, '?') || $querystring) {
|
||||
$querystring .= '&' . $this->timestamp;
|
||||
} else {
|
||||
$querystring .= '?' . $this->timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
return $querystring;
|
||||
}
|
||||
}
|
||||
137
system/src/Grav/Common/Assets/Traits/LegacyAssetsTrait.php
Normal file
137
system/src/Grav/Common/Assets/Traits/LegacyAssetsTrait.php
Normal file
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Assets\Traits
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Assets\Traits;
|
||||
|
||||
use Grav\Common\Assets;
|
||||
use function count;
|
||||
use function is_array;
|
||||
use function is_int;
|
||||
|
||||
/**
|
||||
* Trait LegacyAssetsTrait
|
||||
* @package Grav\Common\Assets\Traits
|
||||
*/
|
||||
trait LegacyAssetsTrait
|
||||
{
|
||||
/**
|
||||
* @param array $args
|
||||
* @param string $type
|
||||
* @return array
|
||||
*/
|
||||
protected function unifyLegacyArguments($args, $type = Assets::CSS_TYPE)
|
||||
{
|
||||
// First argument is always the asset
|
||||
array_shift($args);
|
||||
|
||||
if (count($args) === 0) {
|
||||
return [];
|
||||
}
|
||||
// New options array format
|
||||
if (count($args) === 1 && is_array($args[0])) {
|
||||
return $args[0];
|
||||
}
|
||||
// Handle obscure case where options array is mixed with a priority
|
||||
if (count($args) === 2 && is_array($args[0]) && is_int($args[1])) {
|
||||
$arguments = $args[0];
|
||||
$arguments['priority'] = $args[1];
|
||||
return $arguments;
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case (Assets::JS_TYPE):
|
||||
$defaults = ['priority' => null, 'pipeline' => true, 'loading' => null, 'group' => null];
|
||||
$arguments = $this->createArgumentsFromLegacy($args, $defaults);
|
||||
break;
|
||||
|
||||
case (Assets::INLINE_JS_TYPE):
|
||||
$defaults = ['priority' => null, 'group' => null, 'attributes' => null];
|
||||
$arguments = $this->createArgumentsFromLegacy($args, $defaults);
|
||||
|
||||
// special case to handle old attributes being passed in
|
||||
if (isset($arguments['attributes'])) {
|
||||
$old_attributes = $arguments['attributes'];
|
||||
if (is_array($old_attributes)) {
|
||||
$arguments = array_merge($arguments, $old_attributes);
|
||||
} else {
|
||||
$arguments['type'] = $old_attributes;
|
||||
}
|
||||
}
|
||||
unset($arguments['attributes']);
|
||||
|
||||
break;
|
||||
|
||||
case (Assets::INLINE_CSS_TYPE):
|
||||
$defaults = ['priority' => null, 'group' => null];
|
||||
$arguments = $this->createArgumentsFromLegacy($args, $defaults);
|
||||
break;
|
||||
|
||||
default:
|
||||
case (Assets::CSS_TYPE):
|
||||
$defaults = ['priority' => null, 'pipeline' => true, 'group' => null, 'loading' => null];
|
||||
$arguments = $this->createArgumentsFromLegacy($args, $defaults);
|
||||
}
|
||||
|
||||
return $arguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
* @param array $defaults
|
||||
* @return array
|
||||
*/
|
||||
protected function createArgumentsFromLegacy(array $args, array $defaults)
|
||||
{
|
||||
// Remove arguments with old default values.
|
||||
$arguments = [];
|
||||
foreach ($args as $arg) {
|
||||
$default = current($defaults);
|
||||
if ($arg !== $default) {
|
||||
$arguments[key($defaults)] = $arg;
|
||||
}
|
||||
next($defaults);
|
||||
}
|
||||
|
||||
return $arguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience wrapper for async loading of JavaScript
|
||||
*
|
||||
* @param string|array $asset
|
||||
* @param int $priority
|
||||
* @param bool $pipeline
|
||||
* @param string $group name of the group
|
||||
* @return Assets
|
||||
* @deprecated Please use dynamic method with ['loading' => 'async'].
|
||||
*/
|
||||
public function addAsyncJs($asset, $priority = 10, $pipeline = true, $group = 'head')
|
||||
{
|
||||
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use dynamic method with [\'loading\' => \'async\']', E_USER_DEPRECATED);
|
||||
|
||||
return $this->addJs($asset, $priority, $pipeline, 'async', $group);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience wrapper for deferred loading of JavaScript
|
||||
*
|
||||
* @param string|array $asset
|
||||
* @param int $priority
|
||||
* @param bool $pipeline
|
||||
* @param string $group name of the group
|
||||
* @return Assets
|
||||
* @deprecated Please use dynamic method with ['loading' => 'defer'].
|
||||
*/
|
||||
public function addDeferJs($asset, $priority = 10, $pipeline = true, $group = 'head')
|
||||
{
|
||||
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use dynamic method with [\'loading\' => \'defer\']', E_USER_DEPRECATED);
|
||||
|
||||
return $this->addJs($asset, $priority, $pipeline, 'defer', $group);
|
||||
}
|
||||
}
|
||||
341
system/src/Grav/Common/Assets/Traits/TestingAssetsTrait.php
Normal file
341
system/src/Grav/Common/Assets/Traits/TestingAssetsTrait.php
Normal file
@@ -0,0 +1,341 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Assets\Traits
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Assets\Traits;
|
||||
|
||||
use FilesystemIterator;
|
||||
use Grav\Common\Grav;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use RegexIterator;
|
||||
use function strlen;
|
||||
|
||||
/**
|
||||
* Trait TestingAssetsTrait
|
||||
* @package Grav\Common\Assets\Traits
|
||||
*/
|
||||
trait TestingAssetsTrait
|
||||
{
|
||||
/**
|
||||
* Determines if an asset exists as a collection, CSS or JS reference
|
||||
*
|
||||
* @param string $asset
|
||||
* @return bool
|
||||
*/
|
||||
public function exists($asset)
|
||||
{
|
||||
return isset($this->collections[$asset]) || isset($this->assets_css[$asset]) || isset($this->assets_js[$asset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the array of all the registered collections
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getCollections()
|
||||
{
|
||||
return $this->collections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the array of collections explicitly
|
||||
*
|
||||
* @param array $collections
|
||||
* @return $this
|
||||
*/
|
||||
public function setCollection($collections)
|
||||
{
|
||||
$this->collections = $collections;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the array of all the registered CSS assets
|
||||
* If a $key is provided, it will try to return only that asset
|
||||
* else it will return null
|
||||
*
|
||||
* @param string|null $key the asset key
|
||||
* @return array
|
||||
*/
|
||||
public function getCss($key = null)
|
||||
{
|
||||
if (null !== $key) {
|
||||
$asset_key = md5($key);
|
||||
|
||||
return $this->assets_css[$asset_key] ?? null;
|
||||
}
|
||||
|
||||
return $this->assets_css;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the array of all the registered JS assets
|
||||
* If a $key is provided, it will try to return only that asset
|
||||
* else it will return null
|
||||
*
|
||||
* @param string|null $key the asset key
|
||||
* @return array
|
||||
*/
|
||||
public function getJs($key = null)
|
||||
{
|
||||
if (null !== $key) {
|
||||
$asset_key = md5($key);
|
||||
|
||||
return $this->assets_js[$asset_key] ?? null;
|
||||
}
|
||||
|
||||
return $this->assets_js;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the whole array of CSS assets
|
||||
*
|
||||
* @param array $css
|
||||
* @return $this
|
||||
*/
|
||||
public function setCss($css)
|
||||
{
|
||||
$this->assets_css = $css;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the whole array of JS assets
|
||||
*
|
||||
* @param array $js
|
||||
* @return $this
|
||||
*/
|
||||
public function setJs($js)
|
||||
{
|
||||
$this->assets_js = $js;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an item from the CSS array if set
|
||||
*
|
||||
* @param string $key The asset key
|
||||
* @return $this
|
||||
*/
|
||||
public function removeCss($key)
|
||||
{
|
||||
$asset_key = md5($key);
|
||||
if (isset($this->assets_css[$asset_key])) {
|
||||
unset($this->assets_css[$asset_key]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an item from the JS array if set
|
||||
*
|
||||
* @param string $key The asset key
|
||||
* @return $this
|
||||
*/
|
||||
public function removeJs($key)
|
||||
{
|
||||
$asset_key = md5($key);
|
||||
if (isset($this->assets_js[$asset_key])) {
|
||||
unset($this->assets_js[$asset_key]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the state of CSS Pipeline
|
||||
*
|
||||
* @param bool $value
|
||||
* @return $this
|
||||
*/
|
||||
public function setCssPipeline($value)
|
||||
{
|
||||
$this->css_pipeline = (bool)$value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the state of JS Pipeline
|
||||
*
|
||||
* @param bool $value
|
||||
* @return $this
|
||||
*/
|
||||
public function setJsPipeline($value)
|
||||
{
|
||||
$this->js_pipeline = (bool)$value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset all assets.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
$this->resetCss();
|
||||
$this->resetJs();
|
||||
$this->setCssPipeline(false);
|
||||
$this->setJsPipeline(false);
|
||||
$this->order = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset JavaScript assets.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function resetJs()
|
||||
{
|
||||
$this->assets_js = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset CSS assets.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function resetCss()
|
||||
{
|
||||
$this->assets_css = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly set's a timestamp for assets
|
||||
*
|
||||
* @param string|int $value
|
||||
*/
|
||||
public function setTimestamp($value)
|
||||
{
|
||||
$this->timestamp = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the timestamp for assets
|
||||
*
|
||||
* @param bool $include_join
|
||||
* @return string|null
|
||||
*/
|
||||
public function getTimestamp($include_join = true)
|
||||
{
|
||||
if ($this->timestamp) {
|
||||
return $include_join ? '?' . $this->timestamp : $this->timestamp;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all assets matching $pattern within $directory.
|
||||
*
|
||||
* @param string $directory Relative to the Grav root path, or a stream identifier
|
||||
* @param string $pattern (regex)
|
||||
* @return $this
|
||||
*/
|
||||
public function addDir($directory, $pattern = self::DEFAULT_REGEX)
|
||||
{
|
||||
$root_dir = GRAV_ROOT;
|
||||
|
||||
// Check if $directory is a stream.
|
||||
if (strpos($directory, '://')) {
|
||||
$directory = Grav::instance()['locator']->findResource($directory, null);
|
||||
}
|
||||
|
||||
// Get files
|
||||
$files = $this->rglob($root_dir . DIRECTORY_SEPARATOR . $directory, $pattern, $root_dir . '/');
|
||||
|
||||
// No luck? Nothing to do
|
||||
if (!$files) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Add CSS files
|
||||
if ($pattern === self::CSS_REGEX) {
|
||||
foreach ($files as $file) {
|
||||
$this->addCss($file);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Add JavaScript files
|
||||
if ($pattern === self::JS_REGEX) {
|
||||
foreach ($files as $file) {
|
||||
$this->addJs($file);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Unknown pattern.
|
||||
foreach ($files as $asset) {
|
||||
$this->add($asset);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all JavaScript assets within $directory
|
||||
*
|
||||
* @param string $directory Relative to the Grav root path, or a stream identifier
|
||||
* @return $this
|
||||
*/
|
||||
public function addDirJs($directory)
|
||||
{
|
||||
return $this->addDir($directory, self::JS_REGEX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all CSS assets within $directory
|
||||
*
|
||||
* @param string $directory Relative to the Grav root path, or a stream identifier
|
||||
* @return $this
|
||||
*/
|
||||
public function addDirCss($directory)
|
||||
{
|
||||
return $this->addDir($directory, self::CSS_REGEX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively get files matching $pattern within $directory.
|
||||
*
|
||||
* @param string $directory
|
||||
* @param string $pattern (regex)
|
||||
* @param string|null $ltrim Will be trimmed from the left of the file path
|
||||
* @return array
|
||||
*/
|
||||
protected function rglob($directory, $pattern, $ltrim = null)
|
||||
{
|
||||
$iterator = new RegexIterator(new RecursiveIteratorIterator(new RecursiveDirectoryIterator(
|
||||
$directory,
|
||||
FilesystemIterator::SKIP_DOTS
|
||||
)), $pattern);
|
||||
$offset = strlen($ltrim);
|
||||
$files = [];
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
$files[] = substr($file->getPathname(), $offset);
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user