vendor/pimcore/pimcore/bundles/EcommerceFrameworkBundle/DependencyInjection/Configuration.php line 91

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4.  * Pimcore
  5.  *
  6.  * This source file is available under two different licenses:
  7.  * - GNU General Public License version 3 (GPLv3)
  8.  * - Pimcore Enterprise License (PEL)
  9.  * Full copyright and license information is available in
  10.  * LICENSE.md which is distributed with this source code.
  11.  *
  12.  * @copyright  Copyright (c) Pimcore GmbH (http://www.pimcore.org)
  13.  * @license    http://www.pimcore.org/license     GPLv3 and PEL
  14.  */
  15. namespace Pimcore\Bundle\EcommerceFrameworkBundle\DependencyInjection;
  16. use Pimcore\Bundle\CoreBundle\DependencyInjection\Config\Processor\PlaceholderProcessor;
  17. use Pimcore\Bundle\EcommerceFrameworkBundle\CartManager\AbstractCart;
  18. use Pimcore\Bundle\EcommerceFrameworkBundle\CartManager\Cart;
  19. use Pimcore\Bundle\EcommerceFrameworkBundle\CartManager\CartFactory;
  20. use Pimcore\Bundle\EcommerceFrameworkBundle\CartManager\CartPriceCalculator;
  21. use Pimcore\Bundle\EcommerceFrameworkBundle\CartManager\CartPriceCalculatorFactory;
  22. use Pimcore\Bundle\EcommerceFrameworkBundle\CartManager\MultiCartManager;
  23. use Pimcore\Bundle\EcommerceFrameworkBundle\CartManager\SessionCart;
  24. use Pimcore\Bundle\EcommerceFrameworkBundle\CheckoutManager\CheckoutManagerFactory;
  25. use Pimcore\Bundle\EcommerceFrameworkBundle\CheckoutManager\CommitOrderProcessor;
  26. use Pimcore\Bundle\EcommerceFrameworkBundle\DependencyInjection\Config\Processor\TenantProcessor;
  27. use Pimcore\Bundle\EcommerceFrameworkBundle\DependencyInjection\IndexService\DefaultWorkerConfigMapper;
  28. use Pimcore\Bundle\EcommerceFrameworkBundle\Factory;
  29. use Pimcore\Bundle\EcommerceFrameworkBundle\FilterService\FilterService;
  30. use Pimcore\Bundle\EcommerceFrameworkBundle\IndexService\Config\DefaultMysql;
  31. use Pimcore\Bundle\EcommerceFrameworkBundle\IndexService\IndexService;
  32. use Pimcore\Bundle\EcommerceFrameworkBundle\OfferTool\DefaultService as DefaultOfferToolService;
  33. use Pimcore\Bundle\EcommerceFrameworkBundle\OrderManager\Order\AgentFactory;
  34. use Pimcore\Bundle\EcommerceFrameworkBundle\OrderManager\Order\Listing;
  35. use Pimcore\Bundle\EcommerceFrameworkBundle\OrderManager\OrderManager;
  36. use Pimcore\Bundle\EcommerceFrameworkBundle\PaymentManager\PaymentManager;
  37. use Pimcore\Bundle\EcommerceFrameworkBundle\PricingManager\Environment;
  38. use Pimcore\Bundle\EcommerceFrameworkBundle\PricingManager\PriceInfo;
  39. use Pimcore\Bundle\EcommerceFrameworkBundle\PricingManager\PricingManager;
  40. use Pimcore\Bundle\EcommerceFrameworkBundle\PricingManager\Rule;
  41. use Pimcore\Bundle\EcommerceFrameworkBundle\SessionEnvironment;
  42. use Pimcore\Bundle\EcommerceFrameworkBundle\Tracking\TrackingItemBuilder;
  43. use Pimcore\Bundle\EcommerceFrameworkBundle\Tracking\TrackingManager;
  44. use Pimcore\Bundle\EcommerceFrameworkBundle\VoucherService\DefaultService as DefaultVoucherService;
  45. use Pimcore\Bundle\EcommerceFrameworkBundle\VoucherService\TokenManager\TokenManagerFactory;
  46. use Pimcore\Model\DataObject\OfferToolOffer;
  47. use Pimcore\Model\DataObject\OfferToolOfferItem;
  48. use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
  49. use Symfony\Component\Config\Definition\Builder\NodeDefinition;
  50. use Symfony\Component\Config\Definition\Builder\TreeBuilder;
  51. use Symfony\Component\Config\Definition\Builder\VariableNodeDefinition;
  52. use Symfony\Component\Config\Definition\ConfigurationInterface;
  53. use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
  54. class Configuration implements ConfigurationInterface
  55. {
  56.     /**
  57.      * @var TenantProcessor
  58.      */
  59.     private $tenantProcessor;
  60.     /**
  61.      * @var PlaceholderProcessor
  62.      */
  63.     private $placeholderProcessor;
  64.     /**
  65.      * @var DefaultWorkerConfigMapper
  66.      */
  67.     private $indexWorkerConfigMapper;
  68.     public function __construct()
  69.     {
  70.         $this->tenantProcessor = new TenantProcessor();
  71.         $this->placeholderProcessor = new PlaceholderProcessor();
  72.         $this->indexWorkerConfigMapper = new DefaultWorkerConfigMapper();
  73.     }
  74.     /**
  75.      * @inheritDoc
  76.      */
  77.     public function getConfigTreeBuilder()
  78.     {
  79.         $treeBuilder = new TreeBuilder();
  80.         $rootNode $treeBuilder->root('pimcore_ecommerce_framework');
  81.         $rootNode->addDefaultsIfNotSet();
  82.         $this->addRootNodeChildren($rootNode);
  83.         $rootNode
  84.             ->append($this->buildPimcoreNode())
  85.             ->append($this->buildFactoryNode())
  86.             ->append($this->buildEnvironmentNode())
  87.             ->append($this->buildCartManagerNode())
  88.             ->append($this->buildOrderManagerNode())
  89.             ->append($this->buildPricingManagerNode())
  90.             ->append($this->buildPriceSystemsNode())
  91.             ->append($this->buildAvailabilitySystemsNode())
  92.             ->append($this->buildCheckoutManagerNode())
  93.             ->append($this->buildPaymentManagerNode())
  94.             ->append($this->buildIndexServiceNode())
  95.             ->append($this->buildFilterServiceNode())
  96.             ->append($this->buildVoucherServiceNode())
  97.             ->append($this->buildOfferToolNode())
  98.             ->append($this->buildTrackingManagerNode());
  99.         return $treeBuilder;
  100.     }
  101.     private function addRootNodeChildren(ArrayNodeDefinition $rootNode)
  102.     {
  103.         $rootNode
  104.             ->children()
  105.                ->integerNode('decimal_scale')
  106.                     ->info('Default scale used for Decimal objects')
  107.                     ->min(0)
  108.                     ->defaultValue(4)
  109.                 ->end()
  110.             ->end();
  111.     }
  112.     private function buildPimcoreNode(): NodeDefinition
  113.     {
  114.         $builder = new TreeBuilder();
  115.         $pimcore $builder->root('pimcore');
  116.         $pimcore
  117.             ->addDefaultsIfNotSet()
  118.             ->info('Configuration of Pimcore backend menu entries');
  119.         $pimcore
  120.             ->children()
  121.                 ->arrayNode('menu')
  122.                     ->addDefaultsIfNotSet()
  123.                     ->children()
  124.                         ->arrayNode('pricing_rules')
  125.                             ->addDefaultsIfNotSet()
  126.                             ->canBeDisabled()
  127.                             ->info('Enabling/Disabling Pricing Rules menu entry. User specific settings can be done via permissions.')
  128.                         ->end()
  129.                         ->arrayNode('order_list')
  130.                             ->addDefaultsIfNotSet()
  131.                             ->canBeDisabled()
  132.                             ->info('Configuring order list menu - enabling/disabling and defining route of order list to inject custom implementations of order backend.')
  133.                             ->children()
  134.                                 ->scalarNode('route')
  135.                                     ->defaultValue('pimcore_ecommerce_backend_admin-order_list')
  136.                                 ->end()
  137.                                 ->scalarNode('path')
  138.                                     ->defaultNull()
  139.                                 ->end()
  140.                             ->end()
  141.                         ->end()
  142.                     ->end()
  143.                 ->end()
  144.             ->end();
  145.         return $pimcore;
  146.     }
  147.     private function buildFactoryNode(): NodeDefinition
  148.     {
  149.         $builder = new TreeBuilder();
  150.         $factory $builder->root('factory');
  151.         $factory
  152.             ->addDefaultsIfNotSet()
  153.             ->info('Configuration of e-commerce framework factory');
  154.         $factory
  155.             ->children()
  156.                 ->scalarNode('factory_id')
  157.                     ->defaultValue(Factory::class)
  158.                     ->cannotBeEmpty()
  159.                     ->info('Service Id of factory implementation')
  160.                 ->end()
  161.                 ->booleanNode('strict_tenants')
  162.                     ->defaultFalse()
  163.                     ->info('If true the factory will not fall back to the default tenant if a tenant is passed but not existing')
  164.                 ->end()
  165.             ->end();
  166.         return $factory;
  167.     }
  168.     private function buildEnvironmentNode(): NodeDefinition
  169.     {
  170.         $builder = new TreeBuilder();
  171.         $environment $builder->root('environment');
  172.         $environment
  173.             ->addDefaultsIfNotSet()
  174.             ->info('Configuration of environment');
  175.         $environment
  176.             ->children()
  177.                 ->scalarNode('environment_id')
  178.                     ->defaultValue(SessionEnvironment::class)
  179.                 ->end()
  180.                 ->append($this->buildOptionsNode('options'))
  181.             ->end();
  182.         return $environment;
  183.     }
  184.     private function buildCartManagerNode(): NodeDefinition
  185.     {
  186.         $builder = new TreeBuilder();
  187.         $cartManager $builder->root('cart_manager');
  188.         $cartManager
  189.             ->addDefaultsIfNotSet()
  190.             ->info('Settings for cart manager');
  191.         $cartManager
  192.             ->addDefaultsIfNotSet()
  193.             ->children()
  194.                 ->arrayNode('tenants')
  195.                     ->info('Configuration per tenant. If a _defaults key is set, it will be merged into every tenant. It needs to be set in every file. A tenant named "default" is mandatory.')
  196.                     ->example([
  197.                         '_defaults' => [
  198.                             'cart' => [
  199.                                 'factory_id' => 'CartFactory'
  200.                             ]
  201.                         ],
  202.                         'default' => [
  203.                             'cart' => [
  204.                                 'factory_options' => [
  205.                                     'cart_class_name' => 'Pimcore\Bundle\EcommerceFrameworkBundle\CartManager\Cart'
  206.                                 ]
  207.                             ],
  208.                             'price_calculator' => [
  209.                                 'modificators' => [
  210.                                     'shipping' => [
  211.                                         'class' => 'Pimcore\Bundle\EcommerceFrameworkBundle\CartManager\CartPriceModificator\Shipping',
  212.                                         'options' => [
  213.                                             'charge' => '5.90'
  214.                                         ]
  215.                                     ]
  216.                                 ]
  217.                             ]
  218.                         ],
  219.                         'noShipping' => [
  220.                             'price_calculator' => [
  221.                                 'factory_id' => 'PriceCalculatorFactory',
  222.                                 'modificators' => '~'
  223.                             ]
  224.                         ]
  225.                     ])
  226.                     ->useAttributeAsKey('name')
  227.                     ->validate()
  228.                         ->ifTrue(function (array $v) {
  229.                             return !array_key_exists('default'$v);
  230.                         })
  231.                         ->thenInvalid('Cart manager needs at least a default tenant')
  232.                     ->end()
  233.                     ->beforeNormalization()
  234.                     ->always(function ($v) {
  235.                         if (empty($v) || !is_array($v)) {
  236.                             $v = [];
  237.                         }
  238.                         return $this->tenantProcessor->mergeTenantConfig($v);
  239.                     })
  240.                     ->end()
  241.                     ->prototype('array')
  242.                         ->children()
  243.                             ->scalarNode('cart_manager_id')
  244.                                 ->defaultValue(MultiCartManager::class)
  245.                                 ->info('Service id of cart service')
  246.                             ->end()
  247.                             ->arrayNode('cart')
  248.                                 ->addDefaultsIfNotSet()
  249.                                 ->info('Configuration for carts')
  250.                                 ->children()
  251.                                     ->scalarNode('factory_id')
  252.                                         ->cannotBeEmpty()
  253.                                         ->defaultValue(CartFactory::class)
  254.                                         ->info('Service id of cart factory and configuration array')
  255.                                     ->end()
  256.                                     ->append($this->buildOptionsNode('factory_options', [
  257.                                         'cart_class_name' => Cart::class,
  258.                                         'guest_cart_class_name' => SessionCart::class,
  259.                                         'cart_readonly_mode' => AbstractCart::CART_READ_ONLY_MODE_STRICT
  260.                                     ]))
  261.                                 ->end()
  262.                             ->end()
  263.                             ->arrayNode('price_calculator')
  264.                                 ->addDefaultsIfNotSet()
  265.                                 ->children()
  266.                                     ->scalarNode('factory_id')
  267.                                         ->cannotBeEmpty()
  268.                                         ->defaultValue(CartPriceCalculatorFactory::class)
  269.                                     ->end()
  270.                                     ->append($this->buildOptionsNode(
  271.                                         'factory_options',
  272.                                         [
  273.                                             'class' => CartPriceCalculator::class
  274.                                         ],
  275.                                         "'class' defines a class name of the price calculator, which the factory instantiates. If you wish to replace or extend price calculation routine shipped with e-commerce framework provide your custom class name here."
  276.                                     ))
  277.                                     ->arrayNode('modificators')
  278.                                         ->info('List price modificators for cart, e.g. for shipping-cost, special discounts, etc. Key is name of modificator.')
  279.                                         ->useAttributeAsKey('name')
  280.                                         ->prototype('array')
  281.                                             ->children()
  282.                                                 ->scalarNode('class')->isRequired()->end()
  283.                                                 ->append($this->buildOptionsNode())
  284.                                             ->end()
  285.                                         ->end()
  286.                                     ->end()
  287.                                 ->end()
  288.                             ->end()
  289.                         ->end()
  290.                     ->end()
  291.                 ->end();
  292.         return $cartManager;
  293.     }
  294.     private function buildOrderManagerNode(): NodeDefinition
  295.     {
  296.         $builder = new TreeBuilder();
  297.         $orderManager $builder->root('order_manager');
  298.         $orderManager
  299.             ->info('Configuration of Order Manager')
  300.             ->addDefaultsIfNotSet();
  301.         $orderManager
  302.             ->addDefaultsIfNotSet()
  303.             ->children()
  304.                 ->arrayNode('tenants')
  305.                     ->info('Configuration per tenant. If a _defaults key is set, it will be merged into every tenant. A tenant named "default" is mandatory.')
  306.                     ->useAttributeAsKey('name')
  307.                     ->validate()
  308.                         ->ifTrue(function (array $v) {
  309.                             return !array_key_exists('default'$v);
  310.                         })
  311.                         ->thenInvalid('Order manager needs at least a default tenant')
  312.                     ->end()
  313.                     ->beforeNormalization()
  314.                     ->always(function ($v) {
  315.                         if (empty($v) || !is_array($v)) {
  316.                             $v = [];
  317.                         }
  318.                         return $this->tenantProcessor->mergeTenantConfig($v);
  319.                     })
  320.                     ->end()
  321.                     ->prototype('array')
  322.                         ->children()
  323.                             ->scalarNode('order_manager_id')
  324.                                 ->info('Service id for order manager implementation')
  325.                                 ->defaultValue(OrderManager::class)
  326.                             ->end()
  327.                             ->arrayNode('options')
  328.                                 ->info('Options for order manager')
  329.                                 ->addDefaultsIfNotSet()
  330.                                 ->children()
  331.                                     ->scalarNode('order_class')
  332.                                         ->info('Pimcore object class for orders')
  333.                                         ->defaultValue('\\Pimcore\\Model\\DataObject\\OnlineShopOrder')
  334.                                         ->cannotBeEmpty()
  335.                                     ->end()
  336.                                     ->scalarNode('order_item_class')
  337.                                         ->info('Pimcore object class for order items')
  338.                                         ->defaultValue('\\Pimcore\\Model\\DataObject\\OnlineShopOrderItem')
  339.                                         ->cannotBeEmpty()
  340.                                     ->end()
  341.                                     ->scalarNode('list_class')
  342.                                         ->info('Class for order listing')
  343.                                         ->defaultValue(Listing::class)
  344.                                         ->cannotBeEmpty()
  345.                                     ->end()
  346.                                     ->scalarNode('list_item_class')
  347.                                         ->info('Class for order item listing')
  348.                                         ->defaultValue(Listing\Item::class)
  349.                                         ->cannotBeEmpty()
  350.                                     ->end()
  351.                                     ->scalarNode('parent_order_folder')
  352.                                         ->info('Default parent folder for new orders')
  353.                                         ->defaultValue('/order/%%Y/%%m/%%d')
  354.                                         ->cannotBeEmpty()
  355.                                     ->end()
  356.                                 ->end()
  357.                             ->end()
  358.                             ->arrayNode('order_agent')
  359.                                 ->addDefaultsIfNotSet()
  360.                                 ->children()
  361.                                     ->scalarNode('factory_id')
  362.                                         ->info('Service id for order agent factory')
  363.                                         ->defaultValue(AgentFactory::class)
  364.                                         ->cannotBeEmpty()
  365.                                     ->end()
  366.                                     ->append($this->buildOptionsNode('factory_options'))
  367.                                 ->end()
  368.                             ->end()
  369.                         ->end()
  370.                     ->end()
  371.                 ->end();
  372.         return $orderManager;
  373.     }
  374.     private function buildPricingManagerNode(): NodeDefinition
  375.     {
  376.         $builder = new TreeBuilder();
  377.         $pricingManager $builder->root('pricing_manager');
  378.         $pricingManager
  379.             ->info('Configuration of Pricing Manager')
  380.             ->addDefaultsIfNotSet();
  381.         $pricingManager
  382.             // support deprecated options at the root level of the pricing_manager
  383.             // values set here will OVERWRITE the value in every tenant, even if the
  384.             // tenant defines the value!
  385.             // TODO remove in Pimcore 7
  386.             ->validate()
  387.                 ->always(function ($v) {
  388.                     $enabled null;
  389.                     if (isset($v['enabled']) && is_bool($v['enabled'])) {
  390.                         $enabled $v['enabled'];
  391.                         unset($v['enabled']);
  392.                     }
  393.                     $pricingManagerId null;
  394.                     if (isset($v['pricing_manager_id'])) {
  395.                         $pricingManagerId $v['pricing_manager_id'];
  396.                         unset($v['pricing_manager_id']);
  397.                     }
  398.                     $pricingManagerOptions null;
  399.                     if (isset($v['pricing_manager_options']) && !empty($v['pricing_manager_options'])) {
  400.                         $pricingManagerOptions $v['pricing_manager_options'];
  401.                         unset($v['pricing_manager_options']);
  402.                     }
  403.                     if (null === $enabled && null === $pricingManagerId && null === $pricingManagerOptions) {
  404.                         return $v;
  405.                     }
  406.                     foreach ($v['tenants'] as $tenant => &$tenantConfig) {
  407.                         if (null !== $enabled) {
  408.                             $tenantConfig['enabled'] = $enabled;
  409.                         }
  410.                         if (null !== $pricingManagerId) {
  411.                             $tenantConfig['pricing_manager_id'] = $pricingManagerId;
  412.                         }
  413.                         if (null !== $pricingManagerOptions) {
  414.                             $tenantConfig['pricing_manager_options'] = array_merge(
  415.                                 $tenantConfig['pricing_manager_options'],
  416.                                 $pricingManagerOptions
  417.                             );
  418.                         }
  419.                     }
  420.                     return $v;
  421.                 })
  422.             ->end()
  423.             ->children()
  424.                 ->booleanNode('enabled')
  425.                     ->setDeprecated('The child node "%node%" at the root level path "%path%" is deprecated. Please migrate to the new tenant structure.')
  426.                 ->end()
  427.                 ->scalarNode('pricing_manager_id')
  428.                     ->setDeprecated('The child node "%node%" at the root level path "%path%" is deprecated. Please migrate to the new tenant structure.')
  429.                 ->end()
  430.                 ->arrayNode('pricing_manager_options')
  431.                     ->children()
  432.                         ->scalarNode('rule_class')
  433.                             ->setDeprecated('The child node "%node%" at the root level path "%path%" is deprecated. Please migrate to the new tenant structure.')
  434.                         ->end()
  435.                         ->scalarNode('price_info_class')
  436.                             ->setDeprecated('The child node "%node%" at the root level path "%path%" is deprecated. Please migrate to the new tenant structure.')
  437.                         ->end()
  438.                         ->scalarNode('environment_class')
  439.                             ->setDeprecated('The child node "%node%" at the root level path "%path%" is deprecated. Please migrate to the new tenant structure.')
  440.                         ->end()
  441.                     ->end()
  442.                 ->end()
  443.                 ->arrayNode('conditions')
  444.                     ->info('Condition mapping from name to used class')
  445.                     ->useAttributeAsKey('name')
  446.                     ->prototype('scalar')
  447.                         ->cannotBeEmpty()
  448.                     ->end()
  449.                 ->end()
  450.                 ->arrayNode('actions')
  451.                     ->info('Action mapping from name to used class')
  452.                     ->useAttributeAsKey('name')
  453.                     ->prototype('scalar')
  454.                         ->cannotBeEmpty()
  455.                     ->end()
  456.                 ->end()
  457.                 ->arrayNode('tenants')
  458.                     ->info('Configuration per tenant. If a _defaults key is set, it will be merged into every tenant. A tenant named "default" is mandatory.')
  459.                     ->useAttributeAsKey('name')
  460.                     ->validate()
  461.                         ->ifTrue(function (array $v) {
  462.                             return !array_key_exists('default'$v);
  463.                         })
  464.                         ->thenInvalid('Pricing manager needs at least a default tenant')
  465.                     ->end()
  466.                     ->beforeNormalization()
  467.                         ->always(function ($v) {
  468.                             if (empty($v) || !is_array($v)) {
  469.                                 $v = [];
  470.                             }
  471.                             return $this->tenantProcessor->mergeTenantConfig($v);
  472.                         })
  473.                     ->end()
  474.                     ->prototype('array')
  475.                         ->canBeDisabled()
  476.                         ->children()
  477.                             ->scalarNode('pricing_manager_id')
  478.                                 ->info('Service id of pricing manager')
  479.                                 ->cannotBeEmpty()
  480.                                 ->defaultValue(PricingManager::class)
  481.                             ->end()
  482.                             ->arrayNode('pricing_manager_options')
  483.                                 ->info('Options for pricing manager')
  484.                                 ->addDefaultsIfNotSet()
  485.                                 ->children()
  486.                                     ->scalarNode('rule_class')
  487.                                         ->cannotBeEmpty()
  488.                                         ->defaultValue(Rule::class)
  489.                                     ->end()
  490.                                     ->scalarNode('price_info_class')
  491.                                         ->cannotBeEmpty()
  492.                                         ->defaultValue(PriceInfo::class)
  493.                                     ->end()
  494.                                     ->scalarNode('environment_class')
  495.                                         ->cannotBeEmpty()
  496.                                         ->defaultValue(Environment::class)
  497.                                     ->end()
  498.                                 ->end()
  499.                             ->end()
  500.                         ->end()
  501.                     ->end()
  502.                 ->end()
  503.             ->end();
  504.         return $pricingManager;
  505.     }
  506.     private function buildPriceSystemsNode(): NodeDefinition
  507.     {
  508.         $builder = new TreeBuilder();
  509.         $priceSystems $builder->root('price_systems');
  510.         $priceSystems
  511.             ->info('Configuration of price systems - key is name of price system.')
  512.             ->useAttributeAsKey('name')
  513.             ->prototype('array')
  514.                 ->beforeNormalization()
  515.                     ->ifString()
  516.                     ->then(function ($v) {
  517.                         return ['id' => $v];
  518.                     })
  519.                 ->end()
  520.                 ->children()
  521.                     ->scalarNode('name')->end()
  522.                     ->scalarNode('id')
  523.                         ->isRequired()
  524.                     ->end()
  525.                 ->end()
  526.             ->end();
  527.         return $priceSystems;
  528.     }
  529.     private function buildAvailabilitySystemsNode(): NodeDefinition
  530.     {
  531.         $builder = new TreeBuilder();
  532.         $availabilitySystems $builder->root('availability_systems');
  533.         $availabilitySystems
  534.             ->useAttributeAsKey('name')
  535.             ->info('Configuration of availability systems - key is name of price system.')
  536.             ->prototype('array')
  537.                 ->beforeNormalization()
  538.                     ->ifString()
  539.                     ->then(function ($v) {
  540.                         return ['id' => $v];
  541.                     })
  542.                 ->end()
  543.                 ->children()
  544.                     ->scalarNode('name')->end()
  545.                     ->scalarNode('id')
  546.                         ->isRequired()
  547.                     ->end()
  548.                 ->end()
  549.             ->end();
  550.         return $availabilitySystems;
  551.     }
  552.     private function buildCheckoutManagerNode(): NodeDefinition
  553.     {
  554.         $builder = new TreeBuilder();
  555.         $checkoutManager $builder->root('checkout_manager');
  556.         $checkoutManager
  557.             ->info('Configuration of checkout manager')
  558.             ->addDefaultsIfNotSet();
  559.         $checkoutManager
  560.             ->children()
  561.                 ->arrayNode('tenants')
  562.                     ->info('Configuration per tenant. If a _defaults key is set, it will be merged into every tenant. A tenant named "default" is mandatory.')
  563.                     ->useAttributeAsKey('name')
  564.                     ->validate()
  565.                         ->ifTrue(function (array $v) {
  566.                             return !array_key_exists('default'$v);
  567.                         })
  568.                         ->thenInvalid('Checkout manager needs at least a default tenant')
  569.                     ->end()
  570.                     ->beforeNormalization()
  571.                         ->always(function ($v) {
  572.                             if (empty($v) || !is_array($v)) {
  573.                                 $v = [];
  574.                             }
  575.                             return $this->tenantProcessor->mergeTenantConfig($v);
  576.                         })
  577.                     ->end()
  578.                     ->prototype('array')
  579.                         ->children()
  580.                             ->scalarNode('factory_id')
  581.                                 ->defaultValue(CheckoutManagerFactory::class)
  582.                                 ->cannotBeEmpty()
  583.                             ->end()
  584.                             ->append($this->buildOptionsNode('factory_options'))
  585.                             ->arrayNode('payment')
  586.                                 ->info('Define payment provider which should be used for payment. Payment providers are defined in payment_manager section.')
  587.                                 ->addDefaultsIfNotSet()
  588.                                 ->children()
  589.                                     ->scalarNode('provider')
  590.                                         ->defaultNull()
  591.                                     ->end()
  592.                                 ->end()
  593.                             ->end()
  594.                             ->arrayNode('commit_order_processor')
  595.                                 ->info('Define used commit order processor')
  596.                                 ->addDefaultsIfNotSet()
  597.                                 ->children()
  598.                                     ->scalarNode('id')
  599.                                         ->defaultValue(CommitOrderProcessor::class)
  600.                                         ->cannotBeEmpty()
  601.                                     ->end()
  602.                                     ->append($this->buildOptionsNode())
  603.                                 ->end()
  604.                             ->end()
  605.                             ->arrayNode('steps')
  606.                                 ->info('Define different checkout steps which need to be committed before commit of order is possible')
  607.                                 ->requiresAtLeastOneElement()
  608.                                 ->useAttributeAsKey('name')
  609.                                 ->prototype('array')
  610.                                     ->children()
  611.                                         ->scalarNode('class')
  612.                                             ->isRequired()
  613.                                         ->end()
  614.                                         ->append($this->buildOptionsNode())
  615.                                     ->end()
  616.                                 ->end()
  617.                             ->end()
  618.                         ->end()
  619.                     ->end()
  620.                 ->end()
  621.             ->end();
  622.         return $checkoutManager;
  623.     }
  624.     private function buildPaymentManagerNode(): NodeDefinition
  625.     {
  626.         $builder = new TreeBuilder();
  627.         $paymentManager $builder->root('payment_manager');
  628.         $paymentManager
  629.             ->info('Configuration of payment manager and payment providers')
  630.             ->addDefaultsIfNotSet();
  631.         $paymentManager
  632.             ->children()
  633.                 ->scalarNode('payment_manager_id')
  634.                     ->cannotBeEmpty()
  635.                     ->defaultValue(PaymentManager::class)
  636.                 ->end()
  637.                 ->arrayNode('providers')
  638.                     ->info('Configuration of payment providers, key is name of provider.')
  639.                     ->useAttributeAsKey('name')
  640.                     ->prototype('array')
  641.                         ->children()
  642.                             ->scalarNode('name')->end()
  643.                             ->scalarNode('provider_id')
  644.                                 ->info('Service id of payment provider implementation')
  645.                                 ->isRequired()
  646.                             ->end()
  647.                             ->scalarNode('profile')
  648.                                 ->info('Currently active profile')
  649.                                 ->isRequired()
  650.                             ->end()
  651.                             ->arrayNode('profiles')
  652.                                 ->info('Available profiles with options')
  653.                                 ->beforeNormalization()
  654.                                     ->always(function ($v) {
  655.                                         if (empty($v) || !is_array($v)) {
  656.                                             $v = [];
  657.                                         }
  658.                                         return $this->tenantProcessor->mergeTenantConfig($v);
  659.                                     })
  660.                                 ->end()
  661.                                 ->useAttributeAsKey('name')
  662.                                 ->prototype('array')
  663.                                     ->useAttributeAsKey('name')
  664.                                     ->prototype('variable')->end()
  665.                                 ->end()
  666.                             ->end()
  667.                         ->end()
  668.                     ->end()
  669.                 ->end()
  670.             ->end();
  671.         return $paymentManager;
  672.     }
  673.     private function buildIndexServiceNode(): NodeDefinition
  674.     {
  675.         $builder = new TreeBuilder();
  676.         $indexService $builder->root('index_service');
  677.         $indexService
  678.             ->addDefaultsIfNotSet()
  679.             ->info('Configuration of index service');
  680.         $indexService
  681.             ->children()
  682.                 ->scalarNode('index_service_id')
  683.                     ->cannotBeEmpty()
  684.                     ->defaultValue(IndexService::class)
  685.                 ->end()
  686.                 ->scalarNode('default_tenant')
  687.                     ->cannotBeEmpty()
  688.                     ->defaultValue('default')
  689.                 ->end()
  690.                 ->arrayNode('tenants')
  691.                     ->info('Configure assortment tenants - at least one tenant has to be configured. If a _defaults key is set, it will be merged into every tenant.')
  692.                     ->useAttributeAsKey('name'false)
  693.                     ->validate()
  694.                         ->always(function (array $v) {
  695.                             // check if all search attributes are defined as attribute
  696.                             foreach ($v as $tenant => $tenantConfig) {
  697.                                 foreach ($tenantConfig['search_attributes'] as $searchAttribute) {
  698.                                     $attributeFound false;
  699.                                     if (isset($tenantConfig['attributes'][$searchAttribute])) {
  700.                                         $attributeFound true;
  701.                                     }
  702.                                     $delimiters = ['.''^'];
  703.                                     foreach ($delimiters as $delimiter) {
  704.                                         if (!$attributeFound && strpos($searchAttribute$delimiter) !== false) {
  705.                                             $fieldNameParts explode($delimiter$searchAttribute);
  706.                                             if (isset($tenantConfig['attributes'][$fieldNameParts[0]])) {
  707.                                                 $attributeFound true;
  708.                                             }
  709.                                         }
  710.                                     }
  711.                                     if (!$attributeFound) {
  712.                                         throw new InvalidConfigurationException(sprintf(
  713.                                             'The search attribute "%s" in product index tenant "%s" is not defined as attribute.',
  714.                                             $searchAttribute,
  715.                                             $tenant
  716.                                         ));
  717.                                     }
  718.                                 }
  719.                             }
  720.                             return $v;
  721.                         })
  722.                     ->end()
  723.                     ->beforeNormalization()
  724.                         ->always(function ($v) {
  725.                             if (empty($v) || !is_array($v)) {
  726.                                 $v = [];
  727.                             }
  728.                             $config $this->tenantProcessor->mergeTenantConfig($v);
  729.                             foreach ($config as $tenant => $tenantConfig) {
  730.                                 if (isset($tenantConfig['placeholders']) && is_array($tenantConfig['placeholders']) && count($tenantConfig['placeholders']) > 0) {
  731.                                     $placeholders $tenantConfig['placeholders'];
  732.                                     // remove placeholders while replacing as we don't want to replace the placeholders
  733.                                     unset($tenantConfig['placeholders']);
  734.                                     $config[$tenant] = $this->placeholderProcessor->mergePlaceholders($tenantConfig$placeholders);
  735.                                     // re-add placeholders
  736.                                     $config[$tenant]['placeholders'] = $placeholders;
  737.                                 }
  738.                                 $config[$tenant]['config_id'] = $config[$tenant]['config_id'] ?? null;
  739.                                 $config[$tenant]['worker_id'] = $config[$tenant]['worker_id'] ?? null;
  740.                                 // if only config or worker is set, try to auto resolve missing config/worker
  741.                                 if (!($config[$tenant]['config_id'] && $config[$tenant]['worker_id'])) {
  742.                                     // nothing is set - set default value
  743.                                     if (!$config[$tenant]['config_id'] && !$config[$tenant]['worker_id']) {
  744.                                         $config[$tenant]['config_id'] = DefaultMysql::class;
  745.                                     }
  746.                                     // resolve default matching part
  747.                                     if ($config[$tenant]['config_id']) {
  748.                                         $config[$tenant]['worker_id'] = $this->indexWorkerConfigMapper->getWorkerForConfig($config[$tenant]['config_id']);
  749.                                     } elseif ($config[$tenant]['worker_id']) {
  750.                                         $config[$tenant]['config_id'] = $this->indexWorkerConfigMapper->getConfigForWorker($config[$tenant]['worker_id']);
  751.                                     }
  752.                                 }
  753.                             }
  754.                             return $config;
  755.                         })
  756.                     ->end()
  757.                     ->prototype('array')
  758.                         ->addDefaultsIfNotSet()
  759.                         ->canBeDisabled()
  760.                         ->children()
  761.                             ->scalarNode('config_id')
  762.                                 ->info('Service id of config implementation')
  763.                                 ->cannotBeEmpty()
  764.                                 ->defaultValue(DefaultMysql::class)
  765.                             ->end()
  766.                             ->append($this->buildOptionsNode('config_options'))
  767.                             ->scalarNode('worker_id')
  768.                                 ->info('Worker id of worker implementation. Can be omitted, then default worker id of configured config is used.')
  769.                                 ->cannotBeEmpty()
  770.                             ->end()
  771.                             ->arrayNode('placeholders')
  772.                                 ->info('Placeholder values in this tenant attributes definition (locale: "%%locale%%") will be replaced by the given placeholder value (eg. "de_AT")')
  773.                                 ->example([
  774.                                     'placeholders' => [
  775.                                         '%%locale%%' => 'de_AT'
  776.                                     ]
  777.                                 ])
  778.                                 ->defaultValue([])
  779.                                 ->beforeNormalization()
  780.                                     ->castToArray()
  781.                                 ->end()
  782.                                 ->prototype('scalar')->end()
  783.                             ->end()
  784.                             ->arrayNode('search_attributes')
  785.                                 ->info('Add columns for general fulltext search index of product list - they must be part of the column configuration below')
  786.                                 ->defaultValue([])
  787.                                 ->beforeNormalization()
  788.                                     ->castToArray()
  789.                                 ->end()
  790.                                 ->prototype('scalar')->end()
  791.                             ->end()
  792.                             ->arrayNode('attributes')
  793.                                 ->info('Attributes definition for product index - key is name of attribute')
  794.                                 ->useAttributeAsKey('name'false)
  795.                                 ->beforeNormalization()
  796.                                     ->always(function ($v) {
  797.                                         if (empty($v) || !is_array($v)) {
  798.                                             $v = [];
  799.                                         }
  800.                                         // make sure the name property is set
  801.                                         foreach (array_keys($v) as $name) {
  802.                                             if (!isset($v[$name]['name'])) {
  803.                                                 $v[$name]['name'] = $name;
  804.                                             }
  805.                                         }
  806.                                         return $v;
  807.                                     })
  808.                                 ->end()
  809.                                 ->prototype('array')
  810.                                     ->beforeNormalization()
  811.                                         ->always(function ($v) {
  812.                                             if (empty($v) || !is_array($v)) {
  813.                                                 return $v;
  814.                                             }
  815.                                             $v $this->remapProperties($v, [
  816.                                                 'fieldname' => 'field_name',
  817.                                                 'filtergroup' => 'filter_group',
  818.                                                 'getter' => 'getter_id',
  819.                                                 'interpreter' => 'interpreter_id',
  820.                                                 'config' => 'options',
  821.                                                 'hideInFieldlistDatatype' => 'hide_in_fieldlist_datatype'
  822.                                             ]);
  823.                                             // this option was never properly supported
  824.                                             // and is ignored
  825.                                             if (isset($v['mapping'])) {
  826.                                                 @trigger_error('The "mapping" config entry on the ecommerce index attribute level is unsupported and will be removed in Pimcore 7. Please set "options.mapping" instead.'E_USER_DEPRECATED);
  827.                                                 unset($v['mapping']);
  828.                                             }
  829.                                             return $v;
  830.                                         })
  831.                                     ->end()
  832.                                     ->children()
  833.                                         ->scalarNode('name')->isRequired()->end()
  834.                                         ->scalarNode('field_name')->defaultNull()->info('Defines object attribute field name, can be omitted if the same like name of index attribute')->end()
  835.                                         ->scalarNode('type')->defaultNull()->info('Type of index attribute (database column or elastic search data type)')->end()
  836.                                         ->scalarNode('locale')->defaultNull()->info('Locale for localized fields, can be omitted if not necessary')->end()
  837.                                         ->scalarNode('filter_group')->defaultNull()->info('Defines filter group for filter definition in filter service')->end()
  838.                                         ->append($this->buildOptionsNode())
  839.                                         ->scalarNode('getter_id')->defaultNull()->info('Service id of getter for this field')->end()
  840.                                         ->append($this->buildOptionsNode('getter_options'))
  841.                                         ->scalarNode('interpreter_id')->defaultNull()->info('Service id of interpreter for this field')->end()
  842.                                         ->append($this->buildOptionsNode('interpreter_options'))
  843.                                         ->append($this->buildOptionsNode('mapping')) // TODO Symfony 3.4 set as deprecated. TODO Pimcore 7 remove option completely.
  844.                                         ->booleanNode('hide_in_fieldlist_datatype')->defaultFalse()->info('Hides field in field list selection data type of filter service - default to false')->end()
  845.                                     ->end()
  846.                                 ->end()
  847.                             ->end()
  848.                         ->end()
  849.                     ->end()
  850.                 ->end()
  851.             ->end();
  852.         return $indexService;
  853.     }
  854.     private function buildFilterServiceNode(): NodeDefinition
  855.     {
  856.         $builder = new TreeBuilder();
  857.         $filterService $builder->root('filter_service');
  858.         $filterService
  859.             ->info('Configuration of filter service')
  860.             ->addDefaultsIfNotSet();
  861.         $filterService
  862.             ->children()
  863.                 ->arrayNode('tenants')
  864.                     ->info('Configuration per tenant. If a _defaults key is set, it will be merged into every tenant.')
  865.                     ->useAttributeAsKey('name'false)
  866.                     ->beforeNormalization()
  867.                         ->always(function ($v) {
  868.                             if (empty($v) || !is_array($v)) {
  869.                                 $v = [];
  870.                             }
  871.                             return $this->tenantProcessor->mergeTenantConfig($v);
  872.                         })
  873.                     ->end()
  874.                     ->prototype('array')
  875.                         ->addDefaultsIfNotSet()
  876.                         ->canBeDisabled()
  877.                         ->children()
  878.                             ->scalarNode('service_id')
  879.                                 ->cannotBeEmpty()
  880.                                 ->defaultValue(FilterService::class)
  881.                             ->end()
  882.                             ->arrayNode('filter_types')
  883.                                 ->info('Assign backend implementations and views to filter type field collections')
  884.                                 ->useAttributeAsKey('name')
  885.                                 ->prototype('array')
  886.                                     ->addDefaultsIfNotSet()
  887.                                     ->beforeNormalization()
  888.                                         ->always(function ($v) {
  889.                                             if (empty($v) || !is_array($v)) {
  890.                                                 return $v;
  891.                                             }
  892.                                             return $this->remapProperties($v, [
  893.                                                 'class' => 'filter_type_id',
  894.                                                 'script' => 'template'
  895.                                             ]);
  896.                                         })
  897.                                     ->end()
  898.                                     ->children()
  899.                                         ->scalarNode('filter_type_id')
  900.                                             ->info('Service id for filter type implementation')
  901.                                             ->isRequired()
  902.                                         ->end()
  903.                                         ->scalarNode('template')
  904.                                             ->info('Default template for filter, can be overwritten in filter definition')
  905.                                             ->isRequired()
  906.                                         ->end()
  907.                                         ->append($this->buildOptionsNode())
  908.                                     ->end()
  909.                                 ->end()
  910.                             ->end()
  911.                         ->end()
  912.                     ->end()
  913.                 ->end()
  914.             ->end();
  915.         return $filterService;
  916.     }
  917.     private function buildVoucherServiceNode(): NodeDefinition
  918.     {
  919.         $builder = new TreeBuilder();
  920.         $voucherService $builder->root('voucher_service');
  921.         $voucherService
  922.             ->info('Configuration of voucher service')
  923.             ->addDefaultsIfNotSet();
  924.         $voucherService
  925.             ->children()
  926.                 ->scalarNode('voucher_service_id')
  927.                     ->info('Service id of voucher service implementation')
  928.                     ->cannotBeEmpty()
  929.                     ->defaultValue(DefaultVoucherService::class)
  930.                 ->end()
  931.                 ->arrayNode('voucher_service_options')
  932.                     ->addDefaultsIfNotSet()
  933.                     ->children()
  934.                         ->integerNode('reservation_minutes_threshold')
  935.                             ->info('Reservations older than x MINUTES get removed by maintenance task')
  936.                             ->defaultValue(5)
  937.                             ->min(0)
  938.                         ->end()
  939.                         ->integerNode('statistics_days_threshold')
  940.                             ->info('Statistics older than x DAYS get removed by maintenance task')
  941.                             ->defaultValue(30)
  942.                             ->min(0)
  943.                         ->end()
  944.                     ->end()
  945.                 ->end()
  946.                 ->arrayNode('token_managers')
  947.                     ->info('Configuration of token managers')
  948.                     ->addDefaultsIfNotSet()
  949.                     ->children()
  950.                         ->scalarNode('factory_id')
  951.                             ->info('Service id of token manager factory')
  952.                             ->cannotBeEmpty()
  953.                             ->defaultValue(TokenManagerFactory::class)
  954.                         ->end()
  955.                         ->arrayNode('mapping')
  956.                             ->info('Mapping for token manager implementations')
  957.                             ->useAttributeAsKey('name')
  958.                             ->prototype('scalar')
  959.                                 ->cannotBeEmpty()
  960.                             ->end()
  961.                         ->end()
  962.                     ->end()
  963.                 ->end()
  964.             ->end();
  965.         return $voucherService;
  966.     }
  967.     private function buildOfferToolNode(): NodeDefinition
  968.     {
  969.         $builder = new TreeBuilder();
  970.         $offerTool $builder->root('offer_tool');
  971.         $offerTool
  972.             ->info('Configuration of offer tool')
  973.             ->addDefaultsIfNotSet();
  974.         $offerTool
  975.             ->children()
  976.                 ->scalarNode('service_id')
  977.                     ->info('Service id for offer tool service')
  978.                     ->defaultValue(DefaultOfferToolService::class)
  979.                     ->cannotBeEmpty()
  980.                 ->end()
  981.                 ->arrayNode('order_storage')
  982.                     ->addDefaultsIfNotSet()
  983.                     ->children()
  984.                         ->scalarNode('offer_class')
  985.                             ->info('Pimcore object class for offers')
  986.                             ->cannotBeEmpty()
  987.                             ->defaultValue(OfferToolOffer::class)
  988.                         ->end()
  989.                         ->scalarNode('offer_item_class')
  990.                             ->info('Pimcore object class for offer items')
  991.                             ->cannotBeEmpty()
  992.                             ->defaultValue(OfferToolOfferItem::class)
  993.                         ->end()
  994.                         ->scalarNode('parent_folder_path')
  995.                             ->info('default path for new offers')
  996.                             ->cannotBeEmpty()
  997.                             ->defaultValue('/offertool/offers/%%Y/%%m')
  998.                         ->end()
  999.                     ->end()
  1000.                 ->end()
  1001.             ->end();
  1002.         return $offerTool;
  1003.     }
  1004.     private function buildTrackingManagerNode(): NodeDefinition
  1005.     {
  1006.         $builder = new TreeBuilder();
  1007.         $trackingManager $builder->root('tracking_manager');
  1008.         $trackingManager
  1009.             ->info('Configuration of Tracking Manager')
  1010.             ->addDefaultsIfNotSet();
  1011.         $trackingManager
  1012.             ->children()
  1013.                 ->scalarNode('tracking_manager_id')
  1014.                     ->info('Service id of tracking manager')
  1015.                     ->defaultValue(TrackingManager::class)
  1016.                 ->end()
  1017.                 ->arrayNode('trackers')
  1018.                     ->info('Enable/Disable trackers and configure them')
  1019.                     ->useAttributeAsKey('name')
  1020.                     ->prototype('array')
  1021.                         ->canBeDisabled()
  1022.                         ->children()
  1023.                             ->scalarNode('id')
  1024.                                 ->info('Service id for tracker')
  1025.                                 ->isRequired()
  1026.                             ->end()
  1027.                             ->append($this->buildOptionsNode())
  1028.                             ->scalarNode('item_builder_id')
  1029.                                 ->info('Service id for item builder for tracker')
  1030.                                 ->defaultValue(TrackingItemBuilder::class)
  1031.                             ->end()
  1032.                             ->arrayNode('tenants')
  1033.                                 ->addDefaultsIfNotSet()
  1034.                                 ->info('List of assortment and checkout tenants where this tracker should be activated for.')
  1035.                                 ->children()
  1036.                                     ->arrayNode('assortment')
  1037.                                         ->info('Add list of assortment tenants where the tracker should be activated for. Empty array means activated for all tenants.')
  1038.                                         ->defaultValue([])
  1039.                                         ->beforeNormalization()
  1040.                                             ->castToArray()
  1041.                                         ->end()
  1042.                                         ->prototype('scalar')->end()
  1043.                                     ->end()
  1044.                                     ->arrayNode('checkout')
  1045.                                         ->info('Add list of checkout tenants where the tracker should be activated for. Empty array means activated for all tenants.')
  1046.                                         ->defaultValue([])
  1047.                                         ->beforeNormalization()
  1048.                                             ->castToArray()
  1049.                                         ->end()
  1050.                                         ->prototype('scalar')->end()
  1051.                                     ->end()
  1052.                                 ->end()
  1053.                             ->end()
  1054.                         ->end()
  1055.                     ->end()
  1056.                 ->end()
  1057.             ->end();
  1058.         return $trackingManager;
  1059.     }
  1060.     private function buildOptionsNode(string $name 'options', array $defaultValue = [], string $documentation null): NodeDefinition
  1061.     {
  1062.         $node = new VariableNodeDefinition($name);
  1063.         if ($documentation) {
  1064.             $node->info($documentation);
  1065.         }
  1066.         $node
  1067.             ->defaultValue($defaultValue)
  1068.             ->treatNullLike([])
  1069.             ->beforeNormalization()
  1070.                ->castToArray()
  1071.             ->end();
  1072.         return $node;
  1073.     }
  1074.     /**
  1075.      * Normalizes properties from old to new names to easy migration
  1076.      *
  1077.      * @param array $data
  1078.      * @param array $map
  1079.      *
  1080.      * @return array
  1081.      */
  1082.     private function remapProperties(array $data, array $map): array
  1083.     {
  1084.         foreach ($map as $old => $new) {
  1085.             if (isset($data[$old]) && !isset($data[$new])) {
  1086.                 $data[$new] = $data[$old];
  1087.                 unset($data[$old]);
  1088.             }
  1089.         }
  1090.         return $data;
  1091.     }
  1092. }