mirror of
https://github.com/wintercms/winter.git
synced 2024-06-28 05:33:29 +02:00
394 lines
12 KiB
PHP
394 lines
12 KiB
PHP
<?php namespace System\Traits;
|
|
|
|
use Url;
|
|
use Html;
|
|
use File;
|
|
use Event;
|
|
use System\Models\Parameter;
|
|
use System\Models\PluginVersion;
|
|
use System\Classes\CombineAssets;
|
|
|
|
/**
|
|
* Asset Maker Trait
|
|
* Adds asset based methods to a class
|
|
*
|
|
* @package winter\wn-system-module
|
|
* @author Alexey Bobkov, Samuel Georges
|
|
*/
|
|
trait AssetMaker
|
|
{
|
|
/**
|
|
* @var array Collection of assets to display in the layout.
|
|
*/
|
|
protected $assets = ['js'=>[], 'css'=>[], 'rss'=>[]];
|
|
|
|
/**
|
|
* @var string Specifies a path to the asset directory.
|
|
*/
|
|
public $assetPath;
|
|
|
|
/**
|
|
* Disables the use, and subequent broadcast, of assets. This is useful
|
|
* to call during an AJAX request to speed things up. This method works
|
|
* by specifically targeting the hasAssetsDefined method.
|
|
* @return void
|
|
*/
|
|
public function flushAssets()
|
|
{
|
|
$this->assets = ['js'=>[], 'css'=>[], 'rss'=>[]];
|
|
}
|
|
|
|
/**
|
|
* Outputs `<link>` and `<script>` tags to load assets previously added
|
|
* with addJs, addCss, & addRss method calls depending on the provided $type
|
|
*/
|
|
public function makeAssets(string $type = null): ?string
|
|
{
|
|
if ($type != null) {
|
|
$type = strtolower($type);
|
|
}
|
|
$result = null;
|
|
$reserved = ['build'];
|
|
|
|
$this->removeDuplicates();
|
|
|
|
if ($type == null || $type == 'css') {
|
|
foreach ($this->assets['css'] as $asset) {
|
|
/*
|
|
* Prevent duplicates
|
|
*/
|
|
$attributes = Html::attributes(array_merge(
|
|
[
|
|
'rel' => 'stylesheet',
|
|
'href' => $this->getAssetEntryBuildPath($asset)
|
|
],
|
|
array_except($asset['attributes'], $reserved)
|
|
));
|
|
|
|
$result .= '<link' . $attributes . '>' . PHP_EOL;
|
|
}
|
|
}
|
|
|
|
if ($type == null || $type == 'rss') {
|
|
foreach ($this->assets['rss'] as $asset) {
|
|
$attributes = Html::attributes(array_merge(
|
|
[
|
|
'rel' => 'alternate',
|
|
'href' => $this->getAssetEntryBuildPath($asset),
|
|
'title' => 'RSS',
|
|
'type' => 'application/rss+xml'
|
|
],
|
|
array_except($asset['attributes'], $reserved)
|
|
));
|
|
|
|
$result .= '<link' . $attributes . '>' . PHP_EOL;
|
|
}
|
|
}
|
|
|
|
if ($type == null || $type == 'js') {
|
|
foreach ($this->assets['js'] as $asset) {
|
|
$attributes = Html::attributes(array_merge(
|
|
[
|
|
'src' => $this->getAssetEntryBuildPath($asset)
|
|
],
|
|
array_except($asset['attributes'], $reserved)
|
|
));
|
|
|
|
$result .= '<script' . $attributes . '></script>' . PHP_EOL;
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Adds JavaScript asset to the asset list. Call $this->makeAssets() in a view
|
|
* to output corresponding markup.
|
|
* @param array|string $name Specifies a path (URL) or an array of paths to the script(s).
|
|
* @param array $attributes Adds extra HTML attributes to the asset link.
|
|
* @return void
|
|
*/
|
|
public function addJs($name, $attributes = [])
|
|
{
|
|
if (is_array($name)) {
|
|
$name = $this->combineAssets($name, $this->getLocalPath($this->assetPath));
|
|
}
|
|
|
|
// Alias october* assets to winter.*
|
|
if (str_contains($name, 'js/october')) {
|
|
$winterPath = str_replace('js/october', 'js/winter', $name);
|
|
if (file_exists(base_path(ltrim(parse_url($winterPath, PHP_URL_PATH), '/')))) {
|
|
$name = $winterPath;
|
|
}
|
|
}
|
|
|
|
$jsPath = $this->getAssetPath($name);
|
|
|
|
if (isset($this->controller)) {
|
|
$this->controller->addJs($jsPath, $attributes);
|
|
}
|
|
|
|
if (is_string($attributes)) {
|
|
$attributes = ['build' => $attributes];
|
|
}
|
|
|
|
$jsPath = $this->getAssetScheme($jsPath);
|
|
|
|
$this->addAsset('js', $jsPath, $attributes);
|
|
}
|
|
|
|
/**
|
|
* Adds StyleSheet asset to the asset list. Call $this->makeAssets() in a view
|
|
* to output corresponding markup.
|
|
* @param array|string $name Specifies a path (URL) or an array of paths to the stylesheet(s).
|
|
* @param array $attributes Adds extra HTML attributes to the asset link.
|
|
* @return void
|
|
*/
|
|
public function addCss($name, $attributes = [])
|
|
{
|
|
if (is_array($name)) {
|
|
$name = $this->combineAssets($name, $this->getLocalPath($this->assetPath));
|
|
}
|
|
|
|
$cssPath = $this->getAssetPath($name);
|
|
|
|
if (isset($this->controller)) {
|
|
$this->controller->addCss($cssPath, $attributes);
|
|
}
|
|
|
|
if (is_string($attributes)) {
|
|
$attributes = ['build' => $attributes];
|
|
}
|
|
|
|
$cssPath = $this->getAssetScheme($cssPath);
|
|
|
|
$this->addAsset('css', $cssPath, $attributes);
|
|
}
|
|
|
|
/**
|
|
* Adds an RSS link asset to the asset list. Call $this->makeAssets() in a view
|
|
* to output corresponding markup.
|
|
* @param string $name Specifies a path (URL) to the RSS channel
|
|
* @param array $attributes Adds extra HTML attributes to the asset link.
|
|
* @return void
|
|
*/
|
|
public function addRss($name, $attributes = [])
|
|
{
|
|
$rssPath = $this->getAssetPath($name);
|
|
|
|
if (isset($this->controller)) {
|
|
$this->controller->addRss($rssPath, $attributes);
|
|
}
|
|
|
|
if (is_string($attributes)) {
|
|
$attributes = ['build' => $attributes];
|
|
}
|
|
|
|
$rssPath = $this->getAssetScheme($rssPath);
|
|
|
|
$this->addAsset('rss', $rssPath, $attributes);
|
|
}
|
|
|
|
/**
|
|
* Adds the provided asset to the internal asset collections
|
|
*
|
|
* @param string $type The type of the asset: 'js' || 'css' || 'rss'
|
|
* @param string $path The path to the asset
|
|
* @param array $attributes The attributes for the asset
|
|
*/
|
|
protected function addAsset(string $type, string $path, array $attributes)
|
|
{
|
|
if (!in_array($path, $this->assets[$type])) {
|
|
/**
|
|
* @event system.assets.beforeAddAsset
|
|
* Provides an opportunity to inspect or modify an asset.
|
|
*
|
|
* The parameters provided are:
|
|
* string `$type`: The type of the asset being added
|
|
* string `$path`: The path to the asset being added
|
|
* array `$attributes`: The array of attributes for the asset being added.
|
|
*
|
|
* All the parameters are provided by reference for modification.
|
|
* This event is also a halting event, so returning false will prevent the
|
|
* current asset from being added. Note that duplicates are filtered out
|
|
* before the event is fired.
|
|
*
|
|
* Example usage:
|
|
*
|
|
* Event::listen('system.assets.beforeAddAsset', function (string &$type, string &$path, array &$attributes) {
|
|
* if (in_array($path, $blockedAssets)) {
|
|
* return false;
|
|
* }
|
|
* });
|
|
*
|
|
* Or
|
|
*
|
|
* $this->bindEvent('assets.beforeAddAsset', function (string &$type, string &$path, array &$attributes) {
|
|
* $attributes['special_cdn_flag'] = false;
|
|
* });
|
|
*
|
|
*/
|
|
if (
|
|
// Fire local event if exists
|
|
(
|
|
method_exists($this, 'fireEvent') &&
|
|
($this->fireEvent('assets.beforeAddAsset', [&$type, &$path, &$attributes], true) !== false)
|
|
) &&
|
|
// Fire global event
|
|
(Event::fire('system.assets.beforeAddAsset', [&$type, &$path, &$attributes], true) !== false)
|
|
) {
|
|
$this->assets[$type][] = ['path' => $path, 'attributes' => $attributes];
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Run the provided assets through the Asset Combiner
|
|
* @param array $assets Collection of assets
|
|
* @param string $localPath Prefix all assets with this path (optional)
|
|
* @return string
|
|
*/
|
|
public function combineAssets(array $assets, $localPath = '')
|
|
{
|
|
// Short circuit if no assets actually provided
|
|
if (empty($assets)) {
|
|
return '';
|
|
}
|
|
$assetPath = !empty($localPath) ? $localPath : $this->assetPath;
|
|
return Url::to(CombineAssets::combine($assets, $assetPath));
|
|
}
|
|
|
|
/**
|
|
* Returns an array of all registered asset paths.
|
|
* @return array
|
|
*/
|
|
public function getAssetPaths()
|
|
{
|
|
$this->removeDuplicates();
|
|
|
|
$assets = [];
|
|
foreach ($this->assets as $type => $collection) {
|
|
$assets[$type] = [];
|
|
foreach ($collection as $asset) {
|
|
$assets[$type][] = $this->getAssetEntryBuildPath($asset);
|
|
}
|
|
}
|
|
|
|
return $assets;
|
|
}
|
|
|
|
/**
|
|
* Locates a file based on it's definition. If the file starts with
|
|
* a forward slash, it will be returned in context of the application public path,
|
|
* otherwise it will be returned in context of the asset path.
|
|
* @param string $fileName File to load.
|
|
* @param string $assetPath Explicitly define an asset path.
|
|
* @return string Relative path to the asset file.
|
|
*/
|
|
public function getAssetPath($fileName, $assetPath = null)
|
|
{
|
|
if (starts_with($fileName, ['//', 'http://', 'https://'])) {
|
|
return $fileName;
|
|
}
|
|
|
|
if (!$assetPath) {
|
|
$assetPath = $this->assetPath;
|
|
}
|
|
|
|
// Process absolute or symbolized paths
|
|
$publicPath = File::localToPublic(File::symbolizePath($fileName));
|
|
if ($publicPath) {
|
|
$fileName = $publicPath;
|
|
}
|
|
|
|
if (substr($fileName, 0, 1) == '/' || $assetPath === null) {
|
|
return Url::asset($fileName);
|
|
}
|
|
|
|
return Url::asset($assetPath . '/' . $fileName);
|
|
}
|
|
|
|
/**
|
|
* Returns true if assets any have been added.
|
|
* @return bool
|
|
*/
|
|
public function hasAssetsDefined()
|
|
{
|
|
return count($this->assets, COUNT_RECURSIVE) > 3;
|
|
}
|
|
|
|
/**
|
|
* Internal helper, attaches a build code to an asset path
|
|
* @param array $asset Stored asset array
|
|
* @return string
|
|
*/
|
|
protected function getAssetEntryBuildPath($asset)
|
|
{
|
|
$path = $asset['path'];
|
|
if (isset($asset['attributes']['build'])) {
|
|
$build = $asset['attributes']['build'];
|
|
|
|
if ($build == 'core') {
|
|
$build = 'v' . Parameter::get('system::core.build', 1);
|
|
}
|
|
elseif ($pluginVersion = PluginVersion::getVersion($build)) {
|
|
$build = 'v' . $pluginVersion;
|
|
}
|
|
|
|
$path .= '?' . $build;
|
|
}
|
|
|
|
return $path;
|
|
}
|
|
|
|
/**
|
|
* Internal helper, get asset scheme
|
|
* @param string $asset Specifies a path (URL) to the asset.
|
|
* @return string
|
|
*/
|
|
protected function getAssetScheme($asset)
|
|
{
|
|
if (starts_with($asset, ['//', 'http://', 'https://'])) {
|
|
return $asset;
|
|
}
|
|
|
|
if (substr($asset, 0, 1) == '/') {
|
|
$asset = Url::asset($asset);
|
|
}
|
|
|
|
return $asset;
|
|
}
|
|
|
|
/**
|
|
* Removes duplicate assets from the entire collection.
|
|
* @return void
|
|
*/
|
|
protected function removeDuplicates()
|
|
{
|
|
foreach ($this->assets as $type => &$collection) {
|
|
$pathCache = [];
|
|
foreach ($collection as $key => $asset) {
|
|
if (!$path = array_get($asset, 'path')) {
|
|
continue;
|
|
}
|
|
|
|
if (isset($pathCache[$path])) {
|
|
array_forget($collection, $key);
|
|
continue;
|
|
}
|
|
|
|
$pathCache[$path] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
protected function getLocalPath(?string $relativePath)
|
|
{
|
|
$relativePath = File::symbolizePath((string) $relativePath);
|
|
if (!starts_with($relativePath, [base_path()])) {
|
|
$relativePath = base_path($relativePath);
|
|
}
|
|
return $relativePath;
|
|
}
|
|
}
|