![]() Server : Apache System : Linux server2.corals.io 4.18.0-348.2.1.el8_5.x86_64 #1 SMP Mon Nov 15 09:17:08 EST 2021 x86_64 User : corals ( 1002) PHP Version : 7.4.33 Disable Function : exec,passthru,shell_exec,system Directory : /home/corals/old/vendor/magento/composer-root-update-plugin/Updater/ |
<?php /** * Copyright © 2016 Magento. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\ComposerRootUpdatePlugin\Updater; use Closure; use Composer\Package\Link; use Composer\Package\RootPackageInterface; use Magento\ComposerRootUpdatePlugin\Plugin\Commands\RequireCommerceCommand; use Magento\ComposerRootUpdatePlugin\Utils\PackageUtils; use Magento\ComposerRootUpdatePlugin\Plugin\Commands\OverrideRequireCommand; use Magento\ComposerRootUpdatePlugin\Utils\Console; /** * Calculates updated values based on the deltas between original version, target version, and user customizations */ class DeltaResolver { /** * Types of action to take on individual values when a delta is found; returned by findResolution() */ public const ADD_VAL = 'add_value'; public const REMOVE_VAL = 'remove_value'; public const CHANGE_VAL = 'change_value'; /** * @var Console $console */ protected $console; /** * @var PackageUtils $pkgUtils */ protected $pkgUtils; /** * @var bool $overrideUserValues */ protected $overrideUserValues; /** * @var array $jsonChanges */ protected $jsonChanges; /** * @var RootPackageRetriever $retriever */ protected $retriever; /** * @var RootPackageInterface $originalRootPkg */ protected $originalRootPkg; /** * @var RootPackageInterface $targetRootPkg */ protected $targetRootPkg; /** * @var RootPackageInterface $userRootPkg */ protected $userRootPkg; /** * @var string $overrideOptLabel */ protected $overrideOptLabel; /** * @var $interactiveOptLabel */ protected $interactiveOptLabel; /** * @param Console $console * @param bool $overrideUserValues * @param RootPackageRetriever $retriever * @param bool $isOverrideCommand */ public function __construct( Console $console, bool $overrideUserValues, RootPackageRetriever $retriever, bool $isOverrideCommand ) { $this->console = $console; $this->pkgUtils = new PackageUtils($console); $this->overrideUserValues = $overrideUserValues; $this->retriever = $retriever; $this->originalRootPkg = $retriever->getOriginalRootPackage($overrideUserValues); $this->targetRootPkg = $retriever->getTargetRootPackage(); $this->userRootPkg = $retriever->getUserRootPackage(); $this->jsonChanges = []; if ($isOverrideCommand) { $this->overrideOptLabel = OverrideRequireCommand::OVERRIDE_OPT; $this->interactiveOptLabel = OverrideRequireCommand::INTERACTIVE_OPT; } else { $this->overrideOptLabel = RequireCommerceCommand::OVERRIDE_OPT; $this->interactiveOptLabel = RequireCommerceCommand::INTERACTIVE_OPT; } } /** * Run conflict resolution between the three root projects and return the json array of changes that need to be made * * @return array */ public function resolveRootDeltas(): array { $orig = $this->originalRootPkg; $target = $this->targetRootPkg; $user = $this->userRootPkg; $this->resolveLinkSection( 'require', $orig->getRequires(), $target->getRequires(), $user->getRequires(), false ); $this->resolveLinkSection( 'require-dev', $orig->getDevRequires(), $target->getDevRequires(), $user->getDevRequires(), true ); $this->resolveLinkSection( 'conflict', $orig->getConflicts(), $target->getConflicts(), $user->getConflicts(), false ); $this->resolveLinkSection( 'provide', $orig->getProvides(), $target->getProvides(), $user->getProvides(), false ); $this->resolveLinkSection( 'replace', $orig->getReplaces(), $target->getReplaces(), $user->getReplaces(), false ); $this->resolveArraySection('autoload', $orig->getAutoload(), $target->getAutoload(), $user->getAutoload()); $this->resolveArraySection( 'autoload-dev', $orig->getDevAutoload(), $target->getDevAutoload(), $user->getDevAutoload() ); $this->resolveArraySection('extra', $orig->getExtra(), $target->getExtra(), $user->getExtra()); $this->resolveArraySection('suggest', $orig->getSuggests(), $target->getSuggests(), $user->getSuggests()); return $this->jsonChanges; } /** * Find value deltas from original->target version and resolve any conflicts with overlapping user changes * * @param string $field * @param array|mixed|null $originalVal * @param array|mixed|null $targetVal * @param array|mixed|null $userVal * @param string|null $prettyOriginalVal * @param string|null $prettyTargetVal * @param string|null $prettyUserVal * @return string|null ADD_VAL|REMOVE_VAL|CHANGE_VAL to adjust the existing composer.json file, null for no change */ public function findResolution( string $field, $originalVal, $targetVal, $userVal, ?string $prettyOriginalVal = null, ?string $prettyTargetVal = null, ?string $prettyUserVal = null ): ?string { $prettyOriginalVal = $this->prettify($originalVal, $prettyOriginalVal); $prettyTargetVal = $this->prettify($targetVal, $prettyTargetVal); $prettyUserVal = $this->prettify($userVal, $prettyUserVal); $targetLabel = $this->retriever->getTargetLabel(); $origLabel = $this->retriever->getOriginalLabel(); $conflictDesc = null; if ($originalVal == $targetVal || $userVal == $targetVal) { $action = null; } elseif ($originalVal === null) { if ($userVal === null) { $action = self::ADD_VAL; } else { $action = self::CHANGE_VAL; $conflictDesc = "add $field=$prettyTargetVal but it is instead $prettyUserVal"; } } elseif ($targetVal === null) { $action = self::REMOVE_VAL; if ($userVal !== $originalVal) { $conflictDesc = "remove the $field=$prettyOriginalVal entry in $origLabel but it is instead " . $prettyUserVal; } } else { $action = self::CHANGE_VAL; if ($userVal !== $originalVal) { $conflictDesc = "update $field to $prettyTargetVal from $prettyOriginalVal in $origLabel"; if ($userVal === null) { $action = self::ADD_VAL; $conflictDesc = "$conflictDesc but the field has been removed"; } else { $conflictDesc = "$conflictDesc but it is instead $prettyUserVal"; } } } return $this->solveIfConflict($action, $conflictDesc, $targetLabel); } /** * Helper function to make a value human-readable * * @param $val * @param string|null $prettyVal * @return string|null */ protected function prettify($val, ?string $prettyVal = null): ?string { if ($prettyVal === null) { $prettyVal = json_encode($val, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); $prettyVal = trim($prettyVal, "'\""); } return $prettyVal; } /** * Check if a conflict was found and if so adjust the action according to override rules * * @param string|null $action * @param string|null $conflictDesc * @param string $targetLabel * @return string|null */ protected function solveIfConflict(?string $action, ?string $conflictDesc, string $targetLabel): ?string { if ($conflictDesc !== null) { $conflictDesc = "$targetLabel is trying to $conflictDesc in this installation"; $shouldOverride = $this->overrideUserValues; $overrideOpt = $this->overrideOptLabel; $interactiveOpt = $this->interactiveOptLabel; if ($this->overrideUserValues) { $this->console->log($conflictDesc); $this->console->log("Overriding local changes due to --$overrideOpt."); } else { $shouldOverride = $this->console->ask("$conflictDesc.\nWould you like to override the local changes?"); } if (!$shouldOverride) { $this->console->comment("$conflictDesc and will not be changed. Re-run using " . "--$overrideOpt or --$interactiveOpt to override with suggested values."); $action = null; } } return $action; } /** * Process changes to corresponding sets of package version links * * @param string $section * @param Link[] $originalLinks * @param Link[] $targetLinks * @param Link[] $userLinks * @param bool $verifyOrder * @return array */ public function resolveLinkSection( string $section, array $originalLinks, array $targetLinks, array $userLinks, bool $verifyOrder ): array { $toAdd = []; $toRemove = []; $toChange = []; $pkgsToCompare = array_unique(array_merge(array_keys($originalLinks), array_keys($targetLinks))); foreach ($pkgsToCompare as $pkg) { if ($section === 'require' && $this->pkgUtils->getMetapackageEdition($pkg)) { continue; } $this->resolveLink($section, $pkg, $originalLinks, $targetLinks, $userLinks, $toAdd, $toRemove, $toChange); } $changed = false; if ($toAdd !== []) { $changed = true; $prettyAdds = array_map(function ($package) use ($toAdd) { $newVal = $toAdd[$package]->getConstraint()->getPrettyString(); return "$package=$newVal"; }, array_keys($toAdd)); $this->console->labeledVerbose("Adding $section constraints: " . implode(', ', $prettyAdds)); } if ($toRemove !== []) { $changed = true; $this->console->labeledVerbose("Removing $section entries: " . implode(', ', $toRemove)); } if ($toChange !== []) { $changed = true; $prettyChanges = array_map(function ($package) use ($toChange) { $newVal = $toChange[$package]->getConstraint()->getPrettyString(); return "$package=$newVal"; }, array_keys($toChange)); $this->console->labeledVerbose("Updating $section constraints: " . implode(', ', $prettyChanges)); } $enforcedOrder = []; if ($verifyOrder) { $enforcedOrder = $this->getLinkOrderOverride( $section, array_keys($originalLinks), array_keys($targetLinks), array_keys($userLinks), $changed ); } if ($changed) { $this->applyLinkChanges( $section, $targetLinks, $userLinks, $enforcedOrder, $toAdd, $toRemove, $toChange ); } return $this->jsonChanges; } /** * Helper function to find the resolution for a package constraint in the Link sections * * @param string $section * @param string $pkg * @param Link[] $origLinkMap * @param Link[] $targetLinkMap * @param Link[] $userLinkMap * @param Link[] $toAdd * @param Link[] $toRemove * @param Link[] $toChange * @return void */ protected function resolveLink( string $section, string $pkg, array $origLinkMap, array $targetLinkMap, array $userLinkMap, array &$toAdd, array &$toRemove, array &$toChange ) { $field = "$section:$pkg"; list($originalVal, $prettyOriginalVal) = $this->getConstraintValues($origLinkMap, $pkg); list($targetVal, $prettyTargetVal) = $this->getConstraintValues($targetLinkMap, $pkg); list($userVal, $prettyUserVal) = $this->getConstraintValues($userLinkMap, $pkg); $action = $this->findResolution( $field, $originalVal, $targetVal, $userVal, $prettyOriginalVal, $prettyTargetVal, $prettyUserVal ); if ($action == self::ADD_VAL) { $toAdd[$pkg] = $targetLinkMap[$pkg]; } elseif ($action == self::REMOVE_VAL) { $toRemove[] = $pkg; } elseif ($action == self::CHANGE_VAL) { $toChange[$pkg] = $targetLinkMap[$pkg]; } } /** * Helper function to get the raw and pretty forms of a constraint value for a package name * * @param Link[] $linkMap * @param string $pkg * @return string[] */ protected function getConstraintValues(array $linkMap, string $pkg): array { $constraint = key_exists($pkg, $linkMap) ? $linkMap[$pkg]->getConstraint() : null; $val = null; $prettyVal = null; if ($constraint) { $val = $constraint->__toString(); $prettyVal = $constraint->getPrettyString(); } return [$val, $prettyVal]; } /** * Apply added, removed, and changed links to the stored json changes * * @param string $section * @param Link[] $targetLinks * @param Link[] $userLinks * @param string[] $order * @param Link[] $toAdd * @param Link[] $toRemove * @param Link[] $toChange * @return void */ protected function applyLinkChanges( string $section, array $targetLinks, array $userLinks, array $order, array $toAdd, array $toRemove, array $toChange ) { $replacements = array_values($toAdd); foreach ($userLinks as $pkg => $userLink) { if (in_array($pkg, $toRemove)) { continue; } elseif (key_exists($pkg, $toChange)) { $replacements[] = $toChange[$pkg]; } else { $replacements[] = $userLink; } } usort($replacements, $this->buildLinkOrderComparator( $order, array_keys($targetLinks), array_keys($userLinks) )); $newJson = []; /** @var Link $link */ foreach ($replacements as $link) { $newJson[$link->getTarget()] = $link->getConstraint()->getPrettyString(); } $this->jsonChanges[$section] = $newJson; } /** * Process changes to an array (non-package link) section * * @param string $section * @param array|mixed|null $originalVal * @param array|mixed|null $targetVal * @param array|mixed|null $userVal * @return array */ public function resolveArraySection(string $section, $originalVal, $targetVal, $userVal): array { $changed = false; $value = $this->resolveNestedArray($section, $originalVal, $targetVal, $userVal, $changed); if ($changed) { $this->jsonChanges[$section] = $value; } return $this->jsonChanges; } /** * Process changes to arrays that could be nested * * Associative arrays are resolved recursively and non-associative arrays are treated as unordered sets * * @param string $field * @param array|mixed|null $originalVal * @param array|mixed|null $targetVal * @param array|mixed|null $userVal * @param bool $changed * @return array|mixed|null null/empty array indicates to remove the entry from parent */ public function resolveNestedArray(string $field, $originalVal, $targetVal, $userVal, bool &$changed) { $result = $userVal === null ? [] : $userVal; if (is_array($originalVal) && is_array($targetVal) && is_array($userVal)) { $assocResult = $this->resolveAssociativeArray( $field, $originalVal, $targetVal, $userVal, $changed ); $flatResult = $this->resolveFlatArray( $field, $originalVal, $targetVal, $userVal, $changed ); $result = array_merge($flatResult, $assocResult); } else { // Some or all of the values aren't arrays so they should all be compared as non-array values $action = $this->findResolution($field, $originalVal, $targetVal, $userVal); $prettyTargetVal = json_encode($targetVal, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); if ($action == self::ADD_VAL) { $changed = true; $this->console->labeledVerbose("Adding $field entry: $prettyTargetVal"); $result = $targetVal; } elseif ($action == self::CHANGE_VAL) { $changed = true; $this->console->labeledVerbose("Updating $field entry: $prettyTargetVal"); $result = $targetVal; } elseif ($action == self::REMOVE_VAL) { $changed = true; $this->console->labeledVerbose("Removing $field entry"); $result = null; } } return $result; } /** * Process changes to the non-associative portion of an array * * @param string $field * @param array $originalArray * @param array $targetArray * @param array $userArray * @param bool $changed * @return array */ protected function resolveFlatArray( string $field, array $originalArray, array $targetArray, array $userArray, bool &$changed ): array { $originalFlatPart = array_filter($originalArray, 'is_int', ARRAY_FILTER_USE_KEY); $targetFlatPart = array_filter($targetArray, 'is_int', ARRAY_FILTER_USE_KEY); $result = array_filter($userArray, 'is_int', ARRAY_FILTER_USE_KEY); $toAdd = array_diff(array_diff($targetFlatPart, $originalFlatPart), $result); if ($toAdd !== []) { $changed = true; $this->console->labeledVerbose("Adding $field entries: " . implode(', ', $toAdd)); $result = array_unique(array_merge($result, $toAdd)); } $toRemove = array_intersect(array_diff($originalFlatPart, $targetFlatPart), $result); if ($toRemove !== []) { $changed = true; $this->console->labeledVerbose("Removing $field entries: " . implode(', ', $toRemove)); $result = array_diff($result, $toRemove); } return $result; } /** * Process changes to the associative portion of an array that could be nested * * @param string $field * @param array $originalArray * @param array $targetArray * @param array $userArray * @param bool $changed * @return array */ protected function resolveAssociativeArray( string $field, array $originalArray, array $targetArray, array $userArray, bool &$changed ): array { $originalAssocPart = array_filter($originalArray, 'is_string', ARRAY_FILTER_USE_KEY); $targetAssocPart = array_filter($targetArray, 'is_string', ARRAY_FILTER_USE_KEY); $userAssocPart = array_filter($userArray, 'is_string', ARRAY_FILTER_USE_KEY); $result = $userAssocPart; $commerceKeys = array_unique( array_merge(array_keys($originalAssocPart), array_keys($targetAssocPart)) ); foreach ($commerceKeys as $key) { if (key_exists($key, $originalAssocPart)) { $originalNestedVal = $originalAssocPart[$key]; } else { $originalNestedVal = []; } if (key_exists($key, $targetAssocPart)) { $targetNestedVal = $targetAssocPart[$key]; } else { $targetNestedVal = []; } if (key_exists($key, $userAssocPart)) { $userNestedVal = $userAssocPart[$key]; } else { $userNestedVal = []; } $value = $this->resolveNestedArray( "$field.$key", $originalNestedVal, $targetNestedVal, $userNestedVal, $changed ); if ($value === null || $value === []) { if (key_exists($key, $result)) { $changed = true; unset($result[$key]); } } else { $result[$key] = $value; } } return $result; } /** * Get the order to use for a link section if local and target versions disagree * * @param string $section * @param string[] $originalOrder * @param string[] $targetOrder * @param string[] $userOrder * @param bool $changed * @return string[] */ protected function getLinkOrderOverride( string $section, array $originalOrder, array $targetOrder, array $userOrder, bool &$changed ): array { $overrideOrder = []; $conflictTargetOrder = array_values(array_intersect($targetOrder, $userOrder)); $conflictUserOrder = array_values(array_intersect($userOrder, $targetOrder)); $overrideOptLabel = $this->overrideOptLabel; $interactiveOptLabel = $this->interactiveOptLabel; // Check if the user's link order does not match the target section for links that appear in both if ($conflictTargetOrder != $conflictUserOrder) { $conflictOrigOrder = array_values(array_intersect($originalOrder, $targetOrder)); // Check if the user's order is different than the target order because the order has changed between // the original and target magento/project versions if ($conflictOrigOrder !== $conflictUserOrder) { $targetLabel = $this->retriever->getTargetLabel(); $userOrderDesc = " [\n " . implode(",\n ", $conflictUserOrder) . "\n ]"; $targetOrderDesc = " [\n " . implode(",\n ", $conflictTargetOrder) . "\n ]"; $conflictDesc = "$targetLabel is trying to change the existing order of the $section section.\n" . "Local order:\n$userOrderDesc\n$targetLabel order:\n$targetOrderDesc\n"; $shouldOverride = $this->overrideUserValues; if ($this->overrideUserValues) { $this->console->log($conflictDesc); $this->console->log("Overriding local order due to --$overrideOptLabel."); } else { $shouldOverride = $this->console->ask( "$conflictDesc\nWould you like to override the local order?" ); } if (!$shouldOverride) { $this->console->comment("$conflictDesc but it will not be changed. Re-run using " . "--$overrideOptLabel or --$interactiveOptLabel to override with the suggested order."); $overrideOrder = $conflictUserOrder; } else { $overrideOrder = $conflictTargetOrder; } } else { $overrideOrder = $conflictTargetOrder; } } if ($overrideOrder !== []) { $changed = true; $prettyOrder = " [\n " . implode(",\n ", $overrideOrder) . "\n ]"; $this->console->labeledVerbose("Updating $section order:\n$prettyOrder"); } return $overrideOrder; } /** * Construct a comparison function to use in sorting an array of links by prioritized order lists * * @param string[] $overrideOrder * @param string[] $targetOrder * @param string[] $userOrder * @return Closure */ protected function buildLinkOrderComparator(array $overrideOrder, array $targetOrder, array $userOrder): Closure { $prioritizedOrderings = [$overrideOrder, $targetOrder, $userOrder]; return function ($link1, $link2) use ($prioritizedOrderings) { /** * @var Link $link1 * @var Link $link2 */ $package1 = $link1->getTarget(); $package2 = $link2->getTarget(); // Check each ordering array to see if it contains both links and if so use their positions to sort // If the ordering array does not contain both links, try the next one foreach ($prioritizedOrderings as $sortOrder) { $index1 = array_search($package1, $sortOrder); $index2 = array_search($package2, $sortOrder); if ($index1 !== false && $index2 !== false) { if ($index1 == $index2) { return 0; } else { return $index1 < $index2 ? -1 : 1; } } } // None of the ordering arrays contain both elements, so their relative positions in the sorted array // do not matter return 0; }; } }