diff --git a/administrator/templates/atum/Service/HTML/Atum.php b/administrator/templates/atum/Service/HTML/Atum.php index a28602f055e17..18c4c034596f7 100644 --- a/administrator/templates/atum/Service/HTML/Atum.php +++ b/administrator/templates/atum/Service/HTML/Atum.php @@ -123,7 +123,9 @@ public static function rootcolors(Registry $params): void if (count($root)) { - Factory::getDocument()->addStyleDeclaration(':root {' . implode($root) . '}'); + /** @var Joomla\CMS\WebAsset\WebAssetManager $wa */ + $wa = Factory::getApplication()->getDocument()->getWebAssetManager(); + $wa->addInlineStyle(':root {' . implode($root) . '}'); } } diff --git a/administrator/templates/atum/error_full.php b/administrator/templates/atum/error_full.php index f4a77b1941b2a..2e624008da494 100644 --- a/administrator/templates/atum/error_full.php +++ b/administrator/templates/atum/error_full.php @@ -57,7 +57,8 @@ $this->setMetaData('viewport', 'width=device-width, initial-scale=1'); // @TODO sync with _variables.scss $this->setMetaData('theme-color', '#1c3d5c'); -$this->addScriptDeclaration('cssVars();'); +$this->getWebAssetManager() + ->addInlineScript('cssVars();', ['position' => 'after'], ['type' => 'module'], ['css-vars-ponyfill']); // Opacity must be set before displaying the DOM, so don't move to a CSS file $css = ' @@ -69,7 +70,8 @@ } '; -$this->addStyleDeclaration($css); +$this->getWebAssetManager() + ->addInlineStyle($css); $monochrome = (bool) $this->params->get('monochrome'); diff --git a/administrator/templates/atum/error_login.php b/administrator/templates/atum/error_login.php index 648a04bc86ef4..e223110f95e3b 100644 --- a/administrator/templates/atum/error_login.php +++ b/administrator/templates/atum/error_login.php @@ -56,7 +56,8 @@ $this->setMetaData('viewport', 'width=device-width, initial-scale=1'); // @TODO sync with _variables.scss $this->setMetaData('theme-color', '#1c3d5c'); -$this->addScriptDeclaration('cssVars();'); +$this->getWebAssetManager() + ->addInlineScript('cssVars();', ['position' => 'after'], ['type' => 'module'], ['css-vars-ponyfill']); // Opacity must be set before displaying the DOM, so don't move to a CSS file $css = ' @@ -68,7 +69,8 @@ } '; -$this->addStyleDeclaration($css); +$this->getWebAssetManager() + ->addInlineStyle($css); $monochrome = (bool) $this->params->get('monochrome'); diff --git a/administrator/templates/atum/index.php b/administrator/templates/atum/index.php index 23eab61eac586..df3dbe79778a4 100644 --- a/administrator/templates/atum/index.php +++ b/administrator/templates/atum/index.php @@ -63,7 +63,8 @@ $this->setMetaData('viewport', 'width=device-width, initial-scale=1'); // @TODO sync with _variables.scss $this->setMetaData('theme-color', '#1c3d5c'); -$this->addScriptDeclaration('cssVars();'); +$this->getWebAssetManager() + ->addInlineScript('cssVars();', ['position' => 'after'], ['type' => 'module'], ['css-vars-ponyfill']); // Opacity must be set before displaying the DOM, so don't move to a CSS file $css = ' @@ -75,7 +76,8 @@ } '; -$this->addStyleDeclaration($css); +$this->getWebAssetManager() + ->addInlineStyle($css); $monochrome = (bool) $this->params->get('monochrome'); diff --git a/administrator/templates/atum/login.php b/administrator/templates/atum/login.php index 9b3d978b6aac1..6a76902f30bc1 100644 --- a/administrator/templates/atum/login.php +++ b/administrator/templates/atum/login.php @@ -59,7 +59,8 @@ $this->setMetaData('viewport', 'width=device-width, initial-scale=1'); // @TODO sync with _variables.scss $this->setMetaData('theme-color', '#1c3d5c'); -$this->addScriptDeclaration('cssVars();'); +$this->getWebAssetManager() + ->addInlineScript('cssVars();', ['position' => 'after'], ['type' => 'module'], ['css-vars-ponyfill']); // Opacity must be set before displaying the DOM, so don't move to a CSS file $css = ' @@ -71,7 +72,8 @@ } '; -$this->addStyleDeclaration($css); +$this->getWebAssetManager() + ->addInlineStyle($css); $monochrome = (bool) $this->params->get('monochrome'); diff --git a/libraries/src/Document/Document.php b/libraries/src/Document/Document.php index f8852bf9c6aad..4a07f4a363e17 100644 --- a/libraries/src/Document/Document.php +++ b/libraries/src/Document/Document.php @@ -150,6 +150,8 @@ class Document * * @var array * @since 1.7.0 + * + * @deprecated 5.0 Use WebAssetManager */ public $_script = array(); @@ -175,6 +177,8 @@ class Document * * @var array * @since 1.7.0 + * + * @deprecated 5.0 Use WebAssetManager */ public $_style = array(); @@ -545,6 +549,8 @@ public function addScript($url, $options = array(), $attribs = array()) * @return Document instance of $this to allow chaining * * @since 1.7.0 + * + * @deprecated 5.0 Use WebAssetManager */ public function addScriptDeclaration($content, $type = 'text/javascript') { @@ -655,6 +661,8 @@ public function addStyleSheet($url, $options = array(), $attribs = array()) * @return Document instance of $this to allow chaining * * @since 1.7.0 + * + * @deprecated 5.0 Use WebAssetManager */ public function addStyleDeclaration($content, $type = 'text/css') { diff --git a/libraries/src/Document/Renderer/Html/MetasRenderer.php b/libraries/src/Document/Renderer/Html/MetasRenderer.php index 7ff33e91080ab..84634947921de 100644 --- a/libraries/src/Document/Renderer/Html/MetasRenderer.php +++ b/libraries/src/Document/Renderer/Html/MetasRenderer.php @@ -49,11 +49,6 @@ public function render($head, $params = array(), $content = null) $wa = $this->_doc->getWebAssetManager(); $wc = $this->_doc->getScriptOptions('webcomponents'); - if ($this->_doc->getScriptOptions()) - { - $wa->useScript('core'); - } - // Check for AttachBehavior and web components foreach ($wa->getAssets('script', true) as $asset) { @@ -68,11 +63,31 @@ public function render($head, $params = array(), $content = null) } } - $this->_doc->addScriptOptions('webcomponents', $wc); + if ($wc) + { + $this->_doc->addScriptOptions('webcomponents', array_unique($wc)); + } // Trigger the onBeforeCompileHead event $app->triggerEvent('onBeforeCompileHead'); + // Add Script Options as inline asset + $scriptOptions = $this->_doc->getScriptOptions(); + + if ($scriptOptions) + { + $prettyPrint = (JDEBUG && \defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : false); + $jsonOptions = json_encode($scriptOptions, $prettyPrint); + $jsonOptions = $jsonOptions ? $jsonOptions : '{}'; + + $wa->addInlineScript( + $jsonOptions, + ['name' => 'joomla.script.options', 'position' => 'before'], + ['type' => 'application/json', 'class' => 'joomla-script-options new'], + ['core'] + ); + } + // Lock the AssetManager $wa->lock(); diff --git a/libraries/src/Document/Renderer/Html/ScriptsRenderer.php b/libraries/src/Document/Renderer/Html/ScriptsRenderer.php index a1a38f864dc0c..dc764b10c36d8 100644 --- a/libraries/src/Document/Renderer/Html/ScriptsRenderer.php +++ b/libraries/src/Document/Renderer/Html/ScriptsRenderer.php @@ -21,6 +21,15 @@ */ class ScriptsRenderer extends DocumentRenderer { + /** + * List of already rendered src + * + * @var array + * + * @since __DEPLOY_VERSION__ + */ + private $renderedSrc = []; + /** * Renders the document script tags and returns the results as a string * @@ -38,176 +47,295 @@ public function render($head, $params = array(), $content = null) $lnEnd = $this->_doc->_getLineEnd(); $tab = $this->_doc->_getTab(); $buffer = ''; - $mediaVersion = $this->_doc->getMediaVersion(); $wam = $this->_doc->getWebAssetManager(); $assets = $wam->getAssets('script', true); - $assets = array_merge(array_values($assets), $this->_doc->_scripts); - $renderedUrls = []; - $defaultJsMimes = array('text/javascript', 'application/javascript', 'text/x-javascript', 'application/x-javascript'); - $html5NoValueAttributes = array('defer', 'async'); + // Get a list of inline assets and their relation with regular assets + $inlineAssets = $wam->filterOutInlineAssets($assets); + $inlineRelation = $wam->getInlineRelation($inlineAssets); + + // Merge with existing scripts, for rendering + $assets = array_merge(array_values($assets), $this->_doc->_scripts); // Generate script file links foreach ($assets as $key => $item) { + // Check whether we have an Asset instance, or old array with attributes $asset = $item instanceof WebAssetItemInterface ? $item : null; - if ($asset && $asset->getOption('webcomponent')) + // Add src attribute for non Asset item + if (!$asset) { - continue; + $item['src'] = $key; } - if ($asset) + // Check for inline content "before" + if ($asset && !empty($inlineRelation[$asset->getName()]['before'])) { - $src = $asset->getUri(); - - if (!$src) + foreach ($inlineRelation[$asset->getName()]['before'] as $itemBefore) { - continue; - } + $buffer .= $this->renderInlineElement($itemBefore); - $attribs = $asset->getAttributes(); - $version = $asset->getVersion(); - $conditional = $asset->getOption('conditional'); + // Remove this item from inline queue + unset($inlineAssets[$itemBefore->getName()]); + } } - else - { - $src = $key; - $attribs = $item; - $version = isset($attribs['options']['version']) ? $attribs['options']['version'] : ''; - // Check if stylesheet uses IE conditional statements. - $conditional = !empty($attribs['options']['conditional']) ? $attribs['options']['conditional'] : null; - } + $buffer .= $this->renderElement($item); - // Prevent double rendering - if (!empty($renderedUrls[$src])) + // Check for inline content "after" + if ($asset && !empty($inlineRelation[$asset->getName()]['after'])) { - continue; + foreach ($inlineRelation[$asset->getName()]['after'] as $itemBefore) + { + $buffer .= $this->renderInlineElement($itemBefore); + + // Remove this item from inline queue + unset($inlineAssets[$itemBefore->getName()]); + } } + } - $renderedUrls[$src] = true; + // Generate script declarations for assets + foreach ($inlineAssets as $item) + { + $buffer .= $this->renderInlineElement($item); + } - // Check if script uses media version. - if ($version && strpos($src, '?') === false && ($mediaVersion || $version !== 'auto')) + // Generate script declarations for old scripts + foreach ($this->_doc->_script as $type => $contents) + { + // Test for B.C. in case someone still store script declarations as single string + if (\is_string($contents)) { - $src .= '?' . ($version === 'auto' ? $mediaVersion : $version); + $contents = [$contents]; } - $buffer .= $tab; - - // This is for IE conditional statements support. - if (!\is_null($conditional)) + foreach ($contents as $content) { - $buffer .= ''; - } + // Check if script uses media version. + if ($version && strpos($src, '?') === false && ($mediaVersion || $version !== 'auto')) + { + $src .= '?' . ($version === 'auto' ? $mediaVersion : $version); + } + + $buffer .= $tab; - $buffer .= $lnEnd; + // This is for IE conditional statements support. + if (!\is_null($conditional)) + { + $buffer .= ''; + } - foreach ($contents as $content) - { - $buffer .= $tab . '_doc->isHtml5() || !\in_array($type, $defaultJsMimes))) - { - $buffer .= ' type="' . $type . '"'; - } + return $buffer; + } - if ($this->_doc->cspNonce) - { - $buffer .= ' nonce="' . $this->_doc->cspNonce . '"'; - } + /** + * Renders the inline element + * + * @param WebAssetItemInterface|array $item The element + * + * @return string The resulting string + * + * @since __DEPLOY_VERSION__ + */ + private function renderInlineElement($item) : string + { + $buffer = ''; + $lnEnd = $this->_doc->_getLineEnd(); + $tab = $this->_doc->_getTab(); + + if ($item instanceof WebAssetItemInterface) + { + $attribs = $item->getAttributes(); + $content = $item->getOption('content'); + } + else + { + $attribs = $item; + $content = $item['content'] ?? ''; - $buffer .= '>' . $lnEnd; + unset($attribs['content']); + } - // This is for full XHTML support. - if ($this->_doc->_mime != 'text/html') - { - $buffer .= $tab . $tab . '//_doc->cspNonce) + { + $attribs['nonce'] = $this->_doc->cspNonce; + } - // See above note - if ($this->_doc->_mime != 'text/html') - { - $buffer .= $tab . $tab . '//]]>' . $lnEnd; - } + $buffer .= $tab . 'renderAttributes($attribs); + $buffer .= '>' . $lnEnd; - $buffer .= $tab . '' . $lnEnd; - } + // This is for full XHTML support. + if ($this->_doc->_mime !== 'text/html') + { + $buffer .= $tab . $tab . '//_doc->_custom) as $custom) + $buffer .= $content . $lnEnd; + + // See above note + if ($this->_doc->_mime !== 'text/html') { - $buffer .= $tab . $custom . $lnEnd; + $buffer .= $tab . $tab . '//]]>' . $lnEnd; } - return ltrim($buffer, $tab); + $buffer .= $tab . '' . $lnEnd; + + return $buffer; + } + + /** + * Renders the element attributes + * + * @param array $attributes The element attributes + * + * @return string The attributes string + * + * @since __DEPLOY_VERSION__ + */ + private function renderAttributes(array $attributes) : string + { + $buffer = ''; + + $defaultJsMimes = array('text/javascript', 'application/javascript', 'text/x-javascript', 'application/x-javascript'); + $html5NoValueAttributes = array('defer', 'async'); + + foreach ($attributes as $attrib => $value) + { + // Don't add the 'options' attribute. This attribute is for internal use (version, conditional, etc). + if ($attrib === 'options' || $attrib === 'src') + { + continue; + } + + // Don't add type attribute if document is HTML5 and it's a default mime type. 'mime' is for B/C. + if (\in_array($attrib, array('type', 'mime')) && $this->_doc->isHtml5() && \in_array($value, $defaultJsMimes)) + { + continue; + } + + // B/C: If defer and async is false or empty don't render the attribute. + if (\in_array($attrib, array('defer', 'async')) && !$value) + { + continue; + } + + // Don't add type attribute if document is HTML5 and it's a default mime type. 'mime' is for B/C. + if ($attrib === 'mime') + { + $attrib = 'type'; + } + // B/C defer and async can be set to yes when using the old method. + elseif (\in_array($attrib, array('defer', 'async')) && $value === true) + { + $value = $attrib; + } + + // Add attribute to script tag output. + $buffer .= ' ' . htmlspecialchars($attrib, ENT_COMPAT, 'UTF-8'); + + if (!($this->_doc->isHtml5() && \in_array($attrib, $html5NoValueAttributes))) + { + // Json encode value if it's an array. + $value = !is_scalar($value) ? json_encode($value) : $value; + + $buffer .= '="' . htmlspecialchars($value, ENT_COMPAT, 'UTF-8') . '"'; + } + } + + return $buffer; } } diff --git a/libraries/src/Document/Renderer/Html/StylesRenderer.php b/libraries/src/Document/Renderer/Html/StylesRenderer.php index 2fe4ddd722ca6..7d34872664309 100644 --- a/libraries/src/Document/Renderer/Html/StylesRenderer.php +++ b/libraries/src/Document/Renderer/Html/StylesRenderer.php @@ -21,6 +21,15 @@ */ class StylesRenderer extends DocumentRenderer { + /** + * List of already rendered src + * + * @var array + * + * @since __DEPLOY_VERSION__ + */ + private $renderedSrc = []; + /** * Renders the document stylesheets and style tags and returns the results as a string * @@ -34,177 +43,278 @@ class StylesRenderer extends DocumentRenderer */ public function render($head, $params = array(), $content = null) { - // Get line endings - $lnEnd = $this->_doc->_getLineEnd(); $tab = $this->_doc->_getTab(); - $tagEnd = ' />'; $buffer = ''; - $mediaVersion = $this->_doc->getMediaVersion(); $wam = $this->_doc->getWebAssetManager(); $assets = $wam->getAssets('style', true); - $assets = array_merge(array_values($assets), $this->_doc->_styleSheets); - $renderedUrls = []; - $defaultCssMimes = array('text/css'); + // Get a list of inline assets and their relation with regular assets + $inlineAssets = $wam->filterOutInlineAssets($assets); + $inlineRelation = $wam->getInlineRelation($inlineAssets); + + // Merge with existing styleSheets, for rendering + $assets = array_merge(array_values($assets), $this->_doc->_styleSheets); // Generate stylesheet links foreach ($assets as $key => $item) { $asset = $item instanceof WebAssetItemInterface ? $item : null; - if ($asset) + // Add href attribute for non Asset item + if (!$asset) { - $src = $asset->getUri(); + $item['href'] = $key; + } - if (!$src) + // Check for inline content "before" + if ($asset && !empty($inlineRelation[$asset->getName()]['before'])) + { + foreach ($inlineRelation[$asset->getName()]['before'] as $itemBefore) { - continue; - } + $buffer .= $this->renderInlineElement($itemBefore); - $attribs = $asset->getAttributes(); - $version = $asset->getVersion(); - $conditional = $asset->getOption('conditional'); + // Remove this item from inline queue + unset($inlineAssets[$itemBefore->getName()]); + } } - else - { - $src = $key; - $attribs = $item; - $version = isset($attribs['options']['version']) ? $attribs['options']['version'] : ''; - // Check if stylesheet uses IE conditional statements. - $conditional = !empty($attribs['options']['conditional']) ? $attribs['options']['conditional'] : null; - } + $buffer .= $this->renderElement($item); - // Prevent double rendering - if (!empty($renderedUrls[$src])) + // Check for inline content "after" + if ($asset && !empty($inlineRelation[$asset->getName()]['after'])) { - continue; + foreach ($inlineRelation[$asset->getName()]['after'] as $itemBefore) + { + $buffer .= $this->renderInlineElement($itemBefore); + + // Remove this item from inline queue + unset($inlineAssets[$itemBefore->getName()]); + } } + } - $renderedUrls[$src] = true; + // Generate script declarations for assets + foreach ($inlineAssets as $item) + { + $buffer .= $this->renderInlineElement($item); + } - // Check if script uses media version. - if ($version && strpos($src, '?') === false && ($mediaVersion || $version !== 'auto')) + // Generate stylesheet declarations + foreach ($this->_doc->_style as $type => $contents) + { + // Test for B.C. in case someone still store stylesheet declarations as single string + if (\is_string($contents)) { - $src .= '?' . ($version === 'auto' ? $mediaVersion : $version); + $contents = [$contents]; } - $buffer .= $tab; - - // This is for IE conditional statements support. - if (!\is_null($conditional)) + foreach ($contents as $content) { - $buffer .= ''; - } + // Render the element with attributes + $buffer .= 'renderAttributes($attribs); + $buffer .= ' />'; - $buffer .= $lnEnd; + // This is for IE conditional statements support. + if (!\is_null($conditional)) + { + $buffer .= ''; } - // Generate stylesheet declarations - foreach ($this->_doc->_style as $type => $contents) - { - // Test for B.C. in case someone still store stylesheet declarations as single string - if (\is_string($contents)) - { - $contents = [$contents]; - } + $buffer .= $lnEnd; - foreach ($contents as $content) - { - $buffer .= $tab . '_doc->isHtml5() || !\in_array($type, $defaultCssMimes))) - { - $buffer .= ' type="' . $type . '"'; - } + /** + * Renders the inline element + * + * @param WebAssetItemInterface|array $item The element + * + * @return string The resulting string + * + * @since __DEPLOY_VERSION__ + */ + private function renderInlineElement($item) : string + { + $buffer = ''; + $lnEnd = $this->_doc->_getLineEnd(); + $tab = $this->_doc->_getTab(); - if ($this->_doc->cspNonce) - { - $buffer .= ' nonce="' . $this->_doc->cspNonce . '"'; - } + if ($item instanceof WebAssetItemInterface) + { + $attribs = $item->getAttributes(); + $content = $item->getOption('content'); + } + else + { + $attribs = $item; + $content = $item['content'] ?? ''; - $buffer .= '>' . $lnEnd; + unset($attribs['content']); + } - // This is for full XHTML support. - if ($this->_doc->_mime != 'text/html') - { - $buffer .= $tab . $tab . '/*_doc->cspNonce) + { + $attribs['nonce'] = $this->_doc->cspNonce; + } - // See above note - if ($this->_doc->_mime != 'text/html') - { - $buffer .= $tab . $tab . '/*]]>*/' . $lnEnd; - } + $buffer .= $tab . 'renderAttributes($attribs); + $buffer .= '>' . $lnEnd; - $buffer .= $tab . '' . $lnEnd; - } + // This is for full XHTML support. + if ($this->_doc->_mime != 'text/html') + { + $buffer .= $tab . $tab . '/*_doc->getScriptOptions(); + $buffer .= $content . $lnEnd; + + // See above note + if ($this->_doc->_mime != 'text/html') + { + $buffer .= $tab . $tab . '/*]]>*/' . $lnEnd; + } + + $buffer .= $tab . '' . $lnEnd; + + return $buffer; + } + + /** + * Renders the element attributes + * + * @param array $attributes The element attributes + * + * @return string The attributes string + * + * @since __DEPLOY_VERSION__ + */ + private function renderAttributes(array $attributes) : string + { + $buffer = ''; + + $defaultCssMimes = array('text/css'); - if (!empty($scriptOptions)) + foreach ($attributes as $attrib => $value) { - $nonce = ''; + // Don't add the 'options' attribute. This attribute is for internal use (version, conditional, etc). + if ($attrib === 'options' || $attrib === 'href') + { + continue; + } - if ($this->_doc->cspNonce) + // Don't add type attribute if document is HTML5 and it's a default mime type. 'mime' is for B/C. + if (\in_array($attrib, array('type', 'mime')) && $this->_doc->isHtml5() && \in_array($value, $defaultCssMimes)) { - $nonce = ' nonce="' . $this->_doc->cspNonce . '"'; + continue; } - $buffer .= $tab . '' . $lnEnd; + $buffer .= '="' . htmlspecialchars($value, ENT_COMPAT, 'UTF-8') . '"'; } - return ltrim($buffer, $tab); + return $buffer; } } diff --git a/libraries/src/WebAsset/WebAssetManager.php b/libraries/src/WebAsset/WebAssetManager.php index 1e0f2d05fe953..ab6027c6aac56 100644 --- a/libraries/src/WebAsset/WebAssetManager.php +++ b/libraries/src/WebAsset/WebAssetManager.php @@ -22,11 +22,13 @@ * @method WebAssetManager registerAndUseStyle(WebAssetItem|string $asset, string $uri = '', $options = [], $attributes = [], $dependencies = []) * @method WebAssetManager useStyle($name) * @method WebAssetManager disableStyle($name) + * @method WebAssetManager addInlineStyle(WebAssetItem|string $content, $options = [], $attributes = [], $dependencies = []) * * @method WebAssetManager registerScript(WebAssetItem|string $asset, string $uri = '', $options = [], $attributes = [], $dependencies = []) * @method WebAssetManager registerAndUseScript(WebAssetItem|string $asset, string $uri = '', $options = [], $attributes = [], $dependencies = []) * @method WebAssetManager useScript($name) * @method WebAssetManager disableScript($name) + * @method WebAssetManager addInlineScript(WebAssetItem|string $content, $options = [], $attributes = [], $dependencies = []) * * @method WebAssetManager registerPreset(WebAssetItem|string $asset, string $uri = '', $options = [], $attributes = [], $dependencies = []) * @method WebAssetManager registerAndUsePreset(WebAssetItem|string $asset, string $uri = '', $options = [], $attributes = [], $dependencies = []) @@ -165,9 +167,11 @@ public function getRegistry(): WebAssetRegistry */ public function __call($method, $arguments) { + $method = strtolower($method); + if (0 === strpos($method, 'use')) { - $type = strtolower(substr($method, 3)); + $type = substr($method, 3); if (empty($arguments[0])) { @@ -177,9 +181,21 @@ public function __call($method, $arguments) return $this->useAsset($type, $arguments[0]); } + if (0 === strpos($method, 'addinline')) + { + $type = substr($method, 9); + + if (empty($arguments[0])) + { + throw new \BadMethodCallException('A content are required'); + } + + return $this->addInline($type, ...$arguments); + } + if (0 === strpos($method, 'disable')) { - $type = strtolower(substr($method, 7)); + $type = substr($method, 7); if (empty($arguments[0])) { @@ -192,10 +208,10 @@ public function __call($method, $arguments) if (0 === strpos($method, 'register')) { // Check for registerAndUse - $andUse = strtolower(substr($method, 8, 6)) === 'anduse'; + $andUse = substr($method, 8, 6) === 'anduse'; // Extract the type - $type = $andUse ? strtolower(substr($method, 14)) : strtolower(substr($method, 8)); + $type = $andUse ? substr($method, 14) : substr($method, 8); if (empty($arguments[0])) { @@ -458,7 +474,7 @@ public function isAssetActive(string $type, string $name): bool } /** - * Check whether the asset exists in the registry. + * Helper method to check whether the asset exists in the registry. * * @param string $type Asset type, script or style * @param string $name Asset name @@ -509,7 +525,24 @@ public function registerAsset(string $type, $asset, string $uri = '', array $opt } /** - * Get all active assets + * Helper method to get the asset from the registry. + * + * @param string $type Asset type, script or style + * @param string $name Asset name + * + * @return WebAssetItemInterface + * + * @throws UnknownAssetException When Asset cannot be found + * + * @since __DEPLOY_VERSION__ + */ + public function getAsset(string $type, string $name): WebAssetItemInterface + { + return $this->registry->get($type, $name); + } + + /** + * Get all active assets, optionally sort them to follow the dependency Graph * * @param string $type The asset type, script or style * @param bool $sort Whether need to sort the assets to follow the dependency Graph @@ -552,6 +585,127 @@ public function getAssets(string $type, bool $sort = false): array return $assets; } + /** + * Helper method to calculate inline to non inline relation (before/after positions). + * Return associated array, which contain dependency (handle) name as key, and list of inline items for each position. + * Example: ['handle.name' => ['before' => ['inline1', 'inline2'], 'after' => ['inline3', 'inline4']]] + * + * Note: If inline asset have a multiple dependencies, then will be used last one from the list for positioning + * + * @param WebAssetItem[] $assets The assets list + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + public function getInlineRelation(array $assets): array + { + $inlineRelation = []; + + // Find an inline assets and their relations to non inline + foreach ($assets as $k => $asset) + { + if (!$asset->getOption('inline')) + { + continue; + } + + // Add to list of inline assets + $inlineAssets[$asset->getName()] = $asset; + + // Check whether position are requested with dependencies + $position = $asset->getOption('position'); + $position = $position === 'before' || $position === 'after' ? $position : null; + $deps = $asset->getDependencies(); + + if ($position && $deps) + { + // If inline asset have a multiple dependencies, then use last one from the list for positioning + $handle = end($deps); + $inlineRelation[$handle][$position][$asset->getName()] = $asset; + } + } + + return $inlineRelation; + } + + /** + * Helper method to filter an inline assets + * + * @param WebAssetItem[] $assets Reference to a full list of active assets + * + * @return WebAssetItem[] Array of inline assets + * + * @since __DEPLOY_VERSION__ + */ + public function filterOutInlineAssets(array &$assets): array + { + $inlineAssets = []; + + foreach ($assets as $k => $asset) + { + if (!$asset->getOption('inline')) + { + continue; + } + + // Remove inline assets from assets list, and add to list of inline + unset($assets[$k]); + + $inlineAssets[$asset->getName()] = $asset; + } + + return $inlineAssets; + } + + /** + * Add a new inline content asset. + * Allow to register WebAssetItem instance in the registry, by call addInline($type, $assetInstance) + * Or create an asset on fly (from name and Uri) and register in the registry, by call addInline($type, $content, $options ....) + * + * @param string $type The asset type, script or style + * @param WebAssetItem|string $content The content to of inline asset + * @param array $options Additional options for the asset + * @param array $attributes Attributes for the asset + * @param array $dependencies Asset dependencies + * + * @return self + * + * @since __DEPLOY_VERSION__ + */ + public function addInline(string $type, string $content, array $options = [], array $attributes = [], array $dependencies = []): self + { + if ($content instanceof WebAssetItemInterface) + { + $assetInstance = $content; + } + elseif (is_string($content)) + { + $name = $options['name'] ?? ('inline.' . md5($content)); + $assetInstance = $this->registry->createAsset($name, '', $options, $attributes, $dependencies); + } + else + { + throw new \BadMethodCallException('The $content variable should be either WebAssetItemInterface or a string'); + } + + // Get the name + $asset = $assetInstance->getName(); + + // Set required options + $assetInstance->setOption('type', $type); + $assetInstance->setOption('inline', true); + $assetInstance->setOption('content', $content); + + // Add to registry + $this->registry->add($type, $assetInstance); + + // And make active + $this->useAsset($type, $asset); + + return $this; + } + /** * Lock the manager to prevent further modifications *