|
@@ -1,27 +1,33 @@
|
|
|
<?php
|
|
|
+
|
|
|
/**
|
|
|
- * @package Grav.Common
|
|
|
+ * @package Grav\Common
|
|
|
*
|
|
|
- * @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
|
|
+ * @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
|
|
* @license MIT License; see LICENSE file for details.
|
|
|
*/
|
|
|
|
|
|
namespace Grav\Common;
|
|
|
|
|
|
-use Closure;
|
|
|
-use Exception;
|
|
|
-use FilesystemIterator;
|
|
|
+use Grav\Common\Assets\Pipeline;
|
|
|
+use Grav\Common\Assets\Traits\LegacyAssetsTrait;
|
|
|
+use Grav\Common\Assets\Traits\TestingAssetsTrait;
|
|
|
use Grav\Common\Config\Config;
|
|
|
-use RecursiveDirectoryIterator;
|
|
|
-use RecursiveIteratorIterator;
|
|
|
-use RegexIterator;
|
|
|
+use Grav\Framework\Object\PropertyObject;
|
|
|
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
|
|
|
|
|
-define('CSS_ASSET', true);
|
|
|
-define('JS_ASSET', false);
|
|
|
-
|
|
|
-class Assets
|
|
|
+class Assets extends PropertyObject
|
|
|
{
|
|
|
+ use TestingAssetsTrait;
|
|
|
+ use LegacyAssetsTrait;
|
|
|
+
|
|
|
+ const CSS_COLLECTION = 'assets_css';
|
|
|
+ const JS_COLLECTION = 'assets_js';
|
|
|
+ const CSS_TYPE = 'Css';
|
|
|
+ const JS_TYPE = 'Js';
|
|
|
+ const INLINE_CSS_TYPE = 'InlineCss';
|
|
|
+ const INLINE_JS_TYPE = 'InlineJs';
|
|
|
+
|
|
|
/** @const Regex to match CSS and JavaScript files */
|
|
|
const DEFAULT_REGEX = '/.\.(css|js)$/i';
|
|
|
|
|
@@ -31,79 +37,50 @@ class Assets
|
|
|
/** @const Regex to match JavaScript files */
|
|
|
const JS_REGEX = '/.\.js$/i';
|
|
|
|
|
|
- /** @const Regex to match CSS urls */
|
|
|
- const CSS_URL_REGEX = '{url\(([\'\"]?)(.*?)\1\)}';
|
|
|
+ protected $assets_dir;
|
|
|
+ protected $assets_url;
|
|
|
|
|
|
- /** @const Regex to match CSS sourcemap comments */
|
|
|
- const CSS_SOURCEMAP_REGEX = '{\/\*# (.*?) \*\/}';
|
|
|
+ protected $assets_css = [];
|
|
|
+ protected $assets_js = [];
|
|
|
|
|
|
- /** @const Regex to match CSS import content */
|
|
|
- const CSS_IMPORT_REGEX = '{@import(.*?);}';
|
|
|
+ // Config Options
|
|
|
+ protected $css_pipeline;
|
|
|
+ protected $css_pipeline_include_externals;
|
|
|
+ protected $css_pipeline_before_excludes;
|
|
|
+ protected $js_pipeline;
|
|
|
+ protected $js_pipeline_include_externals;
|
|
|
+ protected $js_pipeline_before_excludes;
|
|
|
+ protected $pipeline_options = [];
|
|
|
|
|
|
- /**
|
|
|
- * @const Regex to match <script> or <style> tag when adding inline style/script. Note that this only supports a
|
|
|
- * single tag, so the check is greedy to avoid issues in JS.
|
|
|
- */
|
|
|
- const HTML_TAG_REGEX = '#(<([A-Z][A-Z0-9]*)>)+(.*)(<\/\2>)#is';
|
|
|
+
|
|
|
+ protected $fetch_command;
|
|
|
+ protected $autoload;
|
|
|
+ protected $enable_asset_timestamp;
|
|
|
+ protected $collections;
|
|
|
+ protected $timestamp;
|
|
|
|
|
|
|
|
|
/**
|
|
|
- * Closure used by the pipeline to fetch assets.
|
|
|
- *
|
|
|
- * Useful when file_get_contents() function is not available in your PHP
|
|
|
- * installation or when you want to apply any kind of preprocessing to
|
|
|
- * your assets before they get pipelined.
|
|
|
- *
|
|
|
- * The closure will receive as the only parameter a string with the path/URL of the asset and
|
|
|
- * it should return the content of the asset file as a string.
|
|
|
- *
|
|
|
- * @var Closure
|
|
|
+ * Initialization called in the Grav lifecycle to initialize the Assets with appropriate configuration
|
|
|
*/
|
|
|
- protected $fetch_command;
|
|
|
+ public function init()
|
|
|
+ {
|
|
|
+ $grav = Grav::instance();
|
|
|
+ /** @var Config $config */
|
|
|
+ $config = $grav['config'];
|
|
|
|
|
|
- // Configuration toggles to enable/disable the pipelining feature
|
|
|
- protected $css_pipeline = false;
|
|
|
- protected $css_pipeline_include_externals = true;
|
|
|
- protected $css_pipeline_before_excludes = true;
|
|
|
- protected $js_pipeline = false;
|
|
|
- protected $js_pipeline_include_externals = true;
|
|
|
- protected $js_pipeline_before_excludes = true;
|
|
|
-
|
|
|
- // The asset holding arrays
|
|
|
- protected $collections = [];
|
|
|
- protected $css = [];
|
|
|
- protected $js = [];
|
|
|
- protected $inline_css = [];
|
|
|
- protected $inline_js = [];
|
|
|
- protected $imports = [];
|
|
|
-
|
|
|
- // Some configuration variables
|
|
|
- protected $config;
|
|
|
- protected $base_url;
|
|
|
- protected $timestamp = '';
|
|
|
- protected $assets_dir;
|
|
|
- protected $assets_url;
|
|
|
+ $asset_config = (array)$config->get('system.assets');
|
|
|
|
|
|
- // Default values for pipeline settings
|
|
|
- protected $css_minify = true;
|
|
|
- protected $css_minify_windows = false;
|
|
|
- protected $css_rewrite = true;
|
|
|
- protected $js_minify = true;
|
|
|
+ /** @var UniformResourceLocator $locator */
|
|
|
+ $locator = $grav['locator'];
|
|
|
+ $this->assets_dir = $locator->findResource('asset://') . DS;
|
|
|
+ $this->assets_url = $locator->findResource('asset://', false);
|
|
|
|
|
|
- // Arrays to hold assets that should NOT be pipelined
|
|
|
- protected $css_no_pipeline = [];
|
|
|
- protected $js_no_pipeline = [];
|
|
|
+ $this->config($asset_config);
|
|
|
|
|
|
- /**
|
|
|
- * Assets constructor.
|
|
|
- *
|
|
|
- * @param array $options
|
|
|
- */
|
|
|
- public function __construct(array $options = [])
|
|
|
- {
|
|
|
- // Forward config options
|
|
|
- if ($options) {
|
|
|
- $this->config((array)$options);
|
|
|
+ // Register any preconfigured collections
|
|
|
+ foreach ((array) $this->collections as $name => $collection) {
|
|
|
+ $this->registerCollection($name, (array)$collection);
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -117,140 +94,60 @@ class Assets
|
|
|
* @param array $config Configurable options.
|
|
|
*
|
|
|
* @return $this
|
|
|
- * @throws \Exception
|
|
|
*/
|
|
|
public function config(array $config)
|
|
|
{
|
|
|
- // Set pipeline modes
|
|
|
- if (isset($config['css_pipeline'])) {
|
|
|
- $this->css_pipeline = $config['css_pipeline'];
|
|
|
- }
|
|
|
-
|
|
|
- if (isset($config['css_pipeline_include_externals'])) {
|
|
|
- $this->css_pipeline_include_externals = $config['css_pipeline_include_externals'];
|
|
|
- }
|
|
|
-
|
|
|
- if (isset($config['css_pipeline_before_excludes'])) {
|
|
|
- $this->css_pipeline_before_excludes = $config['css_pipeline_before_excludes'];
|
|
|
- }
|
|
|
-
|
|
|
- if (isset($config['js_pipeline'])) {
|
|
|
- $this->js_pipeline = $config['js_pipeline'];
|
|
|
- }
|
|
|
-
|
|
|
- if (isset($config['js_pipeline_include_externals'])) {
|
|
|
- $this->js_pipeline_include_externals = $config['js_pipeline_include_externals'];
|
|
|
- }
|
|
|
-
|
|
|
- if (isset($config['js_pipeline_before_excludes'])) {
|
|
|
- $this->js_pipeline_before_excludes = $config['js_pipeline_before_excludes'];
|
|
|
- }
|
|
|
-
|
|
|
- // Pipeline requires public dir
|
|
|
- if (($this->js_pipeline || $this->css_pipeline) && !is_dir($this->assets_dir)) {
|
|
|
- throw new \Exception('Assets: Public dir not found');
|
|
|
- }
|
|
|
-
|
|
|
- // Set custom pipeline fetch command
|
|
|
- if (isset($config['fetch_command']) && ($config['fetch_command'] instanceof Closure)) {
|
|
|
- $this->fetch_command = $config['fetch_command'];
|
|
|
- }
|
|
|
-
|
|
|
- // Set CSS Minify state
|
|
|
- if (isset($config['css_minify'])) {
|
|
|
- $this->css_minify = $config['css_minify'];
|
|
|
- }
|
|
|
-
|
|
|
- if (isset($config['css_minify_windows'])) {
|
|
|
- $this->css_minify_windows = $config['css_minify_windows'];
|
|
|
- }
|
|
|
-
|
|
|
- if (isset($config['css_rewrite'])) {
|
|
|
- $this->css_rewrite = $config['css_rewrite'];
|
|
|
- }
|
|
|
-
|
|
|
- // Set JS Minify state
|
|
|
- if (isset($config['js_minify'])) {
|
|
|
- $this->js_minify = $config['js_minify'];
|
|
|
- }
|
|
|
-
|
|
|
- // Set collections
|
|
|
- if (isset($config['collections']) && is_array($config['collections'])) {
|
|
|
- $this->collections = $config['collections'];
|
|
|
- }
|
|
|
-
|
|
|
- // Autoload assets
|
|
|
- if (isset($config['autoload']) && is_array($config['autoload'])) {
|
|
|
- foreach ($config['autoload'] as $asset) {
|
|
|
- $this->add($asset);
|
|
|
+ foreach ($config as $key => $value) {
|
|
|
+ if ($this->hasProperty($key)) {
|
|
|
+ $this->setProperty($key, $value);
|
|
|
+ } elseif (Utils::startsWith($key, 'css_') || Utils::startsWith($key, 'js_')) {
|
|
|
+ $this->pipeline_options[$key] = $value;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // Set timestamp
|
|
|
- if (isset($config['enable_asset_timestamp']) && $config['enable_asset_timestamp'] === true) {
|
|
|
+ // Add timestamp if it's enabled
|
|
|
+ if ($this->enable_asset_timestamp) {
|
|
|
$this->timestamp = Grav::instance()['cache']->getKey();
|
|
|
}
|
|
|
|
|
|
return $this;
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * Initialization called in the Grav lifecycle to initialize the Assets with appropriate configuration
|
|
|
- */
|
|
|
- public function init()
|
|
|
- {
|
|
|
- $grav = Grav::instance();
|
|
|
- /** @var Config $config */
|
|
|
- $config = $grav['config'];
|
|
|
- $base_url = $grav['base_url'];
|
|
|
- $asset_config = (array)$config->get('system.assets');
|
|
|
-
|
|
|
- /** @var UniformResourceLocator $locator */
|
|
|
- $locator = $grav['locator'];
|
|
|
- $this->assets_dir = $locator->findResource('asset://') . DS;
|
|
|
- $this->assets_url = $locator->findResource('asset://', false);
|
|
|
-
|
|
|
- $this->config($asset_config);
|
|
|
- $this->base_url = ($config->get('system.absolute_urls') ? '' : '/') . ltrim(ltrim($base_url, '/') . '/', '/');
|
|
|
-
|
|
|
- // Register any preconfigured collections
|
|
|
- foreach ($config->get('system.assets.collections', []) as $name => $collection) {
|
|
|
- $this->registerCollection($name, (array)$collection);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
/**
|
|
|
* Add an asset or a collection of assets.
|
|
|
*
|
|
|
* It automatically detects the asset type (JavaScript, CSS or collection).
|
|
|
* You may add more than one asset passing an array as argument.
|
|
|
*
|
|
|
- * @param mixed $asset
|
|
|
- * @param int $priority the priority, bigger comes first
|
|
|
- * @param bool $pipeline false if this should not be pipelined
|
|
|
- *
|
|
|
+ * @param array|string $asset
|
|
|
* @return $this
|
|
|
*/
|
|
|
- public function add($asset, $priority = null, $pipeline = true)
|
|
|
+ public function add($asset)
|
|
|
{
|
|
|
+ $args = \func_get_args();
|
|
|
+
|
|
|
// More than one asset
|
|
|
- if (is_array($asset)) {
|
|
|
+ if (\is_array($asset)) {
|
|
|
foreach ($asset as $a) {
|
|
|
- $this->add($a, $priority, $pipeline);
|
|
|
+ array_shift($args);
|
|
|
+ $args = array_merge([$a], $args);
|
|
|
+ \call_user_func_array([$this, 'add'], $args);
|
|
|
}
|
|
|
} elseif (isset($this->collections[$asset])) {
|
|
|
- $this->add($this->collections[$asset], $priority, $pipeline);
|
|
|
+ array_shift($args);
|
|
|
+ $args = array_merge([$this->collections[$asset]], $args);
|
|
|
+ \call_user_func_array([$this, 'add'], $args);
|
|
|
} else {
|
|
|
// Get extension
|
|
|
$extension = pathinfo(parse_url($asset, PHP_URL_PATH), PATHINFO_EXTENSION);
|
|
|
|
|
|
// JavaScript or CSS
|
|
|
- if (strlen($extension) > 0) {
|
|
|
+ if (\strlen($extension) > 0) {
|
|
|
$extension = strtolower($extension);
|
|
|
if ($extension === 'css') {
|
|
|
- $this->addCss($asset, $priority, $pipeline);
|
|
|
+ \call_user_func_array([$this, 'addCss'], $args);
|
|
|
} elseif ($extension === 'js') {
|
|
|
- $this->addJs($asset, $priority, $pipeline);
|
|
|
+ \call_user_func_array([$this, 'addJs'], $args);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -258,1187 +155,221 @@ class Assets
|
|
|
return $this;
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * Add an asset to its assembly.
|
|
|
- *
|
|
|
- * It checks for duplicates.
|
|
|
- * You may add more than one asset passing an array as argument.
|
|
|
- * The third argument may alternatively contain an array of options which take precedence over positional
|
|
|
- * arguments.
|
|
|
- *
|
|
|
- * @param array $assembly the array assembling the assets
|
|
|
- * @param mixed $asset
|
|
|
- * @param int $priority the priority, bigger comes first
|
|
|
- * @param bool $pipeline false if this should not be pipelined
|
|
|
- * @param string $loading how the asset is loaded (async/defer/inline, for CSS: only inline)
|
|
|
- * @param string $group name of the group
|
|
|
- *
|
|
|
- * @return $this
|
|
|
- */
|
|
|
- public function addTo(&$assembly, $asset, $priority = null, $pipeline = true, $loading = null, $group = null)
|
|
|
+ protected function addType($collection, $type, $asset, $options)
|
|
|
{
|
|
|
- if (is_array($asset)) {
|
|
|
+ if (\is_array($asset)) {
|
|
|
foreach ($asset as $a) {
|
|
|
- $this->addTo($assembly, $a, $priority, $pipeline, $loading, $group);
|
|
|
+ $this->addType($collection, $type, $a, $options);
|
|
|
}
|
|
|
-
|
|
|
return $this;
|
|
|
- } elseif (isset($this->collections[$asset])) {
|
|
|
- $this->addTo($assembly, $this->collections[$asset], $priority, $pipeline, $loading, $group);
|
|
|
+ }
|
|
|
|
|
|
+ if (($type === $this::CSS_TYPE || $type === $this::JS_TYPE) && isset($this->collections[$asset])) {
|
|
|
+ $this->addType($collection, $type, $this->collections[$asset], $options);
|
|
|
return $this;
|
|
|
}
|
|
|
|
|
|
- $query = [];
|
|
|
-
|
|
|
- $modified = false;
|
|
|
- $remote = $this->isRemoteLink($asset);
|
|
|
- if (!$remote) {
|
|
|
+ // 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_TYPE : $this::CSS_TYPE;
|
|
|
+ $excludes = strtolower($exclude_type . '_pipeline_before_excludes');
|
|
|
+ if ($this->{$excludes}) {
|
|
|
+ $default = 'after';
|
|
|
+ } else {
|
|
|
+ $default = 'before';
|
|
|
+ }
|
|
|
|
|
|
- $asset_parts = parse_url($asset);
|
|
|
- if (isset($asset_parts['query'])) {
|
|
|
- $query[] = $asset_parts['query'];
|
|
|
- unset($asset_parts['query']);
|
|
|
- $asset = Uri::buildUrl($asset_parts);
|
|
|
+ $options['position'] = $options['position'] ?? $default;
|
|
|
}
|
|
|
|
|
|
-
|
|
|
- $modified = $this->getLastModificationTime($asset);
|
|
|
- $asset = $this->buildLocalLink($asset);
|
|
|
+ unset($options['pipeline']);
|
|
|
}
|
|
|
|
|
|
- // Check for existence
|
|
|
- if ($asset === false) {
|
|
|
- return $this;
|
|
|
- }
|
|
|
+ // Add timestamp
|
|
|
+ $options['timestamp'] = $this->timestamp;
|
|
|
|
|
|
- $data = [
|
|
|
- 'asset' => $asset,
|
|
|
- 'remote' => $remote,
|
|
|
- 'priority' => intval($priority ?: 10),
|
|
|
- 'order' => count($assembly),
|
|
|
- 'pipeline' => (bool) $pipeline,
|
|
|
- 'loading' => $loading ?: '',
|
|
|
- 'group' => $group ?: 'head',
|
|
|
- 'modified' => $modified,
|
|
|
- 'query' => implode('&', $query),
|
|
|
- ];
|
|
|
-
|
|
|
- // check for dynamic array and merge with defaults
|
|
|
- if (func_num_args() > 2) {
|
|
|
- $dynamic_arg = func_get_arg(2);
|
|
|
- if (is_array($dynamic_arg)) {
|
|
|
- $data = array_merge($data, $dynamic_arg);
|
|
|
- }
|
|
|
- }
|
|
|
+ // Set order
|
|
|
+ $options['order'] = \count($this->$collection);
|
|
|
|
|
|
- $key = md5($asset);
|
|
|
- if ($asset) {
|
|
|
- $assembly[$key] = $data;
|
|
|
+ // Create asset of correct type
|
|
|
+ $asset_class = "\\Grav\\Common\\Assets\\{$type}";
|
|
|
+ $asset_object = new $asset_class();
|
|
|
+
|
|
|
+ // If exists
|
|
|
+ if ($asset_object->init($asset, $options)) {
|
|
|
+ $this->$collection[md5($asset)] = $asset_object;
|
|
|
}
|
|
|
|
|
|
return $this;
|
|
|
- }
|
|
|
|
|
|
- /**
|
|
|
- * Add a CSS asset.
|
|
|
- *
|
|
|
- * It checks for duplicates.
|
|
|
- * You may add more than one asset passing an array as argument.
|
|
|
- * The second argument may alternatively contain an array of options which take precedence over positional
|
|
|
- * arguments.
|
|
|
- *
|
|
|
- * @param mixed $asset
|
|
|
- * @param int $priority the priority, bigger comes first
|
|
|
- * @param bool $pipeline false if this should not be pipelined
|
|
|
- * @param string $group
|
|
|
- * @param string $loading how the asset is loaded (async/defer/inline, for CSS: only inline)
|
|
|
- *
|
|
|
- * @return $this
|
|
|
- */
|
|
|
- public function addCss($asset, $priority = null, $pipeline = true, $group = null, $loading = null)
|
|
|
- {
|
|
|
- return $this->addTo($this->css, $asset, $priority, $pipeline, $loading, $group);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Add a JavaScript asset.
|
|
|
- *
|
|
|
- * It checks for duplicates.
|
|
|
- * You may add more than one asset passing an array as argument.
|
|
|
- * The second argument may alternatively contain an array of options which take precedence over positional
|
|
|
- * arguments.
|
|
|
- *
|
|
|
- * @param mixed $asset
|
|
|
- * @param int $priority the priority, bigger comes first
|
|
|
- * @param bool $pipeline false if this should not be pipelined
|
|
|
- * @param string $loading how the asset is loaded (async/defer)
|
|
|
- * @param string $group name of the group
|
|
|
+ * Add a CSS asset or a collection of assets.
|
|
|
*
|
|
|
* @return $this
|
|
|
*/
|
|
|
- public function addJs($asset, $priority = null, $pipeline = true, $loading = null, $group = null)
|
|
|
+ public function addCss($asset)
|
|
|
{
|
|
|
- return $this->addTo($this->js, $asset, $priority, $pipeline, $loading, $group);
|
|
|
+ return $this->addType(Assets::CSS_COLLECTION,Assets::CSS_TYPE, $asset, $this->unifyLegacyArguments(\func_get_args(), Assets::CSS_TYPE));
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Convenience wrapper for async loading of JavaScript
|
|
|
- *
|
|
|
- * @param $asset
|
|
|
- * @param int $priority
|
|
|
- * @param bool $pipeline
|
|
|
- * @param string $group name of the group
|
|
|
+ * Add an Inline CSS asset or a collection of assets.
|
|
|
*
|
|
|
- * @deprecated Please use dynamic method with ['loading' => 'async']
|
|
|
- *
|
|
|
- * @return \Grav\Common\Assets
|
|
|
+ * @return $this
|
|
|
*/
|
|
|
- public function addAsyncJs($asset, $priority = null, $pipeline = true, $group = null)
|
|
|
+ public function addInlineCss($asset)
|
|
|
{
|
|
|
- return $this->addJs($asset, $priority, $pipeline, 'async', $group);
|
|
|
+ return $this->addType(Assets::CSS_COLLECTION, Assets::INLINE_CSS_TYPE, $asset, $this->unifyLegacyArguments(\func_get_args(), Assets::INLINE_CSS_TYPE));
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Convenience wrapper for deferred loading of JavaScript
|
|
|
- *
|
|
|
- * @param $asset
|
|
|
- * @param int $priority
|
|
|
- * @param bool $pipeline
|
|
|
- * @param string $group name of the group
|
|
|
- *
|
|
|
- * @deprecated Please use dynamic method with ['loading' => 'defer']
|
|
|
+ * Add a JS asset or a collection of assets.
|
|
|
*
|
|
|
- * @return \Grav\Common\Assets
|
|
|
+ * @return $this
|
|
|
*/
|
|
|
- public function addDeferJs($asset, $priority = null, $pipeline = true, $group = null)
|
|
|
+ public function addJs($asset)
|
|
|
{
|
|
|
- return $this->addJs($asset, $priority, $pipeline, 'defer', $group);
|
|
|
+ return $this->addType(Assets::JS_COLLECTION, Assets::JS_TYPE, $asset, $this->unifyLegacyArguments(\func_get_args(), Assets::JS_TYPE));
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Add an inline CSS asset.
|
|
|
- *
|
|
|
- * It checks for duplicates.
|
|
|
- * For adding chunks of string-based inline CSS
|
|
|
- *
|
|
|
- * @param mixed $asset
|
|
|
- * @param int $priority the priority, bigger comes first
|
|
|
- * @param null $group
|
|
|
+ * Add an Inline JS asset or a collection of assets.
|
|
|
*
|
|
|
* @return $this
|
|
|
*/
|
|
|
- public function addInlineCss($asset, $priority = null, $group = null)
|
|
|
+ public function addInlineJs($asset)
|
|
|
{
|
|
|
- $asset = trim($asset);
|
|
|
-
|
|
|
- if (is_a($asset, 'Twig_Markup')) {
|
|
|
- preg_match(self::HTML_TAG_REGEX, $asset, $matches);
|
|
|
- if (isset($matches[3])) {
|
|
|
- $asset = $matches[3];
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- $data = [
|
|
|
- 'priority' => intval($priority ?: 10),
|
|
|
- 'order' => count($this->inline_css),
|
|
|
- 'asset' => $asset,
|
|
|
- 'group' => $group ?: 'head'
|
|
|
- ];
|
|
|
-
|
|
|
- // check for dynamic array and merge with defaults
|
|
|
- if (func_num_args() == 2) {
|
|
|
- $dynamic_arg = func_get_arg(1);
|
|
|
- if (is_array($dynamic_arg)) {
|
|
|
- $data = array_merge($data, $dynamic_arg);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- $key = md5($asset);
|
|
|
- if ($asset && is_string($asset) && !array_key_exists($key, $this->inline_css)) {
|
|
|
- $this->inline_css[$key] = $data;
|
|
|
- }
|
|
|
-
|
|
|
- return $this;
|
|
|
+ return $this->addType(Assets::JS_COLLECTION, Assets::INLINE_JS_TYPE, $asset, $this->unifyLegacyArguments(\func_get_args(), Assets::INLINE_JS_TYPE));
|
|
|
}
|
|
|
|
|
|
+
|
|
|
/**
|
|
|
- * Add an inline JS asset.
|
|
|
- *
|
|
|
- * It checks for duplicates.
|
|
|
- * For adding chunks of string-based inline JS
|
|
|
+ * Add/replace collection.
|
|
|
*
|
|
|
- * @param mixed $asset
|
|
|
- * @param int $priority the priority, bigger comes first
|
|
|
- * @param string $group name of the group
|
|
|
- * @param null $attributes
|
|
|
+ * @param string $collectionName
|
|
|
+ * @param array $assets
|
|
|
+ * @param bool $overwrite
|
|
|
*
|
|
|
* @return $this
|
|
|
*/
|
|
|
- public function addInlineJs($asset, $priority = null, $group = null, $attributes = null)
|
|
|
+ public function registerCollection($collectionName, Array $assets, $overwrite = false)
|
|
|
{
|
|
|
- $asset = trim($asset);
|
|
|
-
|
|
|
- if (is_a($asset, 'Twig_Markup')) {
|
|
|
- preg_match(self::HTML_TAG_REGEX, $asset, $matches);
|
|
|
- if (isset($matches[3])) {
|
|
|
- $asset = $matches[3];
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- $data = [
|
|
|
- 'asset' => $asset,
|
|
|
- 'priority' => intval($priority ?: 10),
|
|
|
- 'order' => count($this->js),
|
|
|
- 'group' => $group ?: 'head',
|
|
|
- 'type' => $attributes ?: '',
|
|
|
- ];
|
|
|
-
|
|
|
- // check for dynamic array and merge with defaults
|
|
|
- if (func_num_args() == 2) {
|
|
|
- $dynamic_arg = func_get_arg(1);
|
|
|
- if (is_array($dynamic_arg)) {
|
|
|
- $data = array_merge($data, $dynamic_arg);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- $key = md5($asset);
|
|
|
- if ($asset && is_string($asset) && !array_key_exists($key, $this->inline_js)) {
|
|
|
- $this->inline_js[$key] = $data;
|
|
|
+ if ($overwrite || !isset($this->collections[$collectionName])) {
|
|
|
+ $this->collections[$collectionName] = $assets;
|
|
|
}
|
|
|
|
|
|
return $this;
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * Build the CSS link tags.
|
|
|
- *
|
|
|
- * @param string $group name of the group
|
|
|
- * @param array $attributes
|
|
|
- *
|
|
|
- * @return string
|
|
|
- */
|
|
|
- public function css($group = 'head', $attributes = [])
|
|
|
+ protected function filterAssets($assets, $key, $value, $sort = false)
|
|
|
{
|
|
|
- if (!$this->css && !$this->inline_css) {
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- // Sort array by priorities (larger priority first)
|
|
|
- if (Grav::instance()) {
|
|
|
- uasort($this->css, array($this, 'sortAssetsByPriorityThenOrder'));
|
|
|
- uasort($this->inline_css, array($this, 'sortAssetsByPriorityThenOrder'));
|
|
|
- }
|
|
|
-
|
|
|
- $inlineGroup = array_key_exists('loading', $attributes) && $attributes['loading'] === 'inline';
|
|
|
+ $results = array_filter($assets, function($asset) use ($key, $value) {
|
|
|
|
|
|
- $attributes = $this->attributes(array_merge(['type' => 'text/css', 'rel' => 'stylesheet'], $attributes));
|
|
|
+ if ($key === 'position' && $value === 'pipeline') {
|
|
|
|
|
|
- $output = '';
|
|
|
- $inline_css = '';
|
|
|
+ $type = $asset->getType();
|
|
|
|
|
|
- if ($this->css_pipeline) {
|
|
|
- $pipeline_result = $this->pipelineCss($group, !$inlineGroup);
|
|
|
- $pipeline_html = ($inlineGroup ? '' : '<link href="' . $pipeline_result . '"' . $attributes . ' />' . "\n");
|
|
|
-
|
|
|
- if ($this->css_pipeline_before_excludes && $pipeline_result) {
|
|
|
- if ($inlineGroup) {
|
|
|
- $inline_css .= $pipeline_result;
|
|
|
- }
|
|
|
- else {
|
|
|
- $output .= $pipeline_html;
|
|
|
- }
|
|
|
- }
|
|
|
- foreach ($this->css_no_pipeline as $file) {
|
|
|
- if ($group && $file['group'] == $group) {
|
|
|
- if ($file['loading'] === 'inline') {
|
|
|
- $inline_css .= $this->gatherLinks([$file], CSS_ASSET) . "\n";
|
|
|
- }
|
|
|
- else {
|
|
|
- $media = isset($file['media']) ? sprintf(' media="%s"', $file['media']) : '';
|
|
|
- $output .= '<link href="' . $file['asset'] . $this->getQuerystring($file) . '"' . $attributes . $media . ' />' . "\n";
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- if (!$this->css_pipeline_before_excludes && $pipeline_result) {
|
|
|
- if ($inlineGroup) {
|
|
|
- $inline_css .= $pipeline_result;
|
|
|
- }
|
|
|
- else {
|
|
|
- $output .= $pipeline_html;
|
|
|
- }
|
|
|
- }
|
|
|
- } else {
|
|
|
- foreach ($this->css as $file) {
|
|
|
- if ($group && $file['group'] == $group) {
|
|
|
- if ($inlineGroup || $file['loading'] === 'inline') {
|
|
|
- $inline_css .= $this->gatherLinks([$file], CSS_ASSET) . "\n";
|
|
|
- }
|
|
|
- else {
|
|
|
- $media = isset($file['media']) ? sprintf(' media="%s"', $file['media']) : '';
|
|
|
- $output .= '<link href="' . $file['asset'] . $this->getQuerystring($file) . '"' . $attributes . $media . ' />' . "\n";
|
|
|
+ if ($asset->getRemote() && $this->{$type . '_pipeline_include_externals'} === false && $asset['position'] === 'pipeline' ) {
|
|
|
+ if ($this->{$type . '_pipeline_before_excludes'}) {
|
|
|
+ $asset->setPosition('after');
|
|
|
+ } else {
|
|
|
+ $asset->setPosition('before');
|
|
|
}
|
|
|
+ return false;
|
|
|
}
|
|
|
- }
|
|
|
- }
|
|
|
|
|
|
- // Render Inline CSS
|
|
|
- foreach ($this->inline_css as $inline) {
|
|
|
- if ($group && $inline['group'] == $group) {
|
|
|
- $inline_css .= $inline['asset'] . "\n";
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- if ($inline_css) {
|
|
|
- $output .= "\n<style>\n" . $inline_css . "\n</style>\n";
|
|
|
+ if ($asset[$key] === $value) return true;
|
|
|
+ return false;
|
|
|
+ });
|
|
|
+
|
|
|
+ if ($sort && !empty($results)) {
|
|
|
+ $results = $this->sortAssets($results);
|
|
|
}
|
|
|
|
|
|
|
|
|
- return $output;
|
|
|
+ return $results;
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * Build the JavaScript script tags.
|
|
|
- *
|
|
|
- * @param string $group name of the group
|
|
|
- * @param array $attributes
|
|
|
- *
|
|
|
- * @return string
|
|
|
- */
|
|
|
- public function js($group = 'head', $attributes = [])
|
|
|
+ protected function sortAssets($assets)
|
|
|
{
|
|
|
- if (!$this->js && !$this->inline_js) {
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- // Sort array by priorities (larger priority first)
|
|
|
- uasort($this->js, array($this, 'sortAssetsByPriorityThenOrder'));
|
|
|
- uasort($this->inline_js, array($this, 'sortAssetsByPriorityThenOrder'));
|
|
|
+ uasort ($assets, function($a, $b) {
|
|
|
+ if ($a['priority'] == $b['priority']) {
|
|
|
+ return $a['order'] - $b['order'];
|
|
|
+ }
|
|
|
+ return $b['priority'] - $a['priority'];
|
|
|
+ });
|
|
|
+ return $assets;
|
|
|
+ }
|
|
|
|
|
|
- $inlineGroup = array_key_exists('loading', $attributes) && $attributes['loading'] === 'inline';
|
|
|
+ public function render($type, $group = 'head', $attributes = [])
|
|
|
+ {
|
|
|
+ $before_output = '';
|
|
|
+ $pipeline_output = '';
|
|
|
+ $after_output = '';
|
|
|
|
|
|
- $attributes = $this->attributes($attributes);
|
|
|
+ $assets = 'assets_' . $type;
|
|
|
+ $pipeline_enabled = $type . '_pipeline';
|
|
|
+ $render_pipeline = 'render' . ucfirst($type);
|
|
|
|
|
|
- $output = '';
|
|
|
- $inline_js = '';
|
|
|
+ $group_assets = $this->filterAssets($this->$assets, 'group', $group);
|
|
|
+ $pipeline_assets = $this->filterAssets($group_assets, 'position', 'pipeline', true);
|
|
|
+ $before_assets = $this->filterAssets($group_assets, 'position', 'before', true);
|
|
|
+ $after_assets = $this->filterAssets($group_assets, 'position', 'after', true);
|
|
|
|
|
|
- if ($this->js_pipeline) {
|
|
|
- $pipeline_result = $this->pipelineJs($group, !$inlineGroup);
|
|
|
- $pipeline_html = ($inlineGroup ? '' : '<script src="' . $pipeline_result . '"' . $attributes . ' ></script>' . "\n");
|
|
|
+ // Pipeline
|
|
|
+ if ($this->{$pipeline_enabled}) {
|
|
|
+ $options = array_merge($this->pipeline_options, ['timestamp' => $this->timestamp]);
|
|
|
|
|
|
- if ($this->js_pipeline_before_excludes && $pipeline_result) {
|
|
|
- if ($inlineGroup) {
|
|
|
- $inline_js .= $pipeline_result;
|
|
|
- }
|
|
|
- else {
|
|
|
- $output .= $pipeline_html;
|
|
|
- }
|
|
|
- }
|
|
|
- foreach ($this->js_no_pipeline as $file) {
|
|
|
- if ($group && $file['group'] == $group) {
|
|
|
- if ($file['loading'] === 'inline') {
|
|
|
- $inline_js .= $this->gatherLinks([$file], JS_ASSET) . "\n";
|
|
|
- }
|
|
|
- else {
|
|
|
- $output .= '<script src="' . $file['asset'] . $this->getQuerystring($file) . '"' . $attributes . ' ' . $file['loading'] . '></script>' . "\n";
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- if (!$this->js_pipeline_before_excludes && $pipeline_result) {
|
|
|
- if ($inlineGroup) {
|
|
|
- $inline_js .= $pipeline_result;
|
|
|
- }
|
|
|
- else {
|
|
|
- $output .= $pipeline_html;
|
|
|
- }
|
|
|
- }
|
|
|
+ $pipeline = new Pipeline($options);
|
|
|
+ $pipeline_output = $pipeline->$render_pipeline($pipeline_assets, $group, $attributes);
|
|
|
} else {
|
|
|
- foreach ($this->js as $file) {
|
|
|
- if ($group && $file['group'] == $group) {
|
|
|
- if ($inlineGroup || $file['loading'] === 'inline') {
|
|
|
- $inline_js .= $this->gatherLinks([$file], JS_ASSET) . "\n";
|
|
|
- }
|
|
|
- else {
|
|
|
- $output .= '<script src="' . $file['asset'] . $this->getQuerystring($file) . '"' . $attributes . ' ' . $file['loading'] . '></script>' . "\n";
|
|
|
- }
|
|
|
- }
|
|
|
+ foreach ($pipeline_assets as $asset) {
|
|
|
+ $pipeline_output .= $asset->render();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // Render Inline JS
|
|
|
- foreach ($this->inline_js as $inline) {
|
|
|
- if ($group && $inline['group'] == $group) {
|
|
|
- $inline_js .= $inline['asset'] . "\n";
|
|
|
- }
|
|
|
+ // Before Pipeline
|
|
|
+ foreach ($before_assets as $asset) {
|
|
|
+ $before_output .= $asset->render();
|
|
|
}
|
|
|
|
|
|
- if ($inline_js) {
|
|
|
- $attribute_string = isset($inline) && $inline['type'] ? " type=\"" . $inline['type'] . "\"" : '';
|
|
|
- $output .= "\n<script" . $attribute_string . ">\n" . $inline_js . "\n</script>\n";
|
|
|
+ // After Pipeline
|
|
|
+ foreach ($after_assets as $asset) {
|
|
|
+ $after_output .= $asset->render();
|
|
|
}
|
|
|
|
|
|
- return $output;
|
|
|
+ return $before_output . $pipeline_output . $after_output;
|
|
|
}
|
|
|
|
|
|
+
|
|
|
/**
|
|
|
- * Minify and concatenate CSS
|
|
|
+ * Build the CSS link tags.
|
|
|
*
|
|
|
- * @param string $group
|
|
|
- * @param bool $returnURL true if pipeline should return the URL, otherwise the content
|
|
|
+ * @param string $group name of the group
|
|
|
+ * @param array $attributes
|
|
|
*
|
|
|
- * @return bool|string URL or generated content if available, else false
|
|
|
+ * @return string
|
|
|
*/
|
|
|
- protected function pipelineCss($group = 'head', $returnURL = true)
|
|
|
+ public function css($group = 'head', $attributes = [])
|
|
|
{
|
|
|
- // temporary list of assets to pipeline
|
|
|
- $temp_css = [];
|
|
|
-
|
|
|
- // clear no-pipeline assets lists
|
|
|
- $this->css_no_pipeline = [];
|
|
|
-
|
|
|
- // Compute uid based on assets and timestamp
|
|
|
- $uid = md5(json_encode($this->css) . $this->css_minify . $this->css_rewrite . $group);
|
|
|
- $file = $uid . '.css';
|
|
|
- $inline_file = $uid . '-inline.css';
|
|
|
-
|
|
|
- $relative_path = "{$this->base_url}{$this->assets_url}/{$file}";
|
|
|
-
|
|
|
- // If inline files exist set them on object
|
|
|
- if (file_exists($this->assets_dir . $inline_file)) {
|
|
|
- $this->css_no_pipeline = json_decode(file_get_contents($this->assets_dir . $inline_file), true);
|
|
|
- }
|
|
|
-
|
|
|
- // If pipeline exist return its URL or content
|
|
|
- if (file_exists($this->assets_dir . $file)) {
|
|
|
- if ($returnURL) {
|
|
|
- return $relative_path . $this->getTimestamp();
|
|
|
- }
|
|
|
- else {
|
|
|
- return file_get_contents($this->assets_dir . $file) . "\n";
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // Remove any non-pipeline files
|
|
|
- foreach ($this->css as $id => $asset) {
|
|
|
- if ($asset['group'] == $group) {
|
|
|
- if (!$asset['pipeline'] ||
|
|
|
- ($asset['remote'] && $this->css_pipeline_include_externals === false)) {
|
|
|
- $this->css_no_pipeline[$id] = $asset;
|
|
|
- } else {
|
|
|
- $temp_css[$id] = $asset;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- //if nothing found get out of here!
|
|
|
- if (count($temp_css) == 0) {
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- // Write non-pipeline files out
|
|
|
- if (!empty($this->css_no_pipeline)) {
|
|
|
- file_put_contents($this->assets_dir . $inline_file, json_encode($this->css_no_pipeline));
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- $css_minify = $this->css_minify;
|
|
|
-
|
|
|
- // 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 (strtoupper(substr(php_uname('s'), 0, 3)) === 'WIN' && !$this->css_minify_windows) {
|
|
|
- $css_minify = false;
|
|
|
- }
|
|
|
-
|
|
|
- // Concatenate files
|
|
|
- $buffer = $this->gatherLinks($temp_css, CSS_ASSET);
|
|
|
- if ($css_minify) {
|
|
|
- $minifier = new \MatthiasMullie\Minify\CSS();
|
|
|
- $minifier->add($buffer);
|
|
|
- $buffer = $minifier->minify();
|
|
|
- }
|
|
|
-
|
|
|
- // Write file
|
|
|
- if (strlen(trim($buffer)) > 0) {
|
|
|
- file_put_contents($this->assets_dir . $file, $buffer);
|
|
|
-
|
|
|
- if ($returnURL) {
|
|
|
- return $relative_path . $this->getTimestamp();
|
|
|
- }
|
|
|
- else {
|
|
|
- return $buffer . "\n";
|
|
|
- }
|
|
|
- } else {
|
|
|
- return false;
|
|
|
- }
|
|
|
+ return $this->render('css', $group, $attributes);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Minify and concatenate JS files.
|
|
|
+ * Build the JavaScript script tags.
|
|
|
*
|
|
|
- * @param string $group
|
|
|
- * @param bool $returnURL true if pipeline should return the URL, otherwise the content
|
|
|
+ * @param string $group name of the group
|
|
|
+ * @param array $attributes
|
|
|
*
|
|
|
- * @return bool|string URL or generated content if available, else false
|
|
|
+ * @return string
|
|
|
*/
|
|
|
- protected function pipelineJs($group = 'head', $returnURL = true)
|
|
|
+ public function js($group = 'head', $attributes = [])
|
|
|
{
|
|
|
- // temporary list of assets to pipeline
|
|
|
- $temp_js = [];
|
|
|
-
|
|
|
- // clear no-pipeline assets lists
|
|
|
- $this->js_no_pipeline = [];
|
|
|
-
|
|
|
- // Compute uid based on assets and timestamp
|
|
|
- $uid = md5(json_encode($this->js) . $this->js_minify . $group);
|
|
|
- $file = $uid . '.js';
|
|
|
- $inline_file = $uid . '-inline.js';
|
|
|
-
|
|
|
- $relative_path = "{$this->base_url}{$this->assets_url}/{$file}";
|
|
|
-
|
|
|
- // If inline files exist set them on object
|
|
|
- if (file_exists($this->assets_dir . $inline_file)) {
|
|
|
- $this->js_no_pipeline = json_decode(file_get_contents($this->assets_dir . $inline_file), true);
|
|
|
- }
|
|
|
-
|
|
|
- // If pipeline exist return its URL or content
|
|
|
- if (file_exists($this->assets_dir . $file)) {
|
|
|
- if ($returnURL) {
|
|
|
- return $relative_path . $this->getTimestamp();
|
|
|
- }
|
|
|
- else {
|
|
|
- return file_get_contents($this->assets_dir . $file) . "\n";
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // Remove any non-pipeline files
|
|
|
- foreach ($this->js as $id => $asset) {
|
|
|
- if ($asset['group'] == $group) {
|
|
|
- if (!$asset['pipeline'] ||
|
|
|
- ($asset['remote'] && $this->js_pipeline_include_externals === false)) {
|
|
|
- $this->js_no_pipeline[] = $asset;
|
|
|
- } else {
|
|
|
- $temp_js[$id] = $asset;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- //if nothing found get out of here!
|
|
|
- if (count($temp_js) == 0) {
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- // Write non-pipeline files out
|
|
|
- if (!empty($this->js_no_pipeline)) {
|
|
|
- file_put_contents($this->assets_dir . $inline_file, json_encode($this->js_no_pipeline));
|
|
|
- }
|
|
|
-
|
|
|
- // Concatenate files
|
|
|
- $buffer = $this->gatherLinks($temp_js, JS_ASSET);
|
|
|
- if ($this->js_minify) {
|
|
|
- $minifier = new \MatthiasMullie\Minify\JS();
|
|
|
- $minifier->add($buffer);
|
|
|
- $buffer = $minifier->minify();
|
|
|
- }
|
|
|
-
|
|
|
- // Write file
|
|
|
- if (strlen(trim($buffer)) > 0) {
|
|
|
- file_put_contents($this->assets_dir . $file, $buffer);
|
|
|
-
|
|
|
- if ($returnURL) {
|
|
|
- return $relative_path . $this->getTimestamp();
|
|
|
- }
|
|
|
- else {
|
|
|
- return $buffer . "\n";
|
|
|
- }
|
|
|
- } else {
|
|
|
- return false;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Return the array of all the registered CSS assets
|
|
|
- * If a $key is provided, it will try to return only that asset
|
|
|
- * else it will return null
|
|
|
- *
|
|
|
- * @param null|string $key the asset key
|
|
|
- * @return array
|
|
|
- */
|
|
|
- public function getCss($key = null)
|
|
|
- {
|
|
|
- if (!empty($key)) {
|
|
|
- $asset_key = md5($key);
|
|
|
- if (isset($this->css[$asset_key])) {
|
|
|
- return $this->css[$asset_key];
|
|
|
- } else {
|
|
|
- return null;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return $this->css;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Return the array of all the registered JS assets
|
|
|
- * If a $key is provided, it will try to return only that asset
|
|
|
- * else it will return null
|
|
|
- *
|
|
|
- * @param null|string $key the asset key
|
|
|
- * @return array
|
|
|
- */
|
|
|
- public function getJs($key = null)
|
|
|
- {
|
|
|
- if (!empty($key)) {
|
|
|
- $asset_key = md5($key);
|
|
|
- if (isset($this->js[$asset_key])) {
|
|
|
- return $this->js[$asset_key];
|
|
|
- } else {
|
|
|
- return null;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return $this->js;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Set the whole array of CSS assets
|
|
|
- *
|
|
|
- * @param $css
|
|
|
- */
|
|
|
- public function setCss($css)
|
|
|
- {
|
|
|
- $this->css = $css;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Set the whole array of JS assets
|
|
|
- *
|
|
|
- * @param $js
|
|
|
- */
|
|
|
- public function setJs($js)
|
|
|
- {
|
|
|
- $this->js = $js;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Removes an item from the CSS array if set
|
|
|
- *
|
|
|
- * @param string $key The asset key
|
|
|
- */
|
|
|
- public function removeCss($key)
|
|
|
- {
|
|
|
- $asset_key = md5($key);
|
|
|
- if (isset($this->css[$asset_key])) {
|
|
|
- unset($this->css[$asset_key]);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Removes an item from the JS array if set
|
|
|
- *
|
|
|
- * @param string $key The asset key
|
|
|
- */
|
|
|
- public function removeJs($key)
|
|
|
- {
|
|
|
- $asset_key = md5($key);
|
|
|
- if (isset($this->js[$asset_key])) {
|
|
|
- unset($this->js[$asset_key]);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Return the array of all the registered collections
|
|
|
- *
|
|
|
- * @return array
|
|
|
- */
|
|
|
- public function getCollections()
|
|
|
- {
|
|
|
- return $this->collections;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Set the array of collections explicitly
|
|
|
- *
|
|
|
- * @param $collections
|
|
|
- */
|
|
|
- public function setCollection($collections)
|
|
|
- {
|
|
|
- $this->collections = $collections;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Determines if an asset exists as a collection, CSS or JS reference
|
|
|
- *
|
|
|
- * @param $asset
|
|
|
- *
|
|
|
- * @return bool
|
|
|
- */
|
|
|
- public function exists($asset)
|
|
|
- {
|
|
|
- if (isset($this->collections[$asset]) || isset($this->css[$asset]) || isset($this->js[$asset])) {
|
|
|
- return true;
|
|
|
- } else {
|
|
|
- return false;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Add/replace collection.
|
|
|
- *
|
|
|
- * @param string $collectionName
|
|
|
- * @param array $assets
|
|
|
- * @param bool $overwrite
|
|
|
- *
|
|
|
- * @return $this
|
|
|
- */
|
|
|
- public function registerCollection($collectionName, Array $assets, $overwrite = false)
|
|
|
- {
|
|
|
- if ($overwrite || !isset($this->collections[$collectionName])) {
|
|
|
- $this->collections[$collectionName] = $assets;
|
|
|
- }
|
|
|
-
|
|
|
- return $this;
|
|
|
+ return $this->render('js', $group, $attributes);
|
|
|
}
|
|
|
-
|
|
|
- /**
|
|
|
- * Reset all assets.
|
|
|
- *
|
|
|
- * @return $this
|
|
|
- */
|
|
|
- public function reset()
|
|
|
- {
|
|
|
- return $this->resetCss()->resetJs();
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Reset JavaScript assets.
|
|
|
- *
|
|
|
- * @return $this
|
|
|
- */
|
|
|
- public function resetJs()
|
|
|
- {
|
|
|
- $this->js = [];
|
|
|
- $this->inline_js = [];
|
|
|
-
|
|
|
- return $this;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Reset CSS assets.
|
|
|
- *
|
|
|
- * @return $this
|
|
|
- */
|
|
|
- public function resetCss()
|
|
|
- {
|
|
|
- $this->css = [];
|
|
|
- $this->inline_css = [];
|
|
|
-
|
|
|
- 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);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 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
|
|
|
- * @throws Exception
|
|
|
- */
|
|
|
- public function addDir($directory, $pattern = self::DEFAULT_REGEX)
|
|
|
- {
|
|
|
- $root_dir = rtrim(ROOT_DIR, '/');
|
|
|
-
|
|
|
- // Check if $directory is a stream.
|
|
|
- if (strpos($directory, '://')) {
|
|
|
- $directory = Grav::instance()['locator']->findResource($directory, null);
|
|
|
- }
|
|
|
-
|
|
|
- // Get files
|
|
|
- $files = $this->rglob($root_dir . DIRECTORY_SEPARATOR . $directory, $pattern, $root_dir . '/');
|
|
|
-
|
|
|
- // No luck? Nothing to do
|
|
|
- if (!$files) {
|
|
|
- return $this;
|
|
|
- }
|
|
|
-
|
|
|
- // Add CSS files
|
|
|
- if ($pattern === self::CSS_REGEX) {
|
|
|
- foreach ($files as $file) {
|
|
|
- $this->addCss($file);
|
|
|
- }
|
|
|
-
|
|
|
- return $this;
|
|
|
- }
|
|
|
-
|
|
|
- // Add JavaScript files
|
|
|
- if ($pattern === self::JS_REGEX) {
|
|
|
- foreach ($files as $file) {
|
|
|
- $this->addJs($file);
|
|
|
- }
|
|
|
-
|
|
|
- return $this;
|
|
|
- }
|
|
|
-
|
|
|
- // Unknown pattern.
|
|
|
- foreach ($files as $asset) {
|
|
|
- $this->add($asset);
|
|
|
- }
|
|
|
-
|
|
|
- return $this;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Determine whether a link is local or remote.
|
|
|
- *
|
|
|
- * Understands both "http://" and "https://" as well as protocol agnostic links "//"
|
|
|
- *
|
|
|
- * @param string $link
|
|
|
- *
|
|
|
- * @return bool
|
|
|
- */
|
|
|
- protected 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 ('http://' === substr($link, 0, 7) || 'https://' === substr($link, 0, 8) || '//' === substr($link, 0,
|
|
|
- 2));
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Build local links including grav asset shortcodes
|
|
|
- *
|
|
|
- * @param string $asset the asset string reference
|
|
|
- * @param bool $absolute build absolute asset link
|
|
|
- *
|
|
|
- * @return string the final link url to the asset
|
|
|
- */
|
|
|
- protected function buildLocalLink($asset, $absolute = false)
|
|
|
- {
|
|
|
- try {
|
|
|
- $asset = Grav::instance()['locator']->findResource($asset, $absolute);
|
|
|
- } catch (\Exception $e) {
|
|
|
- }
|
|
|
-
|
|
|
- $uri = $absolute ? $asset : $this->base_url . ltrim($asset, '/');
|
|
|
- return $asset ? $uri : false;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Get the last modification time of asset
|
|
|
- *
|
|
|
- * @param string $asset the asset string reference
|
|
|
- *
|
|
|
- * @return string the last modifcation time or false on error
|
|
|
- */
|
|
|
- protected function getLastModificationTime($asset)
|
|
|
- {
|
|
|
- $file = GRAV_ROOT . $asset;
|
|
|
- if (Grav::instance()['locator']->isStream($asset)) {
|
|
|
- $file = $this->buildLocalLink($asset, true);
|
|
|
- }
|
|
|
-
|
|
|
- return file_exists($file) ? filemtime($file) : false;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Build an HTML attribute string from an array.
|
|
|
- *
|
|
|
- * @param array $attributes
|
|
|
- *
|
|
|
- * @return string
|
|
|
- */
|
|
|
- protected function attributes(array $attributes)
|
|
|
- {
|
|
|
- $html = '';
|
|
|
- $no_key = ['loading'];
|
|
|
-
|
|
|
- foreach ($attributes as $key => $value) {
|
|
|
- // For numeric keys we will assume that the key and the value are the same
|
|
|
- // as this will convert HTML attributes such as "required" to a correct
|
|
|
- // form like required="required" instead of using incorrect numerics.
|
|
|
- if (is_numeric($key)) {
|
|
|
- $key = $value;
|
|
|
- }
|
|
|
- if (is_array($value)) {
|
|
|
- $value = implode(' ', $value);
|
|
|
- }
|
|
|
-
|
|
|
- if (in_array($key, $no_key)) {
|
|
|
- $element = htmlentities($value, ENT_QUOTES, 'UTF-8', false);
|
|
|
- } else {
|
|
|
- $element = $key . '="' . htmlentities($value, ENT_QUOTES, 'UTF-8', false) . '"';
|
|
|
- }
|
|
|
-
|
|
|
- $html .= ' ' . $element;
|
|
|
- }
|
|
|
-
|
|
|
- return $html;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Download and concatenate the content of several links.
|
|
|
- *
|
|
|
- * @param array $links
|
|
|
- * @param bool $css
|
|
|
- *
|
|
|
- * @return string
|
|
|
- */
|
|
|
- protected function gatherLinks(array $links, $css = true)
|
|
|
- {
|
|
|
- $buffer = '';
|
|
|
-
|
|
|
-
|
|
|
- foreach ($links as $asset) {
|
|
|
- $relative_dir = '';
|
|
|
- $local = true;
|
|
|
-
|
|
|
- $link = $asset['asset'];
|
|
|
- $relative_path = $link;
|
|
|
-
|
|
|
- if ($this->isRemoteLink($link)) {
|
|
|
- $local = false;
|
|
|
- if ('//' === substr($link, 0, 2)) {
|
|
|
- $link = 'http:' . $link;
|
|
|
- }
|
|
|
- } else {
|
|
|
- // Fix to remove relative dir if grav is in one
|
|
|
- if (($this->base_url != '/') && (strpos($this->base_url, $link) == 0)) {
|
|
|
- $base_url = '#' . preg_quote($this->base_url, '#') . '#';
|
|
|
- $relative_path = ltrim(preg_replace($base_url, '/', $link, 1), '/');
|
|
|
- }
|
|
|
-
|
|
|
- $relative_dir = dirname($relative_path);
|
|
|
- $link = ROOT_DIR . $relative_path;
|
|
|
- }
|
|
|
-
|
|
|
- $file = ($this->fetch_command instanceof Closure) ? @$this->fetch_command->__invoke($link) : @file_get_contents($link);
|
|
|
-
|
|
|
- // No file found, skip it...
|
|
|
- if ($file === false) {
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- // Double check last character being
|
|
|
- if (!$css) {
|
|
|
- $file = rtrim($file, ' ;') . ';';
|
|
|
- }
|
|
|
-
|
|
|
- // If this is CSS + the file is local + rewrite enabled
|
|
|
- if ($css && $local && $this->css_rewrite) {
|
|
|
- $file = $this->cssRewrite($file, $relative_dir);
|
|
|
- }
|
|
|
-
|
|
|
- $file = rtrim($file) . PHP_EOL;
|
|
|
- $buffer .= $file;
|
|
|
- }
|
|
|
-
|
|
|
- // Pull out @imports and move to top
|
|
|
- if ($css) {
|
|
|
- $buffer = $this->moveImports($buffer);
|
|
|
- }
|
|
|
-
|
|
|
- return $buffer;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Finds relative CSS urls() and rewrites the URL with an absolute one
|
|
|
- *
|
|
|
- * @param string $file the css source file
|
|
|
- * @param string $relative_path relative path to the css file
|
|
|
- *
|
|
|
- * @return mixed
|
|
|
- */
|
|
|
- protected function cssRewrite($file, $relative_path)
|
|
|
- {
|
|
|
- // 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 = preg_replace_callback(self::CSS_URL_REGEX, function ($matches) use ($relative_path) {
|
|
|
-
|
|
|
- $old_url = $matches[2];
|
|
|
-
|
|
|
- // Ensure link is not rooted to webserver, a data URL, or to a remote host
|
|
|
- if (Utils::startsWith($old_url, '/') || Utils::startsWith($old_url, 'data:') || $this->isRemoteLink($old_url)) {
|
|
|
- return $matches[0];
|
|
|
- }
|
|
|
-
|
|
|
- $new_url = $this->base_url . ltrim(Utils::normalizePath($relative_path . '/' . $old_url), '/');
|
|
|
-
|
|
|
- return str_replace($old_url, $new_url, $matches[0]);
|
|
|
- }, $file);
|
|
|
-
|
|
|
- return $file;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 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)
|
|
|
- {
|
|
|
- $this->imports = [];
|
|
|
-
|
|
|
- $file = preg_replace_callback(self::CSS_IMPORT_REGEX, function ($matches) {
|
|
|
- $this->imports[] = $matches[0];
|
|
|
-
|
|
|
- return '';
|
|
|
- }, $file);
|
|
|
-
|
|
|
- return implode("\n", $this->imports) . "\n\n" . $file;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Recursively get files matching $pattern within $directory.
|
|
|
- *
|
|
|
- * @param string $directory
|
|
|
- * @param string $pattern (regex)
|
|
|
- * @param string $ltrim Will be trimmed from the left of the file path
|
|
|
- *
|
|
|
- * @return array
|
|
|
- */
|
|
|
- protected function rglob($directory, $pattern, $ltrim = null)
|
|
|
- {
|
|
|
- $iterator = new RegexIterator(new RecursiveIteratorIterator(new RecursiveDirectoryIterator($directory,
|
|
|
- FilesystemIterator::SKIP_DOTS)), $pattern);
|
|
|
- $offset = strlen($ltrim);
|
|
|
- $files = [];
|
|
|
-
|
|
|
- foreach ($iterator as $file) {
|
|
|
- $files[] = substr($file->getPathname(), $offset);
|
|
|
- }
|
|
|
-
|
|
|
- return $files;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Sets the state of CSS Pipeline
|
|
|
- *
|
|
|
- * @param boolean $value
|
|
|
- */
|
|
|
- public function setCssPipeline($value)
|
|
|
- {
|
|
|
- $this->css_pipeline = (bool)$value;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Sets the state of JS Pipeline
|
|
|
- *
|
|
|
- * @param boolean $value
|
|
|
- */
|
|
|
- public function setJsPipeline($value)
|
|
|
- {
|
|
|
- $this->js_pipeline = (bool)$value;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Explicitly set's a timestamp for assets
|
|
|
- *
|
|
|
- * @param $value
|
|
|
- */
|
|
|
- public function setTimestamp($value)
|
|
|
- {
|
|
|
- $this->timestamp = $value;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Get the timestamp for assets
|
|
|
- *
|
|
|
- * @return string
|
|
|
- */
|
|
|
- public function getTimestamp($include_join = true)
|
|
|
- {
|
|
|
- if ($this->timestamp) {
|
|
|
- $timestamp = $include_join ? '?' . $this->timestamp : $this->timestamp;
|
|
|
- return $timestamp;
|
|
|
- }
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- *
|
|
|
- *
|
|
|
- * @param $asset
|
|
|
- * @return string
|
|
|
- */
|
|
|
- public function getQuerystring($asset)
|
|
|
- {
|
|
|
- $querystring = '';
|
|
|
-
|
|
|
- if (!empty($asset['query'])) {
|
|
|
- if (Utils::contains($asset['asset'], '?')) {
|
|
|
- $querystring .= '&' . $asset['query'];
|
|
|
- } else {
|
|
|
- $querystring .= '?' . $asset['query'];
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if ($this->timestamp) {
|
|
|
- if (Utils::contains($asset['asset'], '?') || $querystring) {
|
|
|
- $querystring .= '&' . $this->timestamp;
|
|
|
- } else {
|
|
|
- $querystring .= '?' . $this->timestamp;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return $querystring;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * @return string
|
|
|
- */
|
|
|
- public function __toString()
|
|
|
- {
|
|
|
- return '';
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * @param $a
|
|
|
- * @param $b
|
|
|
- *
|
|
|
- * @return mixed
|
|
|
- */
|
|
|
- protected function sortAssetsByPriorityThenOrder($a, $b)
|
|
|
- {
|
|
|
- if ($a['priority'] == $b['priority']) {
|
|
|
- return $a['order'] - $b['order'];
|
|
|
- }
|
|
|
-
|
|
|
- return $b['priority'] - $a['priority'];
|
|
|
- }
|
|
|
-
|
|
|
}
|