[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * Joomla! Content Management System 5 * 6 * @copyright (C) 2019 Open Source Matters, Inc. <https://www.joomla.org> 7 * @license GNU General Public License version 2 or later; see LICENSE.txt 8 */ 9 10 namespace Joomla\CMS\WebAsset; 11 12 use Joomla\CMS\Event\WebAsset\WebAssetRegistryAssetChanged; 13 use Joomla\CMS\WebAsset\Exception\InvalidActionException; 14 use Joomla\CMS\WebAsset\Exception\UnknownAssetException; 15 use Joomla\CMS\WebAsset\Exception\UnsatisfiedDependencyException; 16 17 // phpcs:disable PSR1.Files.SideEffects 18 \defined('JPATH_PLATFORM') or die; 19 // phpcs:enable PSR1.Files.SideEffects 20 21 /** 22 * Web Asset Manager class 23 * 24 * @method WebAssetManager registerStyle(WebAssetItem|string $asset, string $uri = '', $options = [], $attributes = [], $dependencies = []) 25 * @method WebAssetManager registerAndUseStyle(WebAssetItem|string $asset, string $uri = '', $options = [], $attributes = [], $dependencies = []) 26 * @method WebAssetManager useStyle($name) 27 * @method WebAssetManager disableStyle($name) 28 * @method WebAssetManager addInlineStyle(WebAssetItem|string $content, $options = [], $attributes = [], $dependencies = []) 29 * 30 * @method WebAssetManager registerScript(WebAssetItem|string $asset, string $uri = '', $options = [], $attributes = [], $dependencies = []) 31 * @method WebAssetManager registerAndUseScript(WebAssetItem|string $asset, string $uri = '', $options = [], $attributes = [], $dependencies = []) 32 * @method WebAssetManager useScript($name) 33 * @method WebAssetManager disableScript($name) 34 * @method WebAssetManager addInlineScript(WebAssetItem|string $content, $options = [], $attributes = [], $dependencies = []) 35 * 36 * @method WebAssetManager registerPreset(WebAssetItem|string $asset, string $uri = '', $options = [], $attributes = [], $dependencies = []) 37 * @method WebAssetManager registerAndUsePreset(WebAssetItem|string $asset, string $uri = '', $options = [], $attributes = [], $dependencies = []) 38 * @method WebAssetManager usePreset($name) 39 * @method WebAssetManager disablePreset($name) 40 * 41 * @since 4.0.0 42 */ 43 class WebAssetManager implements WebAssetManagerInterface 44 { 45 /** 46 * Mark inactive asset 47 * 48 * @var integer 49 * 50 * @since 4.0.0 51 */ 52 public const ASSET_STATE_INACTIVE = 0; 53 54 /** 55 * Mark active asset. Just enabled, but WITHOUT dependency resolved 56 * 57 * @var integer 58 * 59 * @since 4.0.0 60 */ 61 public const ASSET_STATE_ACTIVE = 1; 62 63 /** 64 * Mark active asset that is enabled as dependency to another asset 65 * 66 * @var integer 67 * 68 * @since 4.0.0 69 */ 70 public const ASSET_STATE_DEPENDENCY = 2; 71 72 /** 73 * The WebAsset Registry instance 74 * 75 * @var WebAssetRegistry 76 * 77 * @since 4.0.0 78 */ 79 protected $registry; 80 81 /** 82 * A list of active assets (including their dependencies). 83 * Array of Name => State 84 * 85 * @var array 86 * 87 * @since 4.0.0 88 */ 89 protected $activeAssets = []; 90 91 /** 92 * Internal marker to check the manager state, 93 * to prevent use of the manager after an assets are rendered 94 * 95 * @var boolean 96 * 97 * @since 4.0.0 98 */ 99 protected $locked = false; 100 101 /** 102 * Internal marker to keep track when need to recheck dependencies 103 * 104 * @var boolean 105 * 106 * @since 4.0.0 107 */ 108 protected $dependenciesIsActual = false; 109 110 /** 111 * Class constructor 112 * 113 * @param WebAssetRegistry $registry The WebAsset Registry instance 114 * 115 * @since 4.0.0 116 */ 117 public function __construct(WebAssetRegistry $registry) 118 { 119 $this->registry = $registry; 120 121 // Listen to changes in the registry 122 $this->registry->getDispatcher()->addListener( 123 'onWebAssetRegistryChangedAssetOverride', 124 function (WebAssetRegistryAssetChanged $event) { 125 // If the changed asset are used 126 if (!empty($this->activeAssets[$event->getAssetType()][$event->getAsset()->getName()])) { 127 $this->dependenciesIsActual = false; 128 } 129 } 130 ); 131 132 $this->registry->getDispatcher()->addListener( 133 'onWebAssetRegistryChangedAssetRemove', 134 function (WebAssetRegistryAssetChanged $event) { 135 // If the changed asset are used 136 if (!empty($this->activeAssets[$event->getAssetType()][$event->getAsset()->getName()])) { 137 $this->dependenciesIsActual = false; 138 } 139 } 140 ); 141 } 142 143 /** 144 * Get associated registry instance 145 * 146 * @return WebAssetRegistry 147 * 148 * @since 4.0.0 149 */ 150 public function getRegistry(): WebAssetRegistry 151 { 152 return $this->registry; 153 } 154 155 /** 156 * Clears all collected items. 157 * 158 * @return self 159 * 160 * @since 4.1.1 161 */ 162 public function reset(): WebAssetManagerInterface 163 { 164 if ($this->locked) { 165 throw new InvalidActionException('WebAssetManager is locked'); 166 } 167 168 $this->activeAssets = []; 169 $this->dependenciesIsActual = false; 170 171 return $this; 172 } 173 174 /** 175 * Adds support for magic method calls 176 * 177 * @param string $method A method name 178 * @param array $arguments Arguments for a method 179 * 180 * @return mixed 181 * 182 * @throws \BadMethodCallException 183 * 184 * @since 4.0.0 185 */ 186 public function __call($method, $arguments) 187 { 188 $method = strtolower($method); 189 190 if (0 === strpos($method, 'use')) { 191 $type = substr($method, 3); 192 193 if (empty($arguments[0])) { 194 throw new \BadMethodCallException('An asset name is required'); 195 } 196 197 return $this->useAsset($type, $arguments[0]); 198 } 199 200 if (0 === strpos($method, 'addinline')) { 201 $type = substr($method, 9); 202 203 if (empty($arguments[0])) { 204 throw new \BadMethodCallException('Content is required'); 205 } 206 207 return $this->addInline($type, ...$arguments); 208 } 209 210 if (0 === strpos($method, 'disable')) { 211 $type = substr($method, 7); 212 213 if (empty($arguments[0])) { 214 throw new \BadMethodCallException('An asset name is required'); 215 } 216 217 return $this->disableAsset($type, $arguments[0]); 218 } 219 220 if (0 === strpos($method, 'register')) { 221 // Check for registerAndUse<Type> 222 $andUse = substr($method, 8, 6) === 'anduse'; 223 224 // Extract the type 225 $type = $andUse ? substr($method, 14) : substr($method, 8); 226 227 if (empty($arguments[0])) { 228 throw new \BadMethodCallException('An asset instance or an asset name is required'); 229 } 230 231 if ($andUse) { 232 $name = $arguments[0] instanceof WebAssetItemInterface ? $arguments[0]->getName() : $arguments[0]; 233 234 return $this->registerAsset($type, ...$arguments)->useAsset($type, $name); 235 } else { 236 return $this->registerAsset($type, ...$arguments); 237 } 238 } 239 240 throw new \BadMethodCallException(sprintf('Undefined method %s in class %s', $method, get_class($this))); 241 } 242 243 /** 244 * Enable an asset item to be attached to a Document 245 * 246 * @param string $type The asset type, script or style 247 * @param string $name The asset name 248 * 249 * @return self 250 * 251 * @throws UnknownAssetException When Asset cannot be found 252 * @throws InvalidActionException When the Manager already attached to a Document 253 * 254 * @since 4.0.0 255 */ 256 public function useAsset(string $type, string $name): WebAssetManagerInterface 257 { 258 if ($this->locked) { 259 throw new InvalidActionException('WebAssetManager is locked, you came late'); 260 } 261 262 // Check whether asset exists 263 $asset = $this->registry->get($type, $name); 264 265 if (empty($this->activeAssets[$type])) { 266 $this->activeAssets[$type] = []; 267 } 268 269 // For "preset" need to check the dependencies first 270 if ($type === 'preset') { 271 $this->usePresetItems($name); 272 } 273 274 // Asset already enabled 275 if (!empty($this->activeAssets[$type][$name])) { 276 // Set state to active, in case it was ASSET_STATE_DEPENDENCY 277 $this->activeAssets[$type][$name] = static::ASSET_STATE_ACTIVE; 278 279 return $this; 280 } 281 282 $this->activeAssets[$type][$name] = static::ASSET_STATE_ACTIVE; 283 284 // To re-check dependencies 285 if ($asset->getDependencies()) { 286 $this->dependenciesIsActual = false; 287 } 288 289 return $this; 290 } 291 292 /** 293 * Deactivate an asset item, so it will not be attached to a Document 294 * 295 * @param string $type The asset type, script or style 296 * @param string $name The asset name 297 * 298 * @return self 299 * 300 * @throws UnknownAssetException When Asset cannot be found 301 * @throws InvalidActionException When the Manager already attached to a Document 302 * 303 * @since 4.0.0 304 */ 305 public function disableAsset(string $type, string $name): WebAssetManagerInterface 306 { 307 if ($this->locked) { 308 throw new InvalidActionException('WebAssetManager is locked, you came late'); 309 } 310 311 // Check whether asset exists 312 $this->registry->get($type, $name); 313 314 unset($this->activeAssets[$type][$name]); 315 316 // To re-check dependencies 317 $this->dependenciesIsActual = false; 318 319 // For Preset case 320 if ($type === 'preset') { 321 $this->disablePresetItems($name); 322 } 323 324 return $this; 325 } 326 327 /** 328 * Enable list of assets provided by Preset item. 329 * 330 * "Preset" a special kind of asset that hold a list of assets that has to be enabled, 331 * same as direct call of useAsset() to each of item in list. 332 * Can hold mixed types of assets (script, style, another preset, etc), the type provided after # symbol, after 333 * the asset name, example: foo#style, bar#script. 334 * 335 * The method call useAsset() internally for each of its dependency, this is important for keeping FIFO order 336 * of enabled items. 337 * The Preset not a strict asset, and each of its dependency can be safely disabled by use of disableAsset() later. 338 * 339 * @param string $name The asset name 340 * 341 * @return self 342 * 343 * @throws UnsatisfiedDependencyException When Asset dependency cannot be found 344 * 345 * @since 4.0.0 346 */ 347 protected function usePresetItems($name): WebAssetManagerInterface 348 { 349 // Get the asset object 350 $asset = $this->registry->get('preset', $name); 351 352 // Call useAsset() to each of its dependency 353 foreach ($asset->getDependencies() as $dependency) { 354 $depType = ''; 355 $depName = $dependency; 356 $pos = strrpos($dependency, '#'); 357 358 // Check for cross-dependency "dependency-name#type" case 359 if ($pos) { 360 $depType = substr($dependency, $pos + 1); 361 $depName = substr($dependency, 0, $pos); 362 } 363 364 $depType = $depType ?: 'preset'; 365 366 // Make sure dependency exists 367 if (!$this->registry->exists($depType, $depName)) { 368 throw new UnsatisfiedDependencyException( 369 sprintf('Unsatisfied dependency "%s" for an asset "%s" of type "%s"', $dependency, $name, 'preset') 370 ); 371 } 372 373 $this->useAsset($depType, $depName); 374 } 375 376 return $this; 377 } 378 379 /** 380 * Deactivate list of assets provided by Preset item. 381 * 382 * @param string $name The asset name 383 * 384 * @return self 385 * 386 * @throws UnsatisfiedDependencyException When Asset dependency cannot be found 387 * 388 * @since 4.0.0 389 */ 390 protected function disablePresetItems($name): WebAssetManagerInterface 391 { 392 // Get the asset object 393 $asset = $this->registry->get('preset', $name); 394 395 // Call disableAsset() to each of its dependency 396 foreach ($asset->getDependencies() as $dependency) { 397 $depType = ''; 398 $depName = $dependency; 399 $pos = strrpos($dependency, '#'); 400 401 // Check for cross-dependency "dependency-name#type" case 402 if ($pos) { 403 $depType = substr($dependency, $pos + 1); 404 $depName = substr($dependency, 0, $pos); 405 } 406 407 $depType = $depType ?: 'preset'; 408 409 // Make sure dependency exists 410 if (!$this->registry->exists($depType, $depName)) { 411 throw new UnsatisfiedDependencyException( 412 sprintf('Unsatisfied dependency "%s" for an asset "%s" of type "%s"', $dependency, $name, 'preset') 413 ); 414 } 415 416 $this->disableAsset($depType, $depName); 417 } 418 419 return $this; 420 } 421 422 /** 423 * Get a state for the Asset 424 * 425 * @param string $type The asset type, script or style 426 * @param string $name The asset name 427 * 428 * @return integer 429 * 430 * @throws UnknownAssetException When Asset cannot be found 431 * 432 * @since 4.0.0 433 */ 434 public function getAssetState(string $type, string $name): int 435 { 436 // Check whether asset exists first 437 $this->registry->get($type, $name); 438 439 // Make sure that all dependencies are active 440 if (!$this->dependenciesIsActual) { 441 $this->enableDependencies(); 442 } 443 444 if (!empty($this->activeAssets[$type][$name])) { 445 return $this->activeAssets[$type][$name]; 446 } 447 448 return static::ASSET_STATE_INACTIVE; 449 } 450 451 /** 452 * Check whether the asset are enabled 453 * 454 * @param string $type The asset type, script or style 455 * @param string $name The asset name 456 * 457 * @return boolean 458 * 459 * @throws UnknownAssetException When Asset cannot be found 460 * 461 * @since 4.0.0 462 */ 463 public function isAssetActive(string $type, string $name): bool 464 { 465 return $this->getAssetState($type, $name) !== static::ASSET_STATE_INACTIVE; 466 } 467 468 /** 469 * Helper method to check whether the asset exists in the registry. 470 * 471 * @param string $type Asset type, script or style 472 * @param string $name Asset name 473 * 474 * @return boolean 475 * 476 * @since 4.0.0 477 */ 478 public function assetExists(string $type, string $name): bool 479 { 480 return $this->registry->exists($type, $name); 481 } 482 483 /** 484 * Register a new asset. 485 * Allow to register WebAssetItem instance in the registry, by call registerAsset($type, $assetInstance) 486 * Or create an asset on fly (from name and Uri) and register in the registry, by call registerAsset($type, $assetName, $uri, $options ....) 487 * 488 * @param string $type The asset type, script or style 489 * @param WebAssetItem|string $asset The asset name or instance to register 490 * @param string $uri The URI for the asset 491 * @param array $options Additional options for the asset 492 * @param array $attributes Attributes for the asset 493 * @param array $dependencies Asset dependencies 494 * 495 * @return self 496 * 497 * @since 4.0.0 498 * 499 * @throws \InvalidArgumentException 500 */ 501 public function registerAsset(string $type, $asset, string $uri = '', array $options = [], array $attributes = [], array $dependencies = []) 502 { 503 if ($asset instanceof WebAssetItemInterface) { 504 $this->registry->add($type, $asset); 505 } elseif (is_string($asset)) { 506 $options['type'] = $type; 507 $assetInstance = $this->registry->createAsset($asset, $uri, $options, $attributes, $dependencies); 508 $this->registry->add($type, $assetInstance); 509 } else { 510 throw new \InvalidArgumentException( 511 sprintf( 512 '%s(): Argument #2 ($asset) must be a string or an instance of %s, %s given.', 513 __METHOD__, 514 WebAssetItemInterface::class, 515 \is_object($asset) ? \get_class($asset) : \gettype($asset) 516 ) 517 ); 518 } 519 520 return $this; 521 } 522 523 /** 524 * Helper method to get the asset from the registry. 525 * 526 * @param string $type Asset type, script or style 527 * @param string $name Asset name 528 * 529 * @return WebAssetItemInterface 530 * 531 * @throws UnknownAssetException When Asset cannot be found 532 * 533 * @since 4.0.0 534 */ 535 public function getAsset(string $type, string $name): WebAssetItemInterface 536 { 537 return $this->registry->get($type, $name); 538 } 539 540 /** 541 * Get all active assets, optionally sort them to follow the dependency Graph 542 * 543 * @param string $type The asset type, script or style 544 * @param bool $sort Whether need to sort the assets to follow the dependency Graph 545 * 546 * @return WebAssetItem[] 547 * 548 * @throws UnknownAssetException When Asset cannot be found 549 * @throws UnsatisfiedDependencyException When Dependency cannot be found 550 * 551 * @since 4.0.0 552 */ 553 public function getAssets(string $type, bool $sort = false): array 554 { 555 // Make sure that all dependencies are active 556 if (!$this->dependenciesIsActual) { 557 $this->enableDependencies(); 558 } 559 560 if (empty($this->activeAssets[$type])) { 561 return []; 562 } 563 564 // Apply Tree sorting for regular asset items, but return FIFO order for "preset" 565 if ($sort && $type !== 'preset') { 566 $assets = $this->calculateOrderOfActiveAssets($type); 567 } else { 568 $assets = []; 569 570 foreach (array_keys($this->activeAssets[$type]) as $name) { 571 $assets[$name] = $this->registry->get($type, $name); 572 } 573 } 574 575 return $assets; 576 } 577 578 /** 579 * Helper method to calculate inline to non inline relation (before/after positions). 580 * Return associated array, which contain dependency (handle) name as key, and list of inline items for each position. 581 * Example: ['handle.name' => ['before' => ['inline1', 'inline2'], 'after' => ['inline3', 'inline4']]] 582 * 583 * Note: If inline asset have a multiple dependencies, then will be used last one from the list for positioning 584 * 585 * @param WebAssetItem[] $assets The assets list 586 * 587 * @return array 588 * 589 * @since 4.0.0 590 */ 591 public function getInlineRelation(array $assets): array 592 { 593 $inlineRelation = []; 594 595 // Find an inline assets and their relations to non inline 596 foreach ($assets as $k => $asset) { 597 if (!$asset->getOption('inline')) { 598 continue; 599 } 600 601 // Add to list of inline assets 602 $inlineAssets[$asset->getName()] = $asset; 603 604 // Check whether position are requested with dependencies 605 $position = $asset->getOption('position'); 606 $position = $position === 'before' || $position === 'after' ? $position : null; 607 $deps = $asset->getDependencies(); 608 609 if ($position && $deps) { 610 // If inline asset have a multiple dependencies, then use last one from the list for positioning 611 $handle = end($deps); 612 $inlineRelation[$handle][$position][$asset->getName()] = $asset; 613 } 614 } 615 616 return $inlineRelation; 617 } 618 619 /** 620 * Helper method to filter an inline assets 621 * 622 * @param WebAssetItem[] $assets Reference to a full list of active assets 623 * 624 * @return WebAssetItem[] Array of inline assets 625 * 626 * @since 4.0.0 627 */ 628 public function filterOutInlineAssets(array &$assets): array 629 { 630 $inlineAssets = []; 631 632 foreach ($assets as $k => $asset) { 633 if (!$asset->getOption('inline')) { 634 continue; 635 } 636 637 // Remove inline assets from assets list, and add to list of inline 638 unset($assets[$k]); 639 640 $inlineAssets[$asset->getName()] = $asset; 641 } 642 643 return $inlineAssets; 644 } 645 646 /** 647 * Add a new inline content asset. 648 * Allow to register WebAssetItem instance in the registry, by call addInline($type, $assetInstance) 649 * Or create an asset on fly (from name and Uri) and register in the registry, by call addInline($type, $content, $options ....) 650 * 651 * @param string $type The asset type, script or style 652 * @param WebAssetItem|string $content The content to of inline asset 653 * @param array $options Additional options for the asset 654 * @param array $attributes Attributes for the asset 655 * @param array $dependencies Asset dependencies 656 * 657 * @return self 658 * 659 * @since 4.0.0 660 * 661 * @throws \InvalidArgumentException 662 */ 663 public function addInline(string $type, $content, array $options = [], array $attributes = [], array $dependencies = []): self 664 { 665 if ($content instanceof WebAssetItemInterface) { 666 $assetInstance = $content; 667 } elseif (is_string($content)) { 668 $name = $options['name'] ?? ('inline.' . md5($content)); 669 $assetInstance = $this->registry->createAsset($name, '', $options, $attributes, $dependencies); 670 $assetInstance->setOption('content', $content); 671 } else { 672 throw new \InvalidArgumentException( 673 sprintf( 674 '%s(): Argument #2 ($content) must be a string or an instance of %s, %s given.', 675 __METHOD__, 676 WebAssetItemInterface::class, 677 \is_object($content) ? \get_class($content) : \gettype($content) 678 ) 679 ); 680 } 681 682 // Get the name 683 $asset = $assetInstance->getName(); 684 685 // Set required options 686 $assetInstance->setOption('type', $type); 687 $assetInstance->setOption('inline', true); 688 689 // Add to registry 690 $this->registry->add($type, $assetInstance); 691 692 // And make active 693 $this->useAsset($type, $asset); 694 695 return $this; 696 } 697 698 /** 699 * Lock the manager to prevent further modifications 700 * 701 * @return self 702 * 703 * @since 4.0.0 704 */ 705 public function lock(): self 706 { 707 $this->locked = true; 708 709 return $this; 710 } 711 712 /** 713 * Get the manager state. A collection of registry files and active asset names (per type). 714 * 715 * @return array 716 * 717 * @since 4.0.0 718 */ 719 public function getManagerState(): array 720 { 721 return [ 722 'registryFiles' => $this->getRegistry()->getRegistryFiles(), 723 'activeAssets' => $this->activeAssets, 724 ]; 725 } 726 727 /** 728 * Update Dependencies state for all active Assets or only for given 729 * 730 * @param string $type The asset type, script or style 731 * @param WebAssetItem $asset The asset instance to which need to enable dependencies 732 * 733 * @return self 734 * 735 * @since 4.0.0 736 */ 737 protected function enableDependencies(string $type = null, WebAssetItem $asset = null): self 738 { 739 if ($type === 'preset') { 740 // Preset items already was enabled by usePresetItems() 741 return $this; 742 } 743 744 if ($asset) { 745 // Get all dependencies of given asset recursively 746 $allDependencies = $this->getDependenciesForAsset($type, $asset, true); 747 748 foreach ($allDependencies as $depType => $depItems) { 749 foreach ($depItems as $depItem) { 750 // Set dependency state only when it is inactive, to keep a manually activated Asset in their original state 751 if (empty($this->activeAssets[$depType][$depItem->getName()])) { 752 // Add the dependency at the top of the list of active assets 753 $this->activeAssets[$depType] = [$depItem->getName() => static::ASSET_STATE_DEPENDENCY] + $this->activeAssets[$depType]; 754 } 755 } 756 } 757 } else { 758 // Re-Check for dependencies for all active assets 759 // Firstly, filter out only active assets 760 foreach ($this->activeAssets as $type => $activeAsset) { 761 $this->activeAssets[$type] = array_filter( 762 $activeAsset, 763 function ($state) { 764 return $state === WebAssetManager::ASSET_STATE_ACTIVE; 765 } 766 ); 767 } 768 769 // Secondary, check for dependencies of each active asset 770 // This need to be separated from previous step because we may have "cross type" dependency 771 foreach ($this->activeAssets as $type => $activeAsset) { 772 foreach (array_keys($activeAsset) as $name) { 773 $asset = $this->registry->get($type, $name); 774 $this->enableDependencies($type, $asset); 775 } 776 } 777 778 $this->dependenciesIsActual = true; 779 } 780 781 return $this; 782 } 783 784 /** 785 * Calculate weight of active Assets, by its Dependencies 786 * 787 * @param string $type The asset type, script or style 788 * 789 * @return WebAssetItem[] 790 * 791 * @since 4.0.0 792 */ 793 protected function calculateOrderOfActiveAssets($type): array 794 { 795 // See https://en.wikipedia.org/wiki/Topological_sorting#Kahn.27s_algorithm 796 $graphOrder = []; 797 $activeAssets = $this->getAssets($type, false); 798 799 // Get Graph of Outgoing and Incoming connections 800 $connectionsGraph = $this->getConnectionsGraph($activeAssets); 801 $graphOutgoing = $connectionsGraph['outgoing']; 802 $graphIncoming = $connectionsGraph['incoming']; 803 804 // Make a copy to be used during weight processing 805 $graphIncomingCopy = $graphIncoming; 806 807 // Find items without incoming connections 808 $emptyIncoming = array_keys( 809 array_filter( 810 $graphIncoming, 811 function ($el) { 812 return !$el; 813 } 814 ) 815 ); 816 817 // Reverse, to start from a last enabled and move up to a first enabled, this helps to maintain an original sorting 818 $emptyIncoming = array_reverse($emptyIncoming); 819 820 // Loop through, and sort the graph 821 while ($emptyIncoming) { 822 // Add the node without incoming connection to the result 823 $item = array_shift($emptyIncoming); 824 $graphOrder[] = $item; 825 826 // Check of each neighbor of the node 827 foreach (array_reverse($graphOutgoing[$item]) as $neighbor) { 828 // Remove incoming connection of already visited node 829 unset($graphIncoming[$neighbor][$item]); 830 831 // If there no more incoming connections add the node to queue 832 if (empty($graphIncoming[$neighbor])) { 833 $emptyIncoming[] = $neighbor; 834 } 835 } 836 } 837 838 // Sync Graph order with FIFO order 839 $fifoWeights = []; 840 $graphWeights = []; 841 $requestedWeights = []; 842 843 foreach (array_keys($this->activeAssets[$type]) as $index => $name) { 844 $fifoWeights[$name] = $index * 10 + 10; 845 } 846 847 foreach (array_reverse($graphOrder) as $index => $name) { 848 $graphWeights[$name] = $index * 10 + 10; 849 $requestedWeights[$name] = $activeAssets[$name]->getOption('weight') ?: $fifoWeights[$name]; 850 } 851 852 // Try to set a requested weight, or make it close as possible to requested, but keep the Graph order 853 while ($requestedWeights) { 854 $item = key($requestedWeights); 855 $weight = array_shift($requestedWeights); 856 857 // Skip empty items 858 if ($weight === null) { 859 continue; 860 } 861 862 // Check the predecessors (Outgoing vertexes), the weight cannot be lighter than the predecessor have 863 $topBorder = $weight - 1; 864 865 if (!empty($graphOutgoing[$item])) { 866 $prevWeights = []; 867 868 foreach ($graphOutgoing[$item] as $pItem) { 869 $prevWeights[] = $graphWeights[$pItem]; 870 } 871 872 $topBorder = max($prevWeights); 873 } 874 875 // Calculate a new weight 876 $newWeight = $weight > $topBorder ? $weight : $topBorder + 1; 877 878 // If a new weight heavier than existing, then we need to update all incoming connections (children) 879 if ($newWeight > $graphWeights[$item] && !empty($graphIncomingCopy[$item])) { 880 // Sort Graph of incoming by actual position 881 foreach ($graphIncomingCopy[$item] as $incomingItem) { 882 // Set a weight heavier than current, then this node to be processed in next iteration 883 if (empty($requestedWeights[$incomingItem])) { 884 $requestedWeights[$incomingItem] = $graphWeights[$incomingItem] + $newWeight; 885 } 886 } 887 } 888 889 // Set a new weight 890 $graphWeights[$item] = $newWeight; 891 } 892 893 asort($graphWeights); 894 895 // Get Assets in calculated order 896 $resultAssets = []; 897 898 foreach (array_keys($graphWeights) as $name) { 899 $resultAssets[$name] = $activeAssets[$name]; 900 } 901 902 return $resultAssets; 903 } 904 905 /** 906 * Build Graph of Outgoing and Incoming connections for given assets. 907 * 908 * @param WebAssetItem[] $assets Asset instances 909 * 910 * @return array 911 * 912 * @since 4.0.0 913 */ 914 protected function getConnectionsGraph(array $assets): array 915 { 916 $graphOutgoing = []; 917 $graphIncoming = []; 918 919 foreach ($assets as $asset) { 920 $name = $asset->getName(); 921 922 // Initialise an array for outgoing nodes of the asset 923 $graphOutgoing[$name] = []; 924 925 // Initialise an array for incoming nodes of the asset 926 if (!\array_key_exists($name, $graphIncoming)) { 927 $graphIncoming[$name] = []; 928 } 929 930 // Collect an outgoing/incoming nodes 931 foreach ($asset->getDependencies() as $depName) { 932 $graphOutgoing[$name][$depName] = $depName; 933 $graphIncoming[$depName][$name] = $name; 934 } 935 } 936 937 return [ 938 'outgoing' => $graphOutgoing, 939 'incoming' => $graphIncoming, 940 ]; 941 } 942 943 /** 944 * Return dependencies for Asset as array of WebAssetItem objects 945 * 946 * @param string $type The asset type, script or style 947 * @param WebAssetItem $asset Asset instance 948 * @param boolean $recursively Whether to search for dependency recursively 949 * @param string $recursionType The type of initial item to prevent loop 950 * @param WebAssetItem $recursionRoot Initial item to prevent loop 951 * 952 * @return array 953 * 954 * @throws UnsatisfiedDependencyException When Dependency cannot be found 955 * 956 * @since 4.0.0 957 */ 958 protected function getDependenciesForAsset( 959 string $type, 960 WebAssetItem $asset, 961 $recursively = false, 962 string $recursionType = null, 963 WebAssetItem $recursionRoot = null 964 ): array { 965 $assets = []; 966 $recursionRoot = $recursionRoot ?? $asset; 967 $recursionType = $recursionType ?? $type; 968 969 foreach ($asset->getDependencies() as $depName) { 970 $depType = $type; 971 972 // Skip already loaded in recursion 973 if ($recursionRoot->getName() === $depName && $recursionType === $depType) { 974 continue; 975 } 976 977 if (!$this->registry->exists($depType, $depName)) { 978 throw new UnsatisfiedDependencyException( 979 sprintf('Unsatisfied dependency "%s" for an asset "%s" of type "%s"', $depName, $asset->getName(), $depType) 980 ); 981 } 982 983 $dep = $this->registry->get($depType, $depName); 984 985 $assets[$depType][$depName] = $dep; 986 987 if (!$recursively) { 988 continue; 989 } 990 991 $parentDeps = $this->getDependenciesForAsset($depType, $dep, true, $recursionType, $recursionRoot); 992 $assets = array_replace_recursive($assets, $parentDeps); 993 } 994 995 return $assets; 996 } 997 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Sep 7 05:41:13 2022 | Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer |