__  __    __   __  _____      _            _          _____ _          _ _ 
 |  \/  |   \ \ / / |  __ \    (_)          | |        / ____| |        | | |
 | \  / |_ __\ V /  | |__) | __ ___   ____ _| |_ ___  | (___ | |__   ___| | |
 | |\/| | '__|> <   |  ___/ '__| \ \ / / _` | __/ _ \  \___ \| '_ \ / _ \ | |
 | |  | | |_ / . \  | |   | |  | |\ V / (_| | ||  __/  ____) | | | |  __/ | |
 |_|  |_|_(_)_/ \_\ |_|   |_|  |_| \_/ \__,_|\__\___| |_____/|_| |_|\___V 2.1
 if you need WebShell for Seo everyday contact me on Telegram
 Telegram Address : @jackleet
        
        
For_More_Tools: Telegram: @jackleet | Bulk Smtp support mail sender | Business Mail Collector | Mail Bouncer All Mail | Bulk Office Mail Validator | Html Letter private



Upload:

Command:

[email protected]: ~ $
<?php

/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2019 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\WebAsset;

use Joomla\CMS\Document\Document;
use Joomla\CMS\Event\WebAsset\WebAssetRegistryAssetChanged;
use Joomla\CMS\WebAsset\Exception\InvalidActionException;
use Joomla\CMS\WebAsset\Exception\UnknownAssetException;
use Joomla\CMS\WebAsset\Exception\UnsatisfiedDependencyException;

// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects

/**
 * Web Asset Manager class
 *
 * @method WebAssetManager registerStyle(WebAssetItem|string $asset, string $uri = '', $options = [], $attributes = [], $dependencies = [])
 * @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 = [])
 * @method WebAssetManager usePreset($name)
 * @method WebAssetManager disablePreset($name)
 *
 * @since  4.0.0
 */
class WebAssetManager implements WebAssetManagerInterface
{
    /**
     * Mark inactive asset
     *
     * @var    integer
     *
     * @since  4.0.0
     */
    public const ASSET_STATE_INACTIVE = 0;

    /**
     * Mark active asset. Just enabled, but WITHOUT dependency resolved
     *
     * @var    integer
     *
     * @since  4.0.0
     */
    public const ASSET_STATE_ACTIVE = 1;

    /**
     * Mark active asset that is enabled as dependency to another asset
     *
     * @var    integer
     *
     * @since  4.0.0
     */
    public const ASSET_STATE_DEPENDENCY = 2;

    /**
     * The WebAsset Registry instance
     *
     * @var    WebAssetRegistry
     *
     * @since  4.0.0
     */
    protected $registry;

    /**
     * A list of active assets (including their dependencies).
     * Array of Name => State
     *
     * @var    array
     *
     * @since  4.0.0
     */
    protected $activeAssets = [];

    /**
     * Internal marker to check the manager state,
     * to prevent use of the manager after an assets are rendered
     *
     * @var    boolean
     *
     * @since  4.0.0
     */
    protected $locked = false;

    /**
     * Internal marker to keep track when need to recheck dependencies
     *
     * @var    boolean
     *
     * @since  4.0.0
     */
    protected $dependenciesIsActual = false;

    /**
     * A cache holder for list of sorted assets, used by getAssets() method
     * This is emptied when dependencies actualised, see enableDependencies() method
     *
     * @var    array
     *
     * @since  5.1.0
     */
    protected $sortedAssets = [];

    /**
     * Class constructor
     *
     * @param   WebAssetRegistry  $registry  The WebAsset Registry instance
     *
     * @since   4.0.0
     */
    public function __construct(WebAssetRegistry $registry)
    {
        $this->registry = $registry;

        // Listen to changes in the registry
        $this->registry->getDispatcher()->addListener(
            'onWebAssetRegistryChangedAssetOverride',
            function (WebAssetRegistryAssetChanged $event) {
                // If the changed asset are used
                if (!empty($this->activeAssets[$event->getAssetType()][$event->getAsset()->getName()])) {
                    $this->dependenciesIsActual = false;
                }
            }
        );

        $this->registry->getDispatcher()->addListener(
            'onWebAssetRegistryChangedAssetRemove',
            function (WebAssetRegistryAssetChanged $event) {
                // If the changed asset are used
                if (!empty($this->activeAssets[$event->getAssetType()][$event->getAsset()->getName()])) {
                    $this->dependenciesIsActual = false;
                }
            }
        );
    }

    /**
     * Get associated registry instance
     *
     * @return   WebAssetRegistry
     *
     * @since  4.0.0
     */
    public function getRegistry(): WebAssetRegistry
    {
        return $this->registry;
    }

    /**
     * Clears all collected items.
     *
     * @return self
     *
     * @since  4.1.1
     */
    public function reset(): WebAssetManagerInterface
    {
        if ($this->locked) {
            throw new InvalidActionException('WebAssetManager is locked');
        }

        $this->activeAssets         = [];
        $this->dependenciesIsActual = false;

        return $this;
    }

    /**
     * Adds support for magic method calls
     *
     * @param   string  $method     A method name
     * @param   array   $arguments  Arguments for a method
     *
     * @return mixed
     *
     * @throws  \BadMethodCallException
     *
     * @since  4.0.0
     */
    public function __call($method, $arguments)
    {
        $method = strtolower($method);

        if (str_starts_with($method, 'use')) {
            $type = substr($method, 3);

            if (empty($arguments[0])) {
                throw new \BadMethodCallException('An asset name is required');
            }

            return $this->useAsset($type, $arguments[0]);
        }

        if (str_starts_with($method, 'addinline')) {
            $type = substr($method, 9);

            if (empty($arguments[0])) {
                throw new \BadMethodCallException('Content is required');
            }

            return $this->addInline($type, ...$arguments);
        }

        if (str_starts_with($method, 'disable')) {
            $type = substr($method, 7);

            if (empty($arguments[0])) {
                throw new \BadMethodCallException('An asset name is required');
            }

            return $this->disableAsset($type, $arguments[0]);
        }

        if (str_starts_with($method, 'register')) {
            // Check for registerAndUse<Type>
            $andUse = substr($method, 8, 6) === 'anduse';

            // Extract the type
            $type = $andUse ? substr($method, 14) : substr($method, 8);

            if (empty($arguments[0])) {
                throw new \BadMethodCallException('An asset instance or an asset name is required');
            }

            if ($andUse) {
                $name = $arguments[0] instanceof WebAssetItemInterface ? $arguments[0]->getName() : $arguments[0];

                return $this->registerAsset($type, ...$arguments)->useAsset($type, $name);
            }

            return $this->registerAsset($type, ...$arguments);
        }

        throw new \BadMethodCallException(\sprintf('Undefined method %s in class %s', $method, \get_class($this)));
    }

    /**
     * Enable an asset item to be attached to a Document
     *
     * @param   string  $type  The asset type, script or style
     * @param   string  $name  The asset name
     *
     * @return self
     *
     * @throws  UnknownAssetException  When Asset cannot be found
     * @throws  InvalidActionException When the Manager already attached to a Document
     *
     * @since  4.0.0
     */
    public function useAsset(string $type, string $name): WebAssetManagerInterface
    {
        if ($this->locked) {
            throw new InvalidActionException('WebAssetManager is locked, you came late');
        }

        // Check whether asset exists
        $asset = $this->registry->get($type, $name);

        if (empty($this->activeAssets[$type])) {
            $this->activeAssets[$type] = [];
        }

        // For "preset" need to check the dependencies first
        if ($type === 'preset') {
            $this->usePresetItems($name);
        }

        // Asset already enabled
        if (!empty($this->activeAssets[$type][$name])) {
            // Set state to active, in case it was ASSET_STATE_DEPENDENCY
            $this->activeAssets[$type][$name] = static::ASSET_STATE_ACTIVE;

            return $this;
        }

        $this->activeAssets[$type][$name] = static::ASSET_STATE_ACTIVE;

        // To re-check dependencies
        if ($asset->getDependencies()) {
            $this->dependenciesIsActual = false;
        }

        // To re-order assets
        $this->sortedAssets[$type] = [];

        return $this;
    }

    /**
     * Deactivate an asset item, so it will not be attached to a Document
     *
     * @param   string  $type  The asset type, script or style
     * @param   string  $name  The asset name
     *
     * @return  self
     *
     * @throws  UnknownAssetException  When Asset cannot be found
     * @throws  InvalidActionException When the Manager already attached to a Document
     *
     * @since  4.0.0
     */
    public function disableAsset(string $type, string $name): WebAssetManagerInterface
    {
        if ($this->locked) {
            throw new InvalidActionException('WebAssetManager is locked, you came late');
        }

        // Check whether asset exists
        $this->registry->get($type, $name);

        unset($this->activeAssets[$type][$name]);

        // To re-check dependencies
        $this->dependenciesIsActual = false;

        // To re-order assets
        $this->sortedAssets[$type] = [];

        // For Preset case
        if ($type === 'preset') {
            $this->disablePresetItems($name);
        }

        return $this;
    }

    /**
     * Enable list of assets provided by Preset item.
     *
     * "Preset" a special kind of asset that hold a list of assets that has to be enabled,
     * same as direct call of useAsset() to each of item in list.
     * Can hold mixed types of assets (script, style, another preset, etc), the type provided after # symbol, after
     * the asset name, example: foo#style, bar#script.
     *
     * The method call useAsset() internally for each of its dependency, this is important for keeping FIFO order
     * of enabled items.
     * The Preset not a strict asset, and each of its dependency can be safely disabled by use of disableAsset() later.
     *
     * @param   string  $name  The asset name
     *
     * @return self
     *
     * @throws  UnsatisfiedDependencyException  When Asset dependency cannot be found
     *
     * @since  4.0.0
     */
    protected function usePresetItems($name): WebAssetManagerInterface
    {
        // Get the asset object
        $asset = $this->registry->get('preset', $name);

        // Call useAsset() to each of its dependency
        foreach ($asset->getDependencies() as $dependency) {
            $depType = '';
            $depName = $dependency;
            $pos     = strrpos($dependency, '#');

            // Check for cross-dependency "dependency-name#type" case
            if ($pos) {
                $depType = substr($dependency, $pos + 1);
                $depName = substr($dependency, 0, $pos);
            }

            $depType = $depType ?: 'preset';

            // Make sure dependency exists
            if (!$this->registry->exists($depType, $depName)) {
                throw new UnsatisfiedDependencyException(
                    \sprintf('Unsatisfied dependency "%s" for an asset "%s" of type "%s"', $dependency, $name, 'preset')
                );
            }

            $this->useAsset($depType, $depName);
        }

        return $this;
    }

    /**
     * Deactivate list of assets provided by Preset item.
     *
     * @param   string  $name  The asset name
     *
     * @return  self
     *
     * @throws  UnsatisfiedDependencyException  When Asset dependency cannot be found
     *
     * @since  4.0.0
     */
    protected function disablePresetItems($name): WebAssetManagerInterface
    {
        // Get the asset object
        $asset = $this->registry->get('preset', $name);

        // Call disableAsset() to each of its dependency
        foreach ($asset->getDependencies() as $dependency) {
            $depType = '';
            $depName = $dependency;
            $pos     = strrpos($dependency, '#');

            // Check for cross-dependency "dependency-name#type" case
            if ($pos) {
                $depType = substr($dependency, $pos + 1);
                $depName = substr($dependency, 0, $pos);
            }

            $depType = $depType ?: 'preset';

            // Make sure dependency exists
            if (!$this->registry->exists($depType, $depName)) {
                throw new UnsatisfiedDependencyException(
                    \sprintf('Unsatisfied dependency "%s" for an asset "%s" of type "%s"', $dependency, $name, 'preset')
                );
            }

            $this->disableAsset($depType, $depName);
        }

        return $this;
    }

    /**
     * Get a state for the Asset
     *
     * @param   string  $type  The asset type, script or style
     * @param   string  $name  The asset name
     *
     * @return  integer
     *
     * @throws  UnknownAssetException  When Asset cannot be found
     *
     * @since  4.0.0
     */
    public function getAssetState(string $type, string $name): int
    {
        // Check whether asset exists first
        $this->registry->get($type, $name);

        // Make sure that all dependencies are active
        if (!$this->dependenciesIsActual) {
            $this->enableDependencies();
        }

        if (!empty($this->activeAssets[$type][$name])) {
            return $this->activeAssets[$type][$name];
        }

        return static::ASSET_STATE_INACTIVE;
    }

    /**
     * Check whether the asset are enabled
     *
     * @param   string  $type  The asset type, script or style
     * @param   string  $name  The asset name
     *
     * @return  boolean
     *
     * @throws  UnknownAssetException  When Asset cannot be found
     *
     * @since  4.0.0
     */
    public function isAssetActive(string $type, string $name): bool
    {
        return $this->getAssetState($type, $name) !== static::ASSET_STATE_INACTIVE;
    }

    /**
     * Helper method to check whether the asset exists in the registry.
     *
     * @param   string  $type  Asset type, script or style
     * @param   string  $name  Asset name
     *
     * @return  boolean
     *
     * @since   4.0.0
     */
    public function assetExists(string $type, string $name): bool
    {
        return $this->registry->exists($type, $name);
    }

    /**
     * Register a new asset.
     * Allow to register WebAssetItem instance in the registry, by call registerAsset($type, $assetInstance)
     * Or create an asset on fly (from name and Uri) and register in the registry, by call registerAsset($type, $assetName, $uri, $options ....)
     *
     * @param   string               $type          The asset type, script or style
     * @param   WebAssetItem|string  $asset         The asset name or instance to register
     * @param   string               $uri           The URI for the asset
     * @param   array                $options       Additional options for the asset
     * @param   array                $attributes    Attributes for the asset
     * @param   array                $dependencies  Asset dependencies
     *
     * @return  self
     *
     * @since  4.0.0
     *
     * @throws  \InvalidArgumentException
     */
    public function registerAsset(string $type, $asset, string $uri = '', array $options = [], array $attributes = [], array $dependencies = [])
    {
        if ($asset instanceof WebAssetItemInterface) {
            $this->registry->add($type, $asset);
        } elseif (\is_string($asset)) {
            $options['type'] = $type;
            $assetInstance   = $this->registry->createAsset($asset, $uri, $options, $attributes, $dependencies);
            $this->registry->add($type, $assetInstance);
        } else {
            throw new \InvalidArgumentException(
                \sprintf(
                    '%s(): Argument #2 ($asset) must be a string or an instance of %s, %s given.',
                    __METHOD__,
                    WebAssetItemInterface::class,
                    \is_object($asset) ? \get_class($asset) : \gettype($asset)
                )
            );
        }

        return $this;
    }

    /**
     * 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   4.0.0
     */
    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 we need to sort the assets to follow the dependency Graph
     *
     * @return  WebAssetItem[]
     *
     * @throws  UnknownAssetException  When Asset cannot be found
     * @throws  UnsatisfiedDependencyException When Dependency cannot be found
     *
     * @since  4.0.0
     */
    public function getAssets(string $type, bool $sort = false): array
    {
        // Make sure that all dependencies are active
        if (!$this->dependenciesIsActual) {
            $this->enableDependencies();
        }

        if (empty($this->activeAssets[$type])) {
            return [];
        }

        // Apply Tree sorting for regular asset items, but return FIFO order for "preset"
        if ($sort && $type !== 'preset') {
            // Check previous calculations
            if (!empty($this->sortedAssets[$type])) {
                $assets = [];

                foreach ($this->sortedAssets[$type] as $name) {
                    $assets[$name] = $this->registry->get($type, $name);
                }
            } else {
                $assets = $this->calculateOrderOfActiveAssets($type);

                // Cache the result
                $this->sortedAssets[$type] = array_keys($assets);
            }
        } else {
            $assets = [];

            foreach (array_keys($this->activeAssets[$type]) as $name) {
                $assets[$name] = $this->registry->get($type, $name);
            }
        }

        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  4.0.0
     */
    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;
            }

            // 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  4.0.0
     */
    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  4.0.0
     *
     * @throws \InvalidArgumentException
     */
    public function addInline(string $type, $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);
            $assetInstance->setOption('content', $content);
        } else {
            throw new \InvalidArgumentException(
                \sprintf(
                    '%s(): Argument #2 ($content) must be a string or an instance of %s, %s given.',
                    __METHOD__,
                    WebAssetItemInterface::class,
                    \is_object($content) ? \get_class($content) : \gettype($content)
                )
            );
        }

        // Get the name
        $asset = $assetInstance->getName();

        // Set required options
        $assetInstance->setOption('type', $type);
        $assetInstance->setOption('inline', true);

        // Add to registry
        $this->registry->add($type, $assetInstance);

        // And make active
        $this->useAsset($type, $asset);

        return $this;
    }

    /**
     * Lock the manager to prevent further modifications
     *
     * @return self
     *
     * @since  4.0.0
     */
    public function lock(): self
    {
        $this->locked = true;

        return $this;
    }

    /**
     * Get the manager state. A collection of registry files and active asset names (per type).
     *
     * @return array
     *
     * @since  4.0.0
     */
    public function getManagerState(): array
    {
        return [
            'registryFiles' => $this->getRegistry()->getRegistryFiles(),
            'activeAssets'  => $this->activeAssets,
        ];
    }

    /**
     * Update Dependencies state for all active Assets or only for given
     *
     * @param   ?string        $type   The asset type, script or style
     * @param   ?WebAssetItem  $asset  The asset instance to which need to enable dependencies
     *
     * @return  self
     *
     * @since  4.0.0
     */
    protected function enableDependencies(?string $type = null, ?WebAssetItem $asset = null): self
    {
        if ($type === 'preset') {
            // Preset items already was enabled by usePresetItems()
            return $this;
        }

        if ($asset) {
            // Get all dependencies of given asset recursively
            $allDependencies = $this->getDependenciesForAsset($type, $asset, true);

            foreach ($allDependencies as $depType => $depItems) {
                foreach ($depItems as $depItem) {
                    // Set dependency state only when it is inactive, to keep a manually activated Asset in their original state
                    if (empty($this->activeAssets[$depType][$depItem->getName()])) {
                        // Add the dependency at the top of the list of active assets
                        $this->activeAssets[$depType] = [$depItem->getName() => static::ASSET_STATE_DEPENDENCY] + $this->activeAssets[$depType];
                    }
                }
            }
        } else {
            // Re-Check for dependencies for all active assets
            // Firstly, filter out only active assets
            foreach ($this->activeAssets as $type => $activeAsset) {
                $this->activeAssets[$type] = array_filter(
                    $activeAsset,
                    function ($state) {
                        return $state === WebAssetManager::ASSET_STATE_ACTIVE;
                    }
                );
            }

            // Secondary, check for dependencies of each active asset
            // This need to be separated from previous step because we may have "cross type" dependency
            foreach ($this->activeAssets as $type => $activeAsset) {
                foreach (array_keys($activeAsset) as $name) {
                    $asset = $this->registry->get($type, $name);
                    $this->enableDependencies($type, $asset);
                }
            }

            // Update state flag and clear sorting cache
            $this->dependenciesIsActual = true;
            $this->sortedAssets         = [];
        }

        return $this;
    }

    /**
     * Calculate weight of active Assets, by its Dependencies
     *
     * @param   string  $type  The asset type, script or style
     *
     * @return  WebAssetItem[]
     *
     * @since  4.0.0
     */
    protected function calculateOrderOfActiveAssets($type): array
    {
        // See https://en.wikipedia.org/wiki/Topological_sorting#Kahn.27s_algorithm
        $graphOrder    = [];
        $activeAssets  = $this->getAssets($type, false);

        // Get Graph of Outgoing and Incoming connections
        $connectionsGraph = $this->getConnectionsGraph($activeAssets);
        $graphOutgoing    = $connectionsGraph['outgoing'];
        $graphIncoming    = $connectionsGraph['incoming'];

        // Make a copy to be used during weight processing
        $graphIncomingCopy = $graphIncoming;

        // Find items without incoming connections
        $emptyIncoming = array_keys(
            array_filter(
                $graphIncoming,
                function ($el) {
                    return !$el;
                }
            )
        );

        // Reverse, to start from a last enabled and move up to a first enabled, this helps to maintain an original sorting
        $emptyIncoming = array_reverse($emptyIncoming);

        // Loop through, and sort the graph
        while ($emptyIncoming) {
            // Add the node without incoming connection to the result
            $item         = array_shift($emptyIncoming);
            $graphOrder[] = $item;

            // Check of each neighbor of the node
            foreach (array_reverse($graphOutgoing[$item]) as $neighbor) {
                // Remove incoming connection of already visited node
                unset($graphIncoming[$neighbor][$item]);

                // If there no more incoming connections add the node to queue
                if (empty($graphIncoming[$neighbor])) {
                    $emptyIncoming[] = $neighbor;
                }
            }
        }

        // Sync Graph order with FIFO order
        $fifoWeights      = [];
        $graphWeights     = [];
        $requestedWeights = [];

        foreach (array_keys($this->activeAssets[$type]) as $index => $name) {
            $fifoWeights[$name] = $index * 10 + 10;
        }

        foreach (array_reverse($graphOrder) as $index => $name) {
            $graphWeights[$name]     = $index * 10 + 10;
            $requestedWeights[$name] = $activeAssets[$name]->getOption('weight') ?: $fifoWeights[$name];
        }

        // Try to set a requested weight, or make it close as possible to requested, but keep the Graph order
        while ($requestedWeights) {
            $item   = key($requestedWeights);
            $weight = array_shift($requestedWeights);

            // Skip empty items
            if ($weight === null) {
                continue;
            }

            // Check the predecessors (Outgoing vertexes), the weight cannot be lighter than the predecessor have
            $topBorder = $weight - 1;

            if (!empty($graphOutgoing[$item])) {
                $prevWeights = [];

                foreach ($graphOutgoing[$item] as $pItem) {
                    $prevWeights[] = $graphWeights[$pItem];
                }

                $topBorder = max($prevWeights);
            }

            // Calculate a new weight
            $newWeight = $weight > $topBorder ? $weight : $topBorder + 1;

            // If a new weight heavier than existing, then we need to update all incoming connections (children)
            if ($newWeight > $graphWeights[$item] && !empty($graphIncomingCopy[$item])) {
                // Sort Graph of incoming by actual position
                foreach ($graphIncomingCopy[$item] as $incomingItem) {
                    // Set a weight heavier than current, then this node to be processed in next iteration
                    if (empty($requestedWeights[$incomingItem])) {
                        $requestedWeights[$incomingItem] = $graphWeights[$incomingItem] + $newWeight;
                    }
                }
            }

            // Set a new weight
            $graphWeights[$item] = $newWeight;
        }

        asort($graphWeights);

        // Get Assets in calculated order
        $resultAssets  = [];

        foreach (array_keys($graphWeights) as $name) {
            $resultAssets[$name] = $activeAssets[$name];
        }

        return $resultAssets;
    }

    /**
     * Build Graph of Outgoing and Incoming connections for given assets.
     *
     * @param   WebAssetItem[]  $assets  Asset instances
     *
     * @return  array
     *
     * @since   4.0.0
     */
    protected function getConnectionsGraph(array $assets): array
    {
        $graphOutgoing = [];
        $graphIncoming = [];

        foreach ($assets as $asset) {
            $name = $asset->getName();

            // Initialise an array for outgoing nodes of the asset
            $graphOutgoing[$name] = [];

            // Initialise an array for incoming nodes of the asset
            if (!\array_key_exists($name, $graphIncoming)) {
                $graphIncoming[$name] = [];
            }

            // Collect an outgoing/incoming nodes
            foreach ($asset->getDependencies() as $depName) {
                $graphOutgoing[$name][$depName] = $depName;
                $graphIncoming[$depName][$name] = $name;
            }
        }

        return [
            'outgoing' => $graphOutgoing,
            'incoming' => $graphIncoming,
        ];
    }

    /**
     * Return dependencies for Asset as array of WebAssetItem objects
     *
     * @param   string         $type           The asset type, script or style
     * @param   WebAssetItem   $asset          Asset instance
     * @param   boolean        $recursively    Whether to search for dependency recursively
     * @param   ?string        $recursionType  The type of initial item to prevent loop
     * @param   ?WebAssetItem  $recursionRoot  Initial item to prevent loop
     *
     * @return  array
     *
     * @throws  UnsatisfiedDependencyException When Dependency cannot be found
     *
     * @since   4.0.0
     */
    protected function getDependenciesForAsset(
        string $type,
        WebAssetItem $asset,
        $recursively = false,
        ?string $recursionType = null,
        ?WebAssetItem $recursionRoot = null
    ): array {
        $assets        = [];
        $recursionRoot ??= $asset;
        $recursionType ??= $type;

        foreach ($asset->getDependencies() as $depName) {
            $depType = $type;

            // Skip already loaded in recursion
            if ($recursionRoot->getName() === $depName && $recursionType === $depType) {
                continue;
            }

            if (!$this->registry->exists($depType, $depName)) {
                throw new UnsatisfiedDependencyException(
                    \sprintf('Unsatisfied dependency "%s" for an asset "%s" of type "%s"', $depName, $asset->getName(), $depType)
                );
            }

            $dep = $this->registry->get($depType, $depName);

            $assets[$depType][$depName] = $dep;

            if (!$recursively) {
                continue;
            }

            $parentDeps = $this->getDependenciesForAsset($depType, $dep, true, $recursionType, $recursionRoot);
            $assets     = array_replace_recursive($assets, $parentDeps);
        }

        return $assets;
    }

    /**
     * A helper method to call onAttachCallback for script assets that implements WebAssetAttachBehaviorInterface
     *
     * @param   array     $assets     Array of assets
     * @param   Document  $document   Document instance to attach
     * @param   array     $cache      Array of object ids which callback was already called
     *
     * @return  array  Array of object ids for which callback was called
     *
     * @since 5.1.0
     */
    public static function callOnAttachCallback(array $assets, Document $document, array $cache = []): array
    {
        foreach ($assets as $asset) {
            if (!$asset instanceof WebAssetAttachBehaviorInterface) {
                continue;
            }

            $oid = spl_object_id($asset);

            if (empty($cache[$oid])) {
                $asset->onAttachCallback($document);
                $cache[$oid] = true;
            }
        }

        return $cache;
    }
}

Filemanager

Name Type Size Permission Actions
AssetItem Folder 0775
Exception Folder 0775
WebAssetAttachBehaviorInterface.php File 776 B 0664
WebAssetItem.php File 8.11 KB 0664
WebAssetItemInterface.php File 2.72 KB 0664
WebAssetManager.php File 34.09 KB 0664
WebAssetManagerInterface.php File 2.44 KB 0664
WebAssetRegistry.php File 12.73 KB 0664
WebAssetRegistryInterface.php File 1.81 KB 0664
Filemanager