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