diff --git a/app/code/Magento/Paypal/Model/Config/Structure/PaymentSectionModifier.php b/app/code/Magento/Paypal/Model/Config/Structure/PaymentSectionModifier.php new file mode 100644 index 0000000000000000000000000000000000000000..76b24f65b7ca17e2fe3f4abc65454fef2bfbe982 --- /dev/null +++ b/app/code/Magento/Paypal/Model/Config/Structure/PaymentSectionModifier.php @@ -0,0 +1,113 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Paypal\Model\Config\Structure; + +/** + * PayPal change structure of payment methods configuration in admin panel. + */ +class PaymentSectionModifier +{ + /** + * Identifiers of special payment method configuration groups + * + * @var array + */ + private static $specialGroups = [ + 'account', + 'recommended_solutions', + 'other_paypal_payment_solutions', + 'other_payment_methods', + ]; + + /** + * Returns changed section structure. + * + * Payment configuration has predefined special blocks: + * - Account information (id = account), + * - Recommended Solutions (id = recommended_solutions), + * - Other PayPal paymnt solution (id = other_paypal_payment_solutions), + * - Other payment methods (id = other_payment_methods). + * All payment methods configuration should be moved to one of this group. + * To move payment method to specific configuration group specify "displayIn" + * attribute in system.xml file equals to any id of predefined special group. + * If "displayIn" attribute is not specified then payment method moved to "Other payment methods" group + * + * @param array $initialStructure + * @return array + */ + public function modify(array $initialStructure) + { + $changedStructure = array_fill_keys(self::$specialGroups, []); + + foreach ($initialStructure as $childSection => $childData) { + if (in_array($childSection, self::$specialGroups)) { + if (isset($changedStructure[$childSection]['children'])) { + $children = $changedStructure[$childSection]['children']; + if (isset($childData['children'])) { + $children += $childData['children']; + } + $childData['children'] = $children; + unset($children); + } + $changedStructure[$childSection] = $childData; + } else { + $moveInstructions = $this->getMoveInstructions($childSection, $childData); + if (!empty($moveInstructions)) { + foreach ($moveInstructions as $moveInstruction) { + unset($childData['children'][$moveInstruction['section']]); + unset($moveInstruction['data']['displayIn']); + $changedStructure + [$moveInstruction['parent']] + ['children'] + [$moveInstruction['section']] = $moveInstruction['data']; + } + } + if (!isset($moveInstructions[$childSection])) { + $changedStructure['other_payment_methods']['children'][$childSection] = $childData; + } + } + } + + return $changedStructure; + } + + /** + * Recursively collect groups that should be moved to special section + * + * @param string $section + * @param array $data + * @return array + */ + private function getMoveInstructions($section, $data) + { + $moved = []; + + if (array_key_exists('children', $data)) { + foreach ($data['children'] as $childSection => $childData) { + $movedChildren = $this->getMoveInstructions($childSection, $childData); + if (isset($movedChildren[$childSection])) { + unset($data['children'][$childSection]); + } + $moved = array_merge($moved, $movedChildren); + } + } + + if (isset($data['displayIn']) && in_array($data['displayIn'], self::$specialGroups)) { + $moved = array_merge( + [ + $section => [ + 'parent' => $data['displayIn'], + 'section' => $section, + 'data' => $data + ] + ], + $moved + ); + } + + return $moved; + } +} diff --git a/app/code/Magento/Paypal/Model/Config/StructurePlugin.php b/app/code/Magento/Paypal/Model/Config/StructurePlugin.php index b026aa09692930fea7a58c1afc2946b280bad9fa..0d07abfb8047fc1c94bd9aba04cb15a0c6fdabfa 100644 --- a/app/code/Magento/Paypal/Model/Config/StructurePlugin.php +++ b/app/code/Magento/Paypal/Model/Config/StructurePlugin.php @@ -10,6 +10,8 @@ use Magento\Config\Model\Config\Structure; use Magento\Config\Model\Config\Structure\Element\Section; use Magento\Config\Model\Config\Structure\ElementInterface; use Magento\Paypal\Helper\Backend as BackendHelper; +use Magento\Framework\App\ObjectManager; +use Magento\Paypal\Model\Config\Structure\PaymentSectionModifier; /** * Plugin for \Magento\Config\Model\Config\Structure @@ -31,6 +33,11 @@ class StructurePlugin */ private $scopeDefiner; + /** + * @var PaymentSectionModifier + */ + private $paymentSectionModifier; + /** * @var string[] */ @@ -50,12 +57,18 @@ class StructurePlugin /** * @param ScopeDefiner $scopeDefiner - * @param BackendHelper $helper + * @param BackendHelper $backendHelper + * @param PaymentSectionModifier|null $paymentSectionModifier */ - public function __construct(ScopeDefiner $scopeDefiner, BackendHelper $helper) - { + public function __construct( + ScopeDefiner $scopeDefiner, + BackendHelper $backendHelper, + PaymentSectionModifier $paymentSectionModifier = null + ) { $this->scopeDefiner = $scopeDefiner; - $this->backendHelper = $helper; + $this->backendHelper = $backendHelper; + $this->paymentSectionModifier = $paymentSectionModifier + ?: ObjectManager::getInstance()->get(PaymentSectionModifier::class); } /** @@ -118,62 +131,17 @@ class StructurePlugin } /** - * Change payment config structure - * - * Groups which have `displayIn` element, transfer to appropriate group. - * Groups without `displayIn` transfer to other payment methods group. + * Changes payment config structure. * * @param Section $result * @return void */ private function restructurePayments(Section $result) { - $sectionMap = [ - 'account' => [], - 'recommended_solutions' => [], - 'other_paypal_payment_solutions' => [], - 'other_payment_methods' => [] - ]; - - $configuration = $result->getData(); - - foreach ($configuration['children'] as $section => $data) { - if (array_key_exists($section, $sectionMap)) { - $sectionMap[$section] = $data; - } elseif ($displayIn = $this->getDisplayInSection($section, $data)) { - $sectionMap[$displayIn['parent']]['children'][$displayIn['section']] = $displayIn['data']; - } else { - $sectionMap['other_payment_methods']['children'][$section] = $data; - } - } - - $configuration['children'] = $sectionMap; - $result->setData($configuration, $this->scopeDefiner->getScope()); - } - - /** - * Recursive search of `displayIn` element in node children - * - * @param string $section - * @param array $data - * @return array|null - */ - private function getDisplayInSection($section, $data) - { - if (is_array($data) && array_key_exists('displayIn', $data)) { - return [ - 'parent' => $data['displayIn'], - 'section' => $section, - 'data' => $data - ]; - } - - if (array_key_exists('children', $data)) { - foreach ($data['children'] as $childSection => $childData) { - return $this->getDisplayInSection($childSection, $childData); - } - } - - return null; + $sectionData = $result->getData(); + $sectionInitialStructure = isset($sectionData['children']) ? $sectionData['children'] : []; + $sectionChangedStructure = $this->paymentSectionModifier->modify($sectionInitialStructure); + $sectionData['children'] = $sectionChangedStructure; + $result->setData($sectionData, $this->scopeDefiner->getScope()); } } diff --git a/app/code/Magento/Paypal/Test/Unit/Model/Config/Structure/PaymentSectionModifierTest.php b/app/code/Magento/Paypal/Test/Unit/Model/Config/Structure/PaymentSectionModifierTest.php new file mode 100644 index 0000000000000000000000000000000000000000..f2b5caf909327461c1ff9821b33a5ae23e538556 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Unit/Model/Config/Structure/PaymentSectionModifierTest.php @@ -0,0 +1,182 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Paypal\Test\Unit\Model\Config\Structure; + +use Magento\Paypal\Model\Config\Structure\PaymentSectionModifier; + +class PaymentSectionModifierTest extends \PHPUnit_Framework_TestCase +{ + private static $specialGroups = [ + 'account', + 'recommended_solutions', + 'other_paypal_payment_solutions', + 'other_payment_methods', + ]; + + /** + * @param string $case + * @param array $structure + * @dataProvider caseProvider + */ + public function testSpecialGroupsPresent($case, $structure) + { + $modifier = new PaymentSectionModifier(); + $modifiedStructure = $modifier->modify($structure); + $presentSpecialGroups = array_intersect( + self::$specialGroups, + array_keys($modifiedStructure) + ); + + $this->assertEquals( + self::$specialGroups, + $presentSpecialGroups, + sprintf('All special groups must be present in %s case', $case) + ); + } + + /** + * @param string $case + * @param array $structure + * @dataProvider caseProvider + */ + public function testOnlySpecialGroupsPresent($case, $structure) + { + $modifier = new PaymentSectionModifier(); + $modifiedStructure = $modifier->modify($structure); + $presentNotSpecialGroups = array_diff( + array_keys($modifiedStructure), + self::$specialGroups + ); + + $this->assertEquals( + [], + $presentNotSpecialGroups, + sprintf('Only special groups should be present at top level in "%s" case', $case) + ); + } + + /** + * @param string $case + * @param array $structure + * @dataProvider caseProvider + */ + public function testGroupsNotRemovedAfterModification($case, $structure) + { + $modifier = new PaymentSectionModifier(); + $modifiedStructure = $modifier->modify($structure); + + $removedGroups = array_diff( + $this->fetchAllAvailableGroups($structure), + $this->fetchAllAvailableGroups($modifiedStructure) + ); + + $this->assertEquals( + [], + $removedGroups, + sprintf('Groups should not be removed after modification in "%s" case', $case) + ); + } + + public function testMovedToTargetSpecialGroup() + { + $structure = [ + 'some_payment_method1' => [ + 'id' => 'some_payment_method1', + 'displayIn' => 'recommended_solutions', + ], + 'some_group' => [ + 'id' => 'some_group', + 'children' => [ + 'some_payment_method2' => [ + 'id' => 'some_payment_method2', + 'displayIn' => 'recommended_solutions' + ], + 'some_payment_method3' => [ + 'id' => 'some_payment_method3', + 'displayIn' => 'other_payment_methods' + ], + 'some_payment_method4' => [ + 'id' => 'some_payment_method4', + 'displayIn' => 'recommended_solutions' + ], + 'some_payment_method5' => [ + 'id' => 'some_payment_method5', + ], + ] + ], + ]; + + $modifier = new PaymentSectionModifier(); + $modifiedStructure = $modifier->modify($structure); + + $this->assertEquals( + [ + 'account' => [], + 'recommended_solutions' => [ + 'children' => [ + 'some_payment_method1' => [ + 'id' => 'some_payment_method1', + ], + 'some_payment_method2' => [ + 'id' => 'some_payment_method2', + ], + 'some_payment_method4' => [ + 'id' => 'some_payment_method4', + ], + ], + ], + 'other_paypal_payment_solutions' => [], + 'other_payment_methods' => [ + 'children' => [ + 'some_payment_method3' => [ + 'id' => 'some_payment_method3', + ], + 'some_group' => [ + 'id' => 'some_group', + 'children' => [ + 'some_payment_method5' => [ + 'id' => 'some_payment_method5', + ], + ], + ], + ], + ], + ], + $modifiedStructure, + 'Some group is not moved correctly' + ); + } + + /** + * This helper method walks recursively through configuration structure and + * collect available configuration groups + * + * @param array $structure + * @return array Sorted list of group identifiers + */ + private function fetchAllAvailableGroups($structure) + { + $availableGroups = []; + foreach ($structure as $group => $data) { + $availableGroups[] = $group; + if (isset($data['children'])) { + $availableGroups = array_merge( + $availableGroups, + $this->fetchAllAvailableGroups($data['children']) + ); + } + } + $availableGroups = array_values(array_unique($availableGroups)); + sort($availableGroups); + return $availableGroups; + } + + public function caseProvider() + { + return include __DIR__ . '/_files/payment_section_structure_variations.php'; + } +} diff --git a/app/code/Magento/Paypal/Test/Unit/Model/Config/Structure/_files/payment_section_structure_variations.php b/app/code/Magento/Paypal/Test/Unit/Model/Config/Structure/_files/payment_section_structure_variations.php new file mode 100644 index 0000000000000000000000000000000000000000..b9a91f3f6104aa4b22a75177a525c2a7fe696912 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Unit/Model/Config/Structure/_files/payment_section_structure_variations.php @@ -0,0 +1,129 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +return [ + [ + 'empty structure', + [] + ], + [ + 'structure with special groups at the begin of the list', + [ + 'account' => [ + 'id' => 'account', + ], + 'recommended_solutions' => [ + 'id' => 'recommended_solutions', + ], + 'other_paypal_payment_solutions' => [ + 'id' => 'other_paypal_payment_solutions', + ], + 'other_payment_methods' => [ + 'id' => 'other_payment_methods', + ], + 'some_payment_method' => [ + 'id' => 'some_payment_method', + ], + ] + ], + [ + 'structure with special groups at the end of the list', + [ + 'some_payment_method' => [ + 'id' => 'some_payment_method', + ], + 'account' => [ + 'id' => 'account', + ], + 'recommended_solutions' => [ + 'id' => 'recommended_solutions', + ], + 'other_paypal_payment_solutions' => [ + 'id' => 'other_paypal_payment_solutions', + ], + 'other_payment_methods' => [ + 'id' => 'other_payment_methods', + ], + ] + ], + [ + 'structure with special groups in the middle of the list', + [ + 'some_payment_methodq' => [ + 'id' => 'some_payment_methodq', + ], + 'account' => [ + 'id' => 'account', + ], + 'recommended_solutions' => [ + 'id' => 'recommended_solutions', + ], + 'other_paypal_payment_solutions' => [ + 'id' => 'other_paypal_payment_solutions', + ], + 'other_payment_methods' => [ + 'id' => 'other_payment_methods', + ], + 'some_payment_method2' => [ + 'id' => 'some_payment_method2', + ], + ] + ], + [ + 'structure with all assigned groups', + [ + 'some_payment_method1' => [ + 'id' => 'some_payment_method1', + 'displayIn' => 'other_paypal_payment_solutions', + ], + 'some_payment_method2' => [ + 'id' => 'some_payment_method2', + 'displayIn' => 'recommended_solutions', + ], + ] + ], + [ + 'structure with not assigned groups', + [ + 'some_payment_method1' => [ + 'id' => 'some_payment_method1', + 'displayIn' => 'other_paypal_payment_solutions', + ], + 'some_payment_method2' => [ + 'id' => 'some_payment_method2', + ], + ] + ], + [ + 'special groups has predefined children', + [ + 'recommended_solutions' => [ + 'id' => 'recommended_solutions', + 'children' => [ + 'some_payment_method1' => [ + 'id' => 'some_payment_method1', + ], + ] + ], + 'some_payment_method2' => [ + 'id' => 'some_payment_method2', + 'displayIn' => 'recommended_solutions', + ], + ] + ], + [ + 'structure with displayIn that do not reference to special groups', + [ + 'some_payment_method1' => [ + 'id' => 'some_payment_method1', + ], + 'some_payment_method2' => [ + 'id' => 'some_payment_method2', + 'displayIn' => 'some_payment_method1', + ], + ] + ], +]; diff --git a/app/code/Magento/Paypal/Test/Unit/Model/Config/StructurePluginTest.php b/app/code/Magento/Paypal/Test/Unit/Model/Config/StructurePluginTest.php index 26dd4af0220aaeebfb10b397a9fa7c494fa36936..2e4d146ac6159b5bf995dca335ea3abe4a06549e 100644 --- a/app/code/Magento/Paypal/Test/Unit/Model/Config/StructurePluginTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Model/Config/StructurePluginTest.php @@ -61,7 +61,10 @@ class StructurePluginTest extends \PHPUnit_Framework_TestCase $this->objectManagerHelper = new ObjectManagerHelper($this); $this->plugin = $this->objectManagerHelper->getObject( ConfigStructurePlugin::class, - ['scopeDefiner' => $this->configScopeDefinerMock, 'helper' => $this->backendHelperMock] + [ + 'scopeDefiner' => $this->configScopeDefinerMock, + 'backendHelper' => $this->backendHelperMock + ] ); }