diff --git a/modules/inpostizi/config/admin/services.yml b/modules/inpostizi/config/admin/services.yml new file mode 100644 index 00000000..ea8dd0aa --- /dev/null +++ b/modules/inpostizi/config/admin/services.yml @@ -0,0 +1,65 @@ +# PS >= 1.7.4 BO configuration +imports: + - { resource: ../services/sf34.yml } + - { resource: ../services/common_admin.yml } + +parameters: + container.autowiring.strict_mode: true + +services: + _defaults: + public: false + autowire: true + bind: + $context: '@inpost.izi.context' + $assetsProviders: !tagged inpost.izi.admin_assets_provider + $formFactory: '@=container.has("form.factory") ? service("form.factory") : service("inpost.izi.form_factory")' + + izi\prestashop\Hook\Admin\ActionAdminCartRuleSaveAfter: + class: izi\prestashop\Hook\Admin\ActionAdminCartRuleSaveAfter + + izi\prestashop\Hook\Admin\ActionAdminControllerSetMedia: + class: izi\prestashop\Hook\Admin\ActionAdminControllerSetMedia + + izi\prestashop\Hook\Admin\ActionAdminInPostConfirmedShipmentsControllerAfter: + class: izi\prestashop\Hook\Admin\ActionAdminInPostConfirmedShipmentsControllerAfter + + izi\prestashop\Hook\Admin\ActionAdminInPostConfirmedShipmentsControllerBefore: + class: izi\prestashop\Hook\Admin\ActionAdminInPostConfirmedShipmentsControllerBefore + + izi\prestashop\Hook\Admin\DisplayAdminOrderSide: + class: izi\prestashop\Hook\Admin\DisplayAdminOrderSide + + izi\prestashop\Hook\Admin\DisplayAdminOrderLeft: + class: izi\prestashop\Hook\Admin\DisplayAdminOrderLeft + + izi\prestashop\Hook\Admin\DisplayBackOfficeHeader: + class: izi\prestashop\Hook\Admin\DisplayBackOfficeHeader + + izi\prestashop\CommandBus: + class: izi\prestashop\CommandBus + tags: [ container.service_subscriber ] + + izi\prestashop\Hook\HookExecutor: + class: izi\prestashop\Hook\HookExecutor + tags: [ container.service_subscriber ] + arguments: + $widget: '@inpost.izi.module' + + izi\prestashop\Form\FormFactoryFactory: + class: izi\prestashop\Form\FormFactoryFactory + + inpost.izi.form_factory: + class: Symfony\Component\Form\FormFactoryInterface + factory: [ '@izi\prestashop\Form\FormFactoryFactory', create ] + arguments: + - '@inpost.izi.form_type_locator' + - Symfony\Component\Form\Extension\Core\Type\FormType: ['@izi\prestashop\Form\TypeExtension\HelpTextExtension'] + + inpost.izi.form_type_locator: + class: Symfony\Component\DependencyInjection\ServiceLocator + tags: + - { name: container.service_locator } + arguments: + - izi\prestashop\Form\Type\CartRuleOptionsType: '@izi\prestashop\Form\Type\CartRuleOptionsType' + izi\prestashop\Form\Type\ObjectModelType: '@izi\prestashop\Form\Type\ObjectModelType' diff --git a/modules/inpostizi/config/front/services.yml b/modules/inpostizi/config/front/services.yml new file mode 100644 index 00000000..6f24dad4 --- /dev/null +++ b/modules/inpostizi/config/front/services.yml @@ -0,0 +1,100 @@ +# PS >= 1.7.4 FO configuration +imports: + - { resource: ../services/sf34.yml } + - { resource: ../services/common_front.yml } + +services: + _defaults: + public: false + + inpost.izi.hook_locator: + class: Symfony\Component\DependencyInjection\ServiceLocator + tags: [ container.service_locator ] + arguments: + - actionObjectCartDeleteBefore: '@izi\prestashop\Hook\Common\ActionCartDeleteBefore' + actionObjectCartUpdateAfter: '@izi\prestashop\Hook\Common\ActionCartUpdateAfter' + actionObjectInPostShipmentModelAddAfter: '@izi\prestashop\Hook\Common\ActionShipmentAddAfter' + actionObjectInPostShipmentModelUpdateBefore: '@izi\prestashop\Hook\Common\ActionShipmentUpdateBefore' + actionObjectInPostShipmentModelUpdateAfter: '@izi\prestashop\Hook\Common\ActionShipmentUpdateAfter' + actionValidateOrder: '@izi\prestashop\Hook\Common\ActionValidateOrder' + actionOrderStatusPostUpdate: '@izi\prestashop\Hook\Common\ActionOrderStatusPostUpdate' + actionObjectOrderUpdateBefore: '@izi\prestashop\Hook\Common\ActionObjectOrderUpdateBefore' + actionObjectOrderUpdateAfter: '@izi\prestashop\Hook\Common\ActionObjectOrderUpdateAfter' + actionAjaxDieCartControllerdisplayAjaxUpdateBefore: '@izi\prestashop\Hook\Front\ActionCartControllerAjaxUpdateResponse' + actionFrontControllerSetMedia: '@izi\prestashop\Hook\Front\ActionFrontControllerSetMedia' + actionEmailSendBefore: '@izi\prestashop\Hook\Common\ActionEmailSendBefore' + displayHeader: '@izi\prestashop\Hook\Front\DisplayHeader' + paymentOptions: '@izi\prestashop\Hook\Front\ActionGetPaymentOptions' + displayOrderConfirmation: '@izi\prestashop\Hook\Front\DisplayOrderConfirmation' + displayIziThankYou: '@izi\prestashop\Hook\Front\DisplayIziThankYou' + displayPaymentReturn: '@izi\prestashop\Hook\Front\DisplayPaymentReturn' + displayProductActions: '@izi\prestashop\Hook\Front\DisplayProductActions' + displayProductAdditionalInfo: '@izi\prestashop\Hook\Front\DisplayProductAdditionalInfo' + displayExpressCheckout: '@izi\prestashop\Hook\Front\DisplayExpressCheckout' + displayShoppingCart: '@izi\prestashop\Hook\Front\DisplayShoppingCart' + displayShoppingCartFooter: '@izi\prestashop\Hook\Front\DisplayShoppingCartFooter' + displayCheckoutSummaryTop: '@izi\prestashop\Hook\Front\DisplayCheckoutSummaryTop' + displayCustomerAccountFormTop: '@izi\prestashop\Hook\Front\DisplayCustomerAccountFormTop' + displayCustomerLoginFormAfter: '@izi\prestashop\Hook\Front\DisplayCustomerLoginFormAfter' + displayIziCartPreviewButton: '@izi\prestashop\Hook\Front\DisplayIziCartPreviewButton' + displayIziCheckoutButton: '@izi\prestashop\Hook\Front\DisplayIziCheckoutButton' + # products + actionObjectProductDeleteBefore: '@izi\prestashop\Hook\Common\Product\ActionProductDeleteBefore' + actionObjectProductDeleteAfter: '@izi\prestashop\Hook\Common\Product\ActionProductDeleteAfter' + actionObjectProductUpdateAfter: '@izi\prestashop\Hook\Common\Product\ActionProductUpdateAfter' + actionObjectCombinationDeleteBefore: '@izi\prestashop\Hook\Common\Product\ActionCombinationDeleteBefore' + actionObjectCombinationDeleteAfter: '@izi\prestashop\Hook\Common\Product\ActionCombinationDeleteAfter' + actionObjectCombinationUpdateAfter: '@izi\prestashop\Hook\Common\Product\ActionCombinationUpdateAfter' + actionObjectImageAddAfter: '@izi\prestashop\Hook\Common\Product\ActionImageAddAfter' + actionObjectImageDeleteAfter: '@izi\prestashop\Hook\Common\Product\ActionImageDeleteAfter' + actionObjectSpecificPriceAddAfter: '@izi\prestashop\Hook\Common\Product\ActionSpecificPriceAddAfter' + actionObjectSpecificPriceUpdateAfter: '@izi\prestashop\Hook\Common\Product\ActionSpecificPriceUpdateAfter' + actionObjectSpecificPriceDeleteAfter: '@izi\prestashop\Hook\Common\Product\ActionSpecificPriceDeleteAfter' + actionUpdateQuantity: '@izi\prestashop\Hook\Common\Product\ActionUpdateQuantity' + + inpost.izi.basket_event_handler_locator: + class: Symfony\Component\DependencyInjection\ServiceLocator + tags: [ container.service_locator ] + arguments: + - PRODUCTS_QUANTITY: '@izi\prestashop\MerchantApi\Handler\Basket\ProductsQuantityEventHandler' + PROMO_CODES: '@izi\prestashop\MerchantApi\Handler\Basket\PromoCodesEventHandler' + RELATED_PRODUCTS: '@izi\prestashop\MerchantApi\Handler\Basket\RelatedProductsEventHandler' + + izi\prestashop\CommandBus: + class: izi\prestashop\CommandBus + arguments: + - '@inpost.izi.command_handler_locator' + + inpost.izi.command_handler_locator: + class: Symfony\Component\DependencyInjection\ServiceLocator + tags: [ container.service_locator ] + arguments: + - izi\prestashop\Command\UnbindBasketCommand: '@izi\prestashop\Handler\UnbindBasketHandlerInterface' + izi\prestashop\Command\UpdateOrderTrackingNumbersCommand: '@izi\prestashop\Handler\UpdateOrderTrackingNumbersHandlerInterface' + izi\prestashop\Command\UpdateOrderStatusCommand: '@izi\prestashop\Handler\UpdateOrderStatusHandlerInterface' + izi\prestashop\Command\UpdateOrderAddressDeliveryCommand: '@izi\prestashop\Handler\UpdateOrderAddressDeliveryHandlerInterface' + izi\prestashop\Command\UpdateBasketCommand: '@izi\prestashop\Handler\UpdateBasketHandlerInterface' + izi\prestashop\Command\GetBasketBindingKeyCommand: '@izi\prestashop\Handler\GetBasketBindingKeyHandlerInterface' + izi\prestashop\Command\GetOrderConfirmationUrlCommand: '@izi\prestashop\Handler\GetOrderConfirmationUrlHandlerInterface' + izi\prestashop\MerchantApi\Command\ConfirmBasketBindingCommand: '@izi\prestashop\MerchantApi\Handler\ConfirmBasketBindingHandlerInterface' + izi\prestashop\MerchantApi\Command\DeleteBasketBindingCommand: '@izi\prestashop\MerchantApi\Handler\DeleteBasketBindingHandlerInterface' + izi\prestashop\MerchantApi\Command\GetBasketCommand: '@izi\prestashop\MerchantApi\Handler\GetBasketHandlerInterface' + izi\prestashop\MerchantApi\Command\UpdateBasketCommand: '@izi\prestashop\MerchantApi\Handler\UpdateBasketHandlerInterface' + izi\prestashop\MerchantApi\Command\GetProductsCommand: '@izi\prestashop\MerchantApi\Handler\GetProductsHandlerInterface' + izi\prestashop\MerchantApi\Command\AddProductToBasketCommand: '@izi\prestashop\MerchantApi\Handler\AddProductToBasketHandlerInterface' + izi\prestashop\MerchantApi\Command\CreateOrderCommand: '@izi\prestashop\MerchantApi\Handler\CreateOrderHandlerInterface' + izi\prestashop\MerchantApi\Command\GetOrderCommand: '@izi\prestashop\MerchantApi\Handler\GetOrderHandlerInterface' + izi\prestashop\MerchantApi\Command\UpdateOrderCommand: '@izi\prestashop\MerchantApi\Handler\UpdateOrderHandlerInterface' + izi\prestashop\MerchantApi\Command\Basket\CreateCartCommand: '@izi\prestashop\MerchantApi\Handler\Basket\CreateCartHandlerInterface' + izi\prestashop\MerchantApi\Command\Basket\AddProductToCartCommand: '@izi\prestashop\MerchantApi\Handler\Basket\AddProductToCartHandlerInterface' + izi\prestashop\MerchantApi\Command\Basket\IncrementCartQuantityCommand: '@izi\prestashop\MerchantApi\Handler\Basket\IncrementCartQuantityHandlerInterface' + izi\prestashop\MerchantApi\Command\Order\UpdateCartMessageCommand: '@izi\prestashop\MerchantApi\Handler\Order\UpdateCartMessageHandlerInterface' + izi\prestashop\HotProduct\Message\DeleteRemoteProductCommand: '@izi\prestashop\HotProduct\MessageHandler\DeleteRemoteProductHandlerInterface' + izi\prestashop\HotProduct\Message\UpdateHotProductCommand: '@izi\prestashop\HotProduct\MessageHandler\UpdateHotProductHandlerInterface' + izi\prestashop\Analytics\Command\UpdateCartAnalyticsCommand: '@izi\prestashop\Analytics\Handler\UpdateCartAnalyticsHandlerInterface' + + inpost.izi.widget_controller_locator: + class: Symfony\Component\DependencyInjection\ServiceLocator + tags: [ container.service_locator ] + arguments: + - Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface: '@inpost.izi.security.authorization_checker' diff --git a/modules/inpostizi/config/routes.yml b/modules/inpostizi/config/routes.yml new file mode 100644 index 00000000..1ebea1ed --- /dev/null +++ b/modules/inpostizi/config/routes.yml @@ -0,0 +1,4 @@ +inpost_izi_admin: + resource: ../src/Controller/Admin + type: annotation + prefix: /inpost/izi diff --git a/modules/inpostizi/config/services.yml b/modules/inpostizi/config/services.yml new file mode 100644 index 00000000..fe42a15c --- /dev/null +++ b/modules/inpostizi/config/services.yml @@ -0,0 +1,632 @@ +# config page configuration +imports: + - { resource: services/common.yml } + - { resource: services/common_admin.yml } + +services: + izi\prestashop\CommandBus: + class: izi\prestashop\CommandBus + public: false + autowire: true + tags: + - { name: container.service_subscriber } + + izi\prestashop\Controller\Admin\ConfigurationController: + class: izi\prestashop\Controller\Admin\ConfigurationController + public: false + autowire: true + tags: + - { name: controller.service_arguments } + - { name: container.service_subscriber } + arguments: + - '@izi\prestashop\Translation\LegacyTranslator' + - '@inpost.izi.context' + - !tagged inpost.izi.config_initializer + - '@izi\prestashop\Configuration\ApiConfigurationInterface' + - '%kernel.debug%' + calls: + - { method: setLogger, arguments: [ '@inpost.izi.general_logger' ] } + + izi\prestashop\Controller\Admin\HotProductController: + class: izi\prestashop\Controller\Admin\HotProductController + public: false + autowire: true + tags: + - { name: controller.service_arguments } + - { name: container.service_subscriber } + arguments: + - '@inpost.izi.shop_context' + - '@izi\prestashop\Translation\LegacyTranslator' + - '@inpost.izi.context' + - '@izi\prestashop\Configuration\ApiConfigurationInterface' + - !tagged inpost.izi.config_initializer + - '%kernel.debug%' + calls: + - { method: setLogger, arguments: [ '@inpost.izi.general_logger' ] } + + izi\prestashop\Form\Type\GeneralConfigurationType: + class: izi\prestashop\Form\Type\GeneralConfigurationType + tags: + - { name: form.type } + arguments: + - '@izi\prestashop\Translation\LegacyTranslator' + - '@izi\prestashop\Event\EventDispatcherInterface' + + izi\prestashop\Form\Type\ApiConfigurationType: + class: izi\prestashop\Form\Type\ApiConfigurationType + autowire: true + tags: + - { name: form.type } + + izi\prestashop\Form\Type\EnvironmentChoiceType: + class: izi\prestashop\Form\Type\EnvironmentChoiceType + autowire: true + tags: + - { name: form.type } + + izi\prestashop\Form\Type\ClientCredentialsType: + class: izi\prestashop\Form\Type\ClientCredentialsType + autowire: true + tags: + - { name: form.type } + + izi\prestashop\Form\Type\MaskedPasswordType: + class: izi\prestashop\Form\Type\MaskedPasswordType + tags: + - { name: form.type } + + izi\prestashop\Form\Type\OrdersConfigurationType: + class: izi\prestashop\Form\Type\OrdersConfigurationType + autowire: true + tags: + - { name: form.type } + + izi\prestashop\Form\Type\OrderStateChoiceType: + class: izi\prestashop\Form\Type\OrderStateChoiceType + tags: + - { name: form.type } + arguments: + - '@inpost.izi.context' + - '@izi\prestashop\Form\ChoiceList\OrderStateChoiceLoader' + + izi\prestashop\Form\Type\Order\AvailablePaymentOptionsChoiceType: + class: izi\prestashop\Form\Type\Order\AvailablePaymentOptionsChoiceType + tags: + - { name: form.type } + arguments: + - '@izi\prestashop\Form\ChoiceList\AvailablePaymentOptionChoiceLoader' + - '@izi\prestashop\Translation\PaymentTypeTranslator' + + izi\prestashop\Form\Type\Order\MessageOptionsType: + class: izi\prestashop\Form\Type\Order\MessageOptionsType + tags: + - { name: form.type } + arguments: + - '@izi\prestashop\Translation\LegacyTranslator' + - '@izi\prestashop\Order\Message\ParameterDescriptorInterface' + + izi\prestashop\Form\Type\ShippingConfigurationType: + class: izi\prestashop\Form\Type\ShippingConfigurationType + autowire: true + tags: + - { name: form.type } + + izi\prestashop\Form\Type\AdvancedConfigurationType: + class: izi\prestashop\Form\Type\AdvancedConfigurationType + autowire: true + tags: + - { name: form.type } + + izi\prestashop\Form\Type\Shipping\CarrierChoiceType: + class: izi\prestashop\Form\Type\Shipping\CarrierChoiceType + tags: + - { name: form.type } + arguments: + - '@izi\prestashop\Form\ChoiceList\CarrierChoiceLoader' + + izi\prestashop\Form\Type\Shipping\CarrierMappingsType: + class: izi\prestashop\Form\Type\Shipping\CarrierMappingsType + autowire: true + tags: + - { name: form.type } + + izi\prestashop\Form\Type\Shipping\CarrierMappingType: + class: izi\prestashop\Form\Type\Shipping\CarrierMappingType + tags: + - { name: form.type } + + izi\prestashop\Form\Type\Shipping\OptionalServicesType: + class: izi\prestashop\Form\Type\Shipping\OptionalServicesType + autowire: true + tags: + - { name: form.type } + + izi\prestashop\Form\Type\Shipping\ServiceOptionsType: + class: izi\prestashop\Form\Type\Shipping\ServiceOptionsType + tags: + - { name: form.type } + arguments: + - '@izi\prestashop\Translation\LegacyTranslator' + - '@inpost.izi.context' + + izi\prestashop\Form\Type\Shipping\ShippingOptionsType: + class: izi\prestashop\Form\Type\Shipping\ShippingOptionsType + tags: + - { name: form.type } + + izi\prestashop\Form\Type\Shipping\TimeOfWeekType: + class: izi\prestashop\Form\Type\Shipping\TimeOfWeekType + tags: + - { name: form.type } + + izi\prestashop\Form\Type\Shipping\TimeOfWeekRangeType: + class: izi\prestashop\Form\Type\Shipping\TimeOfWeekRangeType + autowire: true + tags: + - { name: form.type } + + izi\prestashop\Form\Type\Shipping\WeekDayChoiceType: + class: izi\prestashop\Form\Type\Shipping\WeekDayChoiceType + autowire: true + tags: + - { name: form.type } + + izi\prestashop\Form\Type\OrderStatusDescriptionMapType: + class: izi\prestashop\Form\Type\OrderStatusDescriptionMapType + tags: + - { name: form.type } + arguments: + - '@inpost.izi.context' + - '@izi\prestashop\Form\ChoiceList\OrderStateChoiceLoader' + + izi\prestashop\Form\Type\Image\ImageTypeChoiceType: + class: izi\prestashop\Form\Type\Image\ImageTypeChoiceType + tags: + - { name: form.type } + arguments: + - '@izi\prestashop\Form\ChoiceList\ProductImageTypeChoiceLoader' + + izi\prestashop\Form\Type\ProductConfigurationType: + class: izi\prestashop\Form\Type\ProductConfigurationType + autowire: true + tags: + - { name: form.type } + + izi\prestashop\Form\Type\ObjectModelAutocompleteType: + class: izi\prestashop\Form\Type\ObjectModelAutocompleteType + autowire: true + tags: + - { name: form.type } + + izi\prestashop\Form\Type\Product\ProductRestrictionsType: + class: izi\prestashop\Form\Type\Product\ProductRestrictionsType + tags: + - { name: form.type } + arguments: + - '@izi\prestashop\Translation\LegacyTranslator' + - '@inpost.izi.context' + + izi\prestashop\Form\Type\Widget\ProductPageDisplayConfigurationType: + class: izi\prestashop\Form\Type\Widget\ProductPageDisplayConfigurationType + tags: + - { name: form.type } + arguments: + - '@izi\prestashop\Translation\LegacyTranslator' + + izi\prestashop\Form\ChoiceList\OrderStateChoiceLoader: + class: izi\prestashop\Form\ChoiceList\OrderStateChoiceLoader + public: false + arguments: + - '@inpost.izi.object_model.order_state_repository' + + izi\prestashop\Form\ChoiceList\AvailablePaymentOptionChoiceLoader: + class: izi\prestashop\Form\ChoiceList\AvailablePaymentOptionChoiceLoader + public: false + autowire: true + + izi\prestashop\Form\ChoiceList\CarrierChoiceLoader: + class: izi\prestashop\Form\ChoiceList\CarrierChoiceLoader + public: false + arguments: + - '@izi\prestashop\ObjectModel\Repository\CarrierRepository' + + izi\prestashop\Form\ChoiceList\ProductImageTypeChoiceLoader: + class: izi\prestashop\Form\ChoiceList\ProductImageTypeChoiceLoader + public: false + arguments: + - '@izi\prestashop\ObjectModel\Repository\ImageTypeRepository' + + izi\prestashop\Handler\Config\UpdateGeneralConfigurationHandlerInterface: '@izi\prestashop\Handler\Config\UpdateGeneralConfigurationHandler' + izi\prestashop\Handler\Config\UpdateGeneralConfigurationHandler: + class: izi\prestashop\Handler\Config\UpdateGeneralConfigurationHandler + public: false + tags: + - { name: inpost.izi.command_handler } + arguments: + - '@izi\prestashop\Configuration\ApiConfigurationInterface' + - '@izi\prestashop\Configuration\OrdersConfigurationInterface' + - '@izi\prestashop\Configuration\GeneralConfigurationInterface' + - '@izi\prestashop\Configuration\ProductConfigurationInterface' + - '@izi\prestashop\CacheClearer\CacheClearerInterface' + - '@inpost.izi.module' + + izi\prestashop\BasketApp\BasketAppClientFactory: + class: izi\prestashop\BasketApp\BasketAppClientFactory + public: false + arguments: + - '@inpost.izi.psr17_factory' + - '@inpost.izi.psr17_factory' + - '@inpost.izi.serializer' + - '@izi\prestashop\Http\Client\Factory\GuzzleClientFactory' + - '@izi\prestashop\BasketApp\AuthorizationProviderFactory' + + izi\prestashop\Form\BasketAppClientProvider: + class: izi\prestashop\Form\BasketAppClientProvider + tags: + - { name: kernel.event_subscriber } + arguments: + - '@izi\prestashop\Configuration\ApiConfigurationInterface' + - '@izi\prestashop\BasketApp\BasketAppClientFactory' + - '@izi\prestashop\BasketApp\BasketAppClient' + + izi\prestashop\Validator\InPostApiCredentialsValidator: + class: izi\prestashop\Validator\InPostApiCredentialsValidator + tags: + - { name: validator.constraint_validator } + arguments: + - '@izi\prestashop\Translation\LegacyTranslator' + - '@izi\prestashop\BasketApp\AuthorizationProviderFactory' + + izi\prestashop\Validator\NotBlankInDefaultLanguageValidator: + class: izi\prestashop\Validator\NotBlankInDefaultLanguageValidator + tags: + - { name: validator.constraint_validator } + arguments: + - '@izi\prestashop\Configuration\Adapter\Configuration' + + izi\prestashop\Validator\ProcessableMessageFormatValidator: + class: izi\prestashop\Validator\ProcessableMessageFormatValidator + tags: + - { name: validator.constraint_validator } + arguments: + - '@izi\prestashop\Order\Message\MessageFormatterInterface' + - '@izi\prestashop\Translation\LegacyTranslator' + + izi\prestashop\Validator\Consent\DescriptionUsesIdPlaceholdersValidator: + class: izi\prestashop\Validator\Consent\DescriptionUsesIdPlaceholdersValidator + tags: + - { name: validator.constraint_validator } + arguments: + - '@izi\prestashop\Translation\LegacyTranslator' + + izi\prestashop\Validator\Consent\UniqueIdentifiersValidator: + class: izi\prestashop\Validator\Consent\UniqueIdentifiersValidator + tags: + - { name: validator.constraint_validator } + arguments: + - '@izi\prestashop\Translation\LegacyTranslator' + + izi\prestashop\Command\Config\UpdateGeneralConfigurationCommandFactory: + class: izi\prestashop\Command\Config\UpdateGeneralConfigurationCommandFactory + arguments: + - '@izi\prestashop\Configuration\ApiConfigurationInterface' + - '@izi\prestashop\Configuration\OrdersConfigurationInterface' + - '@izi\prestashop\Configuration\GeneralConfigurationInterface' + - '@izi\prestashop\Configuration\ProductConfigurationInterface' + + izi\prestashop\Form\Type\ConsentsConfigurationType: + class: izi\prestashop\Form\Type\ConsentsConfigurationType + autowire: true + tags: + - { name: form.type } + + izi\prestashop\Form\Type\Consent\ConsentType: + class: izi\prestashop\Form\Type\Consent\ConsentType + tags: + - { name: form.type } + arguments: + - '@izi\prestashop\Translation\LegacyTranslator' + + izi\prestashop\Form\Type\Consent\ConsentLinkType: + class: izi\prestashop\Form\Type\Consent\ConsentLinkType + tags: + - { name: form.type } + arguments: + - '@izi\prestashop\Translation\LegacyTranslator' + - '@inpost.izi.context' + - '@izi\prestashop\ObjectModel\Repository\CmsPageRepository' + + izi\prestashop\Form\Type\Consent\ConsentRequirementChoiceType: + class: izi\prestashop\Form\Type\Consent\ConsentRequirementChoiceType + autowire: true + tags: + - { name: form.type } + + izi\prestashop\Handler\Config\UpdateConsentsConfigurationHandlerInterface: '@izi\prestashop\Handler\Config\UpdateConsentsConfigurationHandler' + izi\prestashop\Handler\Config\UpdateConsentsConfigurationHandler: + class: izi\prestashop\Handler\Config\UpdateConsentsConfigurationHandler + public: false + tags: + - { name: inpost.izi.command_handler } + arguments: + - '@izi\prestashop\Configuration\ConsentsConfigurationInterface' + - '@inpost.izi.clock' + + izi\prestashop\Form\Type\GuiConfigurationType: + class: izi\prestashop\Form\Type\GuiConfigurationType + autowire: true + tags: + - { name: form.type } + + izi\prestashop\Form\Type\Widget\WidgetDisplayConfigurationType: + class: izi\prestashop\Form\Type\Widget\WidgetDisplayConfigurationType + autowire: true + tags: + - { name: form.type } + + izi\prestashop\Form\Type\Widget\HtmlStylesType: + class: izi\prestashop\Form\Type\Widget\HtmlStylesType + autowire: true + tags: + - { name: form.type } + + izi\prestashop\Form\Type\Widget\WidgetConfigurationType: + class: izi\prestashop\Form\Type\Widget\WidgetConfigurationType + autowire: true + tags: + - { name: form.type } + + izi\prestashop\Form\Type\Widget\WidgetVariantChoiceType: + class: izi\prestashop\Form\Type\Widget\WidgetVariantChoiceType + autowire: true + tags: + - { name: form.type } + + izi\prestashop\Form\Type\Widget\WidgetFrameStyleChoiceType: + class: izi\prestashop\Form\Type\Widget\WidgetFrameStyleChoiceType + autowire: true + tags: + - { name: form.type } + + izi\prestashop\Form\Type\Widget\WidgetSizeChoiceType: + class: izi\prestashop\Form\Type\Widget\WidgetSizeChoiceType + autowire: true + tags: + - { name: form.type } + + izi\prestashop\HotProduct\Form\UpdateHotProductType: + class: izi\prestashop\HotProduct\Form\UpdateHotProductType + autowire: true + tags: + - { name: form.type } + + izi\prestashop\HotProduct\Form\CreateHotProductType: + class: izi\prestashop\HotProduct\Form\CreateHotProductType + tags: + - { name: form.type } + arguments: + - '@inpost.izi.context' + - '@izi\prestashop\Translation\LegacyTranslator' + - '@router' + + izi\prestashop\Form\Type\Product\CombinationByAttributesChoiceType: + class: izi\prestashop\Form\Type\Product\CombinationByAttributesChoiceType + tags: + - { name: form.type } + arguments: + - '@izi\prestashop\ObjectModel\ObjectManagerInterface' + - '@inpost.izi.context' + - '@izi\prestashop\Translation\LegacyTranslator' + + izi\prestashop\Handler\Config\UpdateGuiConfigurationHandlerInterface: '@izi\prestashop\Handler\Config\UpdateGuiConfigurationHandler' + izi\prestashop\Handler\Config\UpdateGuiConfigurationHandler: + class: izi\prestashop\Handler\Config\UpdateGuiConfigurationHandler + public: false + tags: + - { name: inpost.izi.command_handler } + arguments: + - '@izi\prestashop\Configuration\GuiConfigurationInterface' + + izi\prestashop\Handler\Config\UpdateShippingConfigurationHandlerInterface: '@izi\prestashop\Handler\Config\UpdateShippingConfigurationHandler' + izi\prestashop\Handler\Config\UpdateShippingConfigurationHandler: + class: izi\prestashop\Handler\Config\UpdateShippingConfigurationHandler + public: false + autowire: true + tags: + - { name: inpost.izi.command_handler } + + izi\prestashop\Handler\Config\UpdateAdvancedConfigurationHandlerInterface: '@izi\prestashop\Handler\Config\UpdateAdvancedConfigurationHandler' + izi\prestashop\Handler\Config\UpdateAdvancedConfigurationHandler: + class: izi\prestashop\Handler\Config\UpdateAdvancedConfigurationHandler + public: false + autowire: true + tags: + - { name: inpost.izi.command_handler } + + izi\prestashop\Handler\Config\CheckStatusHandlerInterface: '@izi\prestashop\Handler\Config\CheckStatusHandler' + izi\prestashop\Handler\Config\CheckStatusHandler: + class: izi\prestashop\Handler\Config\CheckStatusHandler + public: false + tags: + - { name: inpost.izi.command_handler } + arguments: + - !tagged inpost.izi.status_checker + + izi\prestashop\Handler\Config\DownloadModuleDataHandlerInterface: '@izi\prestashop\Handler\Config\DownloadModuleDataHandler' + izi\prestashop\Handler\Config\DownloadModuleDataHandler: + class: izi\prestashop\Handler\Config\DownloadModuleDataHandler + public: false + tags: + - { name: inpost.izi.command_handler } + arguments: + - '@inpost.izi.module' + - '@inpost.izi.context' + - '@izi\prestashop\ObjectModel\ObjectManagerInterface' + - '%inpost.izi.logs_dir%' + + izi\prestashop\Twig\Extension\LegacyTranslationExtension: + class: izi\prestashop\Twig\Extension\LegacyTranslationExtension + public: false + tags: + - { name: inpost.izi.twig_extension } + arguments: + - '@izi\prestashop\Translation\LegacyTranslator' + + izi\prestashop\Handler\Config\Status\ConfigurationStatusChecker: + class: izi\prestashop\Handler\Config\Status\ConfigurationStatusChecker + public: false + tags: + - { name: inpost.izi.status_checker } + arguments: + - '@izi\prestashop\Translation\LegacyTranslator' + - '@izi\prestashop\Configuration\OrdersConfigurationInterface' + - '@izi\prestashop\Configuration\ApiConfigurationInterface' + - '@validator' + + izi\prestashop\Handler\Config\Status\DeliveryOptionsStatusChecker: + class: izi\prestashop\Handler\Config\Status\DeliveryOptionsStatusChecker + public: false + autowire: true + tags: + - { name: inpost.izi.status_checker } + arguments: + - '@izi\prestashop\Translation\LegacyTranslator' + - '@izi\prestashop\ObjectModel\Repository\CarrierRepository' + + izi\prestashop\Handler\Config\Status\CacheStatusChecker: + class: izi\prestashop\Handler\Config\Status\CacheStatusChecker + public: false + tags: + - { name: inpost.izi.status_checker } + arguments: + - '@inpost.izi.module' + - '@service_container' + + izi\prestashop\Translation\LegacyTranslator: + class: izi\prestashop\Translation\LegacyTranslator + public: false + arguments: + - inpostizi + + izi\prestashop\Translation\ServiceNameTranslator: + class: izi\prestashop\Translation\ServiceNameTranslator + public: false + autowire: true + + izi\prestashop\Translation\PaymentTypeTranslator: + class: izi\prestashop\Translation\PaymentTypeTranslator + public: false + autowire: true + + izi\prestashop\Configuration\Initializer\AssetPackageInitializer: + class: izi\prestashop\Configuration\Initializer\AssetPackageInitializer + public: false + tags: + - { name: inpost.izi.config_initializer } + arguments: + - '@assets.packages' + - '@izi\prestashop\View\Asset\AssetManagerInterface' + - InPostIzi + + izi\prestashop\Configuration\Initializer\AnnotationsConfigInitializer: + class: izi\prestashop\Configuration\Initializer\AnnotationsConfigInitializer + public: false + tags: + - { name: inpost.izi.config_initializer } + + izi\prestashop\Configuration\Initializer\TwigConfigInitializer: + class: izi\prestashop\Configuration\Initializer\TwigConfigInitializer + public: false + tags: + - { name: inpost.izi.config_initializer } + arguments: + - '@twig' + - !tagged inpost.izi.twig_extension + + izi\prestashop\Order\Message\ParameterDescriptorInterface: '@izi\prestashop\Order\Message\ParametersExtractor' + + izi\prestashop\Configuration\GuiConfiguration: + class: izi\prestashop\Configuration\GuiConfiguration + autowire: true + public: false + tags: + - { name: container.service_subscriber, key: context, id: inpost.izi.shop_context } + arguments: + - '@izi\prestashop\Configuration\Adapter\Configuration' + - '@inpost.izi.serializer' + + inpost.izi.security.authorization_checker: '@security.authorization_checker' + inpost.izi.validator: '@validator' + + izi\prestashop\CacheClearer\CacheClearerInterface: '@izi\prestashop\CacheClearer\ChainCacheClearer' + izi\prestashop\CacheClearer\ChainCacheClearer: + class: izi\prestashop\CacheClearer\ChainCacheClearer + public: false + arguments: + - !tagged inpost.izi.cache_clearer + + inpost.izi.config_cache_clearer: + class: izi\prestashop\CacheClearer\Psr16CacheClearer + public: false + tags: + - { name: inpost.izi.cache_clearer } + arguments: + - '@izi\prestashop\Cache\ConfigurationCache' + + izi\prestashop\CacheClearer\BindingKeysCacheClearer: + class: izi\prestashop\CacheClearer\BindingKeysCacheClearer + public: false + tags: + - { name: inpost.izi.cache_clearer } + arguments: + - '@izi\prestashop\Repository\BasketSessionRepository' + + izi\prestashop\HotProduct\MessageHandler\CreateHotProductHandlerInterface: '@izi\prestashop\HotProduct\MessageHandler\CreateHotProductHandler' + izi\prestashop\HotProduct\MessageHandler\CreateHotProductHandler: + class: izi\prestashop\HotProduct\MessageHandler\CreateHotProductHandler + public: false + tags: + - { name: inpost.izi.command_handler } + arguments: + - '@izi\prestashop\HotProduct\HotProductRepositoryInterface' + - '@izi\prestashop\HotProduct\HotProductValidator' + - '@izi\prestashop\HotProduct\HotProductDataMapperInterface' + - '@izi\prestashop\BasketApp\Product\ProductsApiClientInterface' + + izi\prestashop\HotProduct\MessageHandler\ImportHotProductHandlerInterface: '@izi\prestashop\HotProduct\MessageHandler\ImportHotProductHandler' + izi\prestashop\HotProduct\MessageHandler\ImportHotProductHandler: + class: izi\prestashop\HotProduct\MessageHandler\ImportHotProductHandler + public: false + tags: + - { name: inpost.izi.command_handler } + arguments: + - '@izi\prestashop\HotProduct\HotProductRepositoryInterface' + - '@izi\prestashop\ObjectModel\Repository\ProductRepository' + - '@izi\prestashop\BasketApp\Product\ProductsApiClientInterface' + - '@izi\prestashop\HotProduct\HotProductDataMapperInterface' + + izi\prestashop\HotProduct\MessageHandler\DeleteHotProductHandlerInterface: '@izi\prestashop\HotProduct\MessageHandler\DeleteHotProductHandler' + izi\prestashop\HotProduct\MessageHandler\DeleteHotProductHandler: + class: izi\prestashop\HotProduct\MessageHandler\DeleteHotProductHandler + public: false + tags: + - { name: inpost.izi.command_handler } + arguments: + - '@izi\prestashop\HotProduct\HotProductRepositoryInterface' + - '@izi\prestashop\BasketApp\Product\ProductsApiClientInterface' + + izi\prestashop\HotProduct\View\HotProductViewDataFactory: + class: izi\prestashop\HotProduct\View\HotProductViewDataFactory + arguments: + - '@inpost.izi.context' + - '@izi\prestashop\ObjectModel\Repository\ProductRepository' + - '@inpost.izi.object_model.shop_repository' + - '@izi\prestashop\BasketApp\Product\ProductsApiClientInterface' + + izi\prestashop\EventListener\CreateShipmentListener: + class: izi\prestashop\EventListener\CreateShipmentListener + tags: + - { name: kernel.event_subscriber } + arguments: + - '@izi\prestashop\Repository\OrderDataRepositoryInterface' + - '@izi\prestashop\Translation\LegacyTranslator' diff --git a/modules/inpostizi/config/services/admin/form.php b/modules/inpostizi/config/services/admin/form.php new file mode 100644 index 00000000..4d9bfce5 --- /dev/null +++ b/modules/inpostizi/config/services/admin/form.php @@ -0,0 +1,30 @@ +removeDefinition(DatePickerCompatibilityTypeExtension::class); + +foreach ($classes as $class) { + $container + ->getDefinition($class) + ->setTags([]) + ->setDeprecated(); +} diff --git a/modules/inpostizi/config/services/admin/form.yml b/modules/inpostizi/config/services/admin/form.yml new file mode 100644 index 00000000..5ba1d9ea --- /dev/null +++ b/modules/inpostizi/config/services/admin/form.yml @@ -0,0 +1,20 @@ +services: + izi\prestashop\Form\TypeExtension\DatePickerCompatibilityTypeExtension: + class: izi\prestashop\Form\TypeExtension\DatePickerCompatibilityTypeExtension + tags: + - { name: form.type_extension, extended_type: PrestaShopBundle\Form\Admin\Type\DatePickerType } + + izi\prestashop\Form\TypeExtension\DateTimeImmutableTimeTypeExtension: + class: izi\prestashop\Form\TypeExtension\DateTimeImmutableTimeTypeExtension + tags: + - { name: form.type_extension, extended_type: Symfony\Component\Form\Extension\Core\Type\TimeType } + + izi\prestashop\Form\TypeExtension\HelpTextExtension: + class: izi\prestashop\Form\TypeExtension\HelpTextExtension + tags: + - { name: form.type_extension, extended_type: Symfony\Component\Form\Extension\Core\Type\FormType } + + izi\prestashop\Form\TypeExtension\UnitTypeExtension: + class: izi\prestashop\Form\TypeExtension\UnitTypeExtension + tags: + - { name: form.type_extension, extended_type: Symfony\Component\Form\Extension\Core\Type\IntegerType } diff --git a/modules/inpostizi/config/services/common.yml b/modules/inpostizi/config/services/common.yml new file mode 100644 index 00000000..1499e0d4 --- /dev/null +++ b/modules/inpostizi/config/services/common.yml @@ -0,0 +1,1324 @@ +# common FO/BO configuration +parameters: + inpost.izi.container_version: '2.2.3' + inpost.izi.logs_dir: '%kernel.root_dir%/../var/logs/inpost' + inpost.izi.logger_config: + type: rotating_file + path: '%inpost.izi.logs_dir%/izi.log' + max_files: 14 + bubble: false + channels: [ general, basket_app, merchant_api ] + process_psr_3_messages: + remove_used_context_fields: true + include_stacktraces: true + + inpost.izi.basket_app_logger_options: + max_response_body_size: 1000 + +services: + inpost.izi.module: + class: InPostIzi + public: false + factory: [ Module, getInstanceByName ] + arguments: + - inpostizi + + inpost.izi.db: + class: Db + public: false + factory: [ Db, getInstance ] + + inpost.izi.context: + class: Context + public: false + factory: [ Context, getContext ] + + inpost.izi.clock: + class: Psr\Clock\ClockInterface + public: false + factory: [ izi\prestashop\Clock\SystemClock, fromSystemTimezone ] + + inpost.izi.serializer: + class: Symfony\Component\Serializer\SerializerInterface + public: false + factory: [ izi\prestashop\Serializer\SerializerFactory, create ] + + izi\prestashop\CommandBusInterface: + alias: izi\prestashop\CommandBus + public: true + + izi\prestashop\Hook\HookExecutorInterface: + alias: izi\prestashop\Hook\HookExecutor + public: true + + izi\prestashop\Hook\HookExecutor: + class: izi\prestashop\Hook\HookExecutor + arguments: + - '@?inpost.izi.hook_locator' + - '@inpost.izi.module' + + inpost.izi.psr17_factory: + class: Nyholm\Psr7\Factory\Psr17Factory + public: false + + izi\prestashop\Http\Client\Factory\GuzzleClientFactory: + class: izi\prestashop\Http\Client\Factory\GuzzleClientFactory + + inpost.izi.http_client.basket_app: + class: Psr\Http\Client\ClientInterface + factory: [ '@izi\prestashop\Http\Client\Factory\GuzzleClientFactory', create ] + + inpost.izi.http_client.basket_app.authorizing: + class: izi\prestashop\Http\Client\AuthorizingClient + public: false + decorates: inpost.izi.http_client.basket_app + arguments: + - '@inpost.izi.http_client.basket_app.authorizing.inner' + - '@inpost.izi.oauth2.auth_provider' + + inpost.izi.http_client.basket_app.logging: + class: izi\prestashop\Http\Client\LoggingClient + public: false + decorates: inpost.izi.http_client.basket_app + arguments: + - '@inpost.izi.http_client.basket_app.logging.inner' + - '@inpost.izi.basket_app_logger' + - '%inpost.izi.basket_app_logger_options%' + + inpost.izi.http_client.basket_app.module_version_info_providing: + class: izi\prestashop\Http\Client\ModuleVersionInfoProvidingClient + public: false + decorates: inpost.izi.http_client.basket_app + arguments: + - '@inpost.izi.http_client.basket_app.module_version_info_providing.inner' + - '@inpost.izi.module' + + izi\prestashop\BasketApp\BasketAppClientInterface: + alias: izi\prestashop\BasketApp\BasketAppClient + public: true + + izi\prestashop\BasketApp\BasketAppClient: + class: izi\prestashop\BasketApp\BasketAppClient + public: false + arguments: + - '@inpost.izi.http_client.basket_app' + - '@inpost.izi.psr17_factory' + - '@inpost.izi.psr17_factory' + - '@inpost.izi.serializer' + - '@=service("inpost.izi.environment").getBasketAppApiUri()' + + izi\prestashop\BasketApp\Basket\BasketsApiClientInterface: '@izi\prestashop\BasketApp\BasketAppClient' + izi\prestashop\BasketApp\Order\OrdersApiClientInterface: '@izi\prestashop\BasketApp\BasketAppClient' + izi\prestashop\BasketApp\Signature\SigningKeysApiClientInterface: '@izi\prestashop\BasketApp\BasketAppClient' + izi\prestashop\BasketApp\Payment\PaymentsApiClientInterface: '@izi\prestashop\BasketApp\BasketAppClient' + izi\prestashop\BasketApp\Product\ProductsApiClientInterface: '@izi\prestashop\BasketApp\BasketAppClient' + + izi\prestashop\Order\Address\AddressDataMapper: + class: izi\prestashop\Order\Address\AddressDataMapper + public: false + + izi\prestashop\Configuration\Adapter\Configuration: + class: izi\prestashop\Configuration\Adapter\Configuration + public: false + arguments: + - '@inpost.izi.db' + + izi\prestashop\Configuration\ApiConfigurationInterface: '@izi\prestashop\Configuration\ApiConfiguration' + izi\prestashop\Configuration\ApiConfiguration: + class: izi\prestashop\Configuration\ApiConfiguration + public: false + arguments: + - '@izi\prestashop\Configuration\Adapter\Configuration' + - '@inpost.izi.serializer' + - '@izi\prestashop\Environment\EnvironmentFactoryInterface' + + izi\prestashop\Configuration\OrdersConfigurationInterface: + alias: izi\prestashop\Configuration\OrdersConfiguration + public: true # todo: make private after refactoring basket data mapper and order creation handler + + izi\prestashop\Configuration\OrdersConfiguration: + class: izi\prestashop\Configuration\OrdersConfiguration + public: false + arguments: + - '@izi\prestashop\Configuration\Adapter\Configuration' + - '@inpost.izi.serializer' + + izi\prestashop\Configuration\GeneralConfigurationInterface: '@izi\prestashop\Configuration\GeneralConfiguration' + izi\prestashop\Configuration\PromoCodesConfigurationInterface: '@izi\prestashop\Configuration\GeneralConfiguration' + + izi\prestashop\Configuration\GeneralConfiguration: + class: izi\prestashop\Configuration\GeneralConfiguration + public: false + arguments: + - '@izi\prestashop\Configuration\Adapter\Configuration' + + izi\prestashop\Configuration\ProductConfigurationInterface: + alias: izi\prestashop\Configuration\ProductConfiguration + public: true # todo: make private after refactoring basket and order data mappers + + izi\prestashop\Configuration\ProductConfiguration: + class: izi\prestashop\Configuration\ProductConfiguration + public: false + arguments: + - '@izi\prestashop\Configuration\Adapter\Configuration' + + izi\prestashop\Configuration\ShippingConfigurationInterface: + alias: izi\prestashop\Configuration\ShippingConfiguration + public: true # todo: make private after refactoring order creation handler + + izi\prestashop\Configuration\ShippingConfiguration: + class: izi\prestashop\Configuration\ShippingConfiguration + public: false + arguments: + - '@izi\prestashop\Configuration\Adapter\Configuration' + - '@inpost.izi.serializer' + + izi\prestashop\Configuration\ConsentsConfigurationInterface: '@izi\prestashop\Configuration\ConsentsConfiguration' + izi\prestashop\Configuration\ConsentsConfiguration: + class: izi\prestashop\Configuration\ConsentsConfiguration + public: false + arguments: + - '@izi\prestashop\Configuration\Adapter\Configuration' + - '@inpost.izi.serializer' + + izi\prestashop\Configuration\AdvancedConfigurationInterface: + alias: izi\prestashop\Configuration\AdvancedConfiguration + public: true + + izi\prestashop\Configuration\AdvancedConfiguration: + class: izi\prestashop\Configuration\AdvancedConfiguration + public: false + arguments: + - '@izi\prestashop\Configuration\Adapter\Configuration' + + izi\prestashop\Configuration\ProductRestrictionsConfigurationInterface: '@izi\prestashop\Configuration\GuiConfiguration' + izi\prestashop\Configuration\GuiConfigurationInterface: '@izi\prestashop\Configuration\GuiConfiguration' + izi\prestashop\Configuration\GuiConfiguration: + class: izi\prestashop\Configuration\GuiConfiguration + public: false + arguments: + - '@izi\prestashop\Configuration\Adapter\Configuration' + - '@inpost.izi.serializer' + - '@inpost.izi.gui_configuration_locator' + + izi\prestashop\Environment\EnvironmentInterface: '@inpost.izi.environment' + inpost.izi.environment: + class: izi\prestashop\Environment\EnvironmentInterface + public: true + factory: [ '@izi\prestashop\Configuration\ApiConfiguration', getEnvironment ] + + izi\prestashop\Environment\AuthServerUriCollection: + class: izi\prestashop\Environment\AuthServerUriCollection + public: false + arguments: + - '@inpost.izi.environment' + + inpost.izi.oauth2.auth_provider: + class: izi\prestashop\OAuth2\LazyAuthorizationProvider + public: false + arguments: + - '@izi\prestashop\BasketApp\AuthorizationProviderFactory' + - '@izi\prestashop\Environment\AuthServerUriCollection' + - '@izi\prestashop\Configuration\ApiConfiguration' + - '@izi\prestashop\Configuration\ApiConfiguration' + + izi\prestashop\BasketApp\AuthorizationProviderFactory: + class: izi\prestashop\BasketApp\AuthorizationProviderFactory + public: false + arguments: + - '@inpost.izi.psr17_factory' + - '@inpost.izi.psr17_factory' + - '@izi\prestashop\Http\Client\Factory\GuzzleClientFactory' + + izi\prestashop\Log\MonologLoggerFactory: + class: izi\prestashop\Log\MonologLoggerFactory + public: false + arguments: + - + - '@izi\prestashop\Log\Handler\RotatingFileHandlerFactory' + + izi\prestashop\Log\Handler\RotatingFileHandlerFactory: + class: izi\prestashop\Log\Handler\RotatingFileHandlerFactory + public: false + arguments: + - '@=service("izi\\prestashop\\Configuration\\AdvancedConfigurationInterface").isDebugEnabled() ? "dev" : "prod"' + + inpost.izi.basket_app_logger: + class: Psr\Log\LoggerInterface + public: false + factory: [ '@izi\prestashop\Log\MonologLoggerFactory', create ] + arguments: + - basket_app + - '%inpost.izi.logger_config%' + + inpost.izi.merchant_api_logger: + class: Psr\Log\LoggerInterface + public: true # todo: make private after refactoring the front controller + factory: [ '@izi\prestashop\Log\MonologLoggerFactory', create ] + arguments: + - merchant_api + - '%inpost.izi.logger_config%' + + inpost.izi.general_logger: + class: Psr\Log\LoggerInterface + public: true + factory: [ '@izi\prestashop\Log\MonologLoggerFactory', create ] + arguments: + - general + - '%inpost.izi.logger_config%' + + izi\prestashop\Handler\UpdateOrderTrackingNumbersHandlerInterface: '@izi\prestashop\Handler\UpdateOrderTrackingNumbersHandler' + izi\prestashop\Handler\UpdateOrderTrackingNumbersHandler: + class: izi\prestashop\Handler\UpdateOrderTrackingNumbersHandler + public: false + tags: + - { name: inpost.izi.command_handler } + arguments: + - '@izi\prestashop\Repository\BasketSessionRepository' + - '@izi\prestashop\Builder\Order\OrderEventBuilderFactory' + - '@izi\prestashop\BasketApp\Order\OrdersApiClientInterface' + - !tagged inpost.izi.tracking_number_provider + + izi\prestashop\Handler\UpdateOrderStatusHandlerInterface: '@izi\prestashop\Handler\UpdateOrderStatusHandler' + izi\prestashop\Handler\UpdateOrderStatusHandler: + class: izi\prestashop\Handler\UpdateOrderStatusHandler + public: false + tags: + - { name: inpost.izi.command_handler } + arguments: + - '@izi\prestashop\Repository\BasketSessionRepository' + - '@izi\prestashop\Builder\Order\OrderEventBuilderFactory' + - '@izi\prestashop\BasketApp\Order\OrdersApiClientInterface' + - '@inpost.izi.general_logger' + + izi\prestashop\Handler\UpdateOrderAddressDeliveryHandlerInterface: '@izi\prestashop\Handler\UpdateOrderAddressDeliveryHandler' + izi\prestashop\Handler\UpdateOrderAddressDeliveryHandler: + class: izi\prestashop\Handler\UpdateOrderAddressDeliveryHandler + public: false + tags: + - { name: inpost.izi.command_handler } + arguments: + - '@izi\prestashop\Repository\BasketSessionRepository' + - '@izi\prestashop\Builder\Order\OrderEventBuilderFactory' + - '@izi\prestashop\BasketApp\Order\OrdersApiClientInterface' + - '@izi\prestashop\Order\Address\AddressDataMapper' + - '@izi\prestashop\ObjectModel\ObjectManager' + + izi\prestashop\Handler\UpdateBasketHandlerInterface: '@izi\prestashop\Handler\UpdateBasketHandler' + izi\prestashop\Handler\UpdateBasketHandler: + class: izi\prestashop\Handler\UpdateBasketHandler + public: false + tags: + - { name: inpost.izi.command_handler } + arguments: + - '@izi\prestashop\Repository\BasketSessionRepository' + - '@izi\prestashop\Builder\Basket\BasketBuilderFactory' + - '@izi\prestashop\BasketApp\Basket\BasketsApiClientInterface' + - '@inpost.izi.general_logger' + + izi\prestashop\Handler\UnbindBasketHandlerInterface: '@izi\prestashop\Handler\UnbindBasketHandler' + izi\prestashop\Handler\UnbindBasketHandler: + class: izi\prestashop\Handler\UnbindBasketHandler + public: false + tags: + - { name: inpost.izi.command_handler } + arguments: + - '@izi\prestashop\Repository\BasketSessionRepository' + - '@izi\prestashop\BasketApp\Basket\BasketsApiClientInterface' + + izi\prestashop\Shipping\CarrierModuleTrackingNumberProvider: + class: izi\prestashop\Shipping\CarrierModuleTrackingNumberProvider + public: false + tags: + - { name: inpost.izi.tracking_number_provider } + arguments: + - '@izi\prestashop\ObjectModel\ObjectManagerInterface' + + izi\prestashop\Builder\Order\OrderEventBuilderFactory: + class: izi\prestashop\Builder\Order\OrderEventBuilderFactory + public: false + arguments: + - '@inpost.izi.object_model.order_repository' + - '@izi\prestashop\Builder\Order\OrderStatusDescriptionProvider' + - '@inpost.izi.clock' + + izi\prestashop\Builder\Order\OrderStatusDescriptionProvider: + class: izi\prestashop\Builder\Order\OrderStatusDescriptionProvider + public: false + arguments: + - '@inpost.izi.object_model.order_state_repository' + - '@izi\prestashop\Configuration\OrdersConfiguration' + + izi\prestashop\Database\Connection: + class: izi\prestashop\Database\Connection + public: true + arguments: + - '@inpost.izi.db' + + izi\prestashop\ObjectModel\Hydrator: + class: izi\prestashop\ObjectModel\Hydrator + public: false + + izi\prestashop\ObjectModel\ObjectManagerInterface: + alias: izi\prestashop\ObjectModel\ObjectManager + public: true + + izi\prestashop\ObjectModel\ObjectManager: + class: izi\prestashop\ObjectModel\ObjectManager + public: true + arguments: + - '@izi\prestashop\Database\Connection' + - '@izi\prestashop\ObjectModel\Repository\ObjectRepositoryFactory' + - '@izi\prestashop\ObjectModel\Hydrator' + + izi\prestashop\ObjectModel\Repository\ObjectRepositoryFactory: + class: izi\prestashop\ObjectModel\Repository\ObjectRepositoryFactory + public: false + arguments: + - '@?inpost.izi.repository_locator' + + izi\prestashop\ObjectModel\Repository\CmsPageRepository: + class: izi\prestashop\ObjectModel\Repository\CmsPageRepository + public: false + arguments: + - '@izi\prestashop\ObjectModel\ObjectManagerInterface' + + izi\prestashop\ObjectModel\Repository\CurrencyRepository: + class: izi\prestashop\ObjectModel\Repository\CurrencyRepository + public: false + tags: + - { name: inpost.izi.model_repository, model_class: Currency } + arguments: + - '@izi\prestashop\ObjectModel\ObjectManagerInterface' + + izi\prestashop\ObjectModel\Repository\CarrierRepository: + class: izi\prestashop\ObjectModel\Repository\CarrierRepository + public: false + tags: + - { name: inpost.izi.model_repository, model_class: Carrier } + arguments: + - '@izi\prestashop\ObjectModel\ObjectManagerInterface' + + izi\prestashop\ObjectModel\Repository\CartRuleRepository: + class: izi\prestashop\ObjectModel\Repository\CartRuleRepository + public: false + tags: + - { name: inpost.izi.model_repository, model_class: CartRule } + arguments: + - '@izi\prestashop\ObjectModel\ObjectManagerInterface' + + izi\prestashop\ObjectModel\Repository\RangePriceRepository: + class: izi\prestashop\ObjectModel\Repository\RangePriceRepository + public: false + tags: + - { name: inpost.izi.model_repository, model_class: RangePrice } + arguments: + - '@izi\prestashop\ObjectModel\ObjectManagerInterface' + + izi\prestashop\ObjectModel\Repository\RangeWeightRepository: + class: izi\prestashop\ObjectModel\Repository\RangeWeightRepository + public: false + tags: + - { name: inpost.izi.model_repository, model_class: RangeWeight } + arguments: + - '@izi\prestashop\ObjectModel\ObjectManagerInterface' + + izi\prestashop\ObjectModel\Repository\ProductRepository: + class: izi\prestashop\ObjectModel\Repository\ProductRepository + public: false + tags: + - { name: inpost.izi.model_repository, model_class: Product } + arguments: + - '@izi\prestashop\ObjectModel\ObjectManagerInterface' + + + izi\prestashop\ObjectModel\Repository\CombinationRepository: + class: izi\prestashop\ObjectModel\Repository\CombinationRepository + public: false + tags: + - { name: inpost.izi.model_repository, model_class: Combination } + arguments: + - '@izi\prestashop\ObjectModel\ObjectManagerInterface' + + izi\prestashop\ObjectModel\Repository\ConfigurationRepository: + class: izi\prestashop\ObjectModel\Repository\ConfigurationRepository + public: false + tags: + - { name: inpost.izi.model_repository, model_class: Configuration } + arguments: + - '@izi\prestashop\ObjectModel\ObjectManagerInterface' + + izi\prestashop\ObjectModel\Repository\HookRepository: + class: izi\prestashop\ObjectModel\Repository\HookRepository + public: false + tags: + - { name: inpost.izi.model_repository, model_class: Hook } + arguments: + - '@izi\prestashop\ObjectModel\ObjectManagerInterface' + + izi\prestashop\ObjectModel\Repository\ShipmentRepository: + class: izi\prestashop\ObjectModel\Repository\ShipmentRepository + public: false + tags: + - { name: inpost.izi.model_repository, model_class: InPostShipmentModel } + arguments: + - '@izi\prestashop\ObjectModel\ObjectManagerInterface' + + izi\prestashop\ObjectModel\Repository\ImageTypeRepository: + class: izi\prestashop\ObjectModel\Repository\ImageTypeRepository + public: false + tags: + - { name: inpost.izi.model_repository, model_class: ImageType } + arguments: + - '@izi\prestashop\ObjectModel\ObjectManagerInterface' + + inpost.izi.object_model.language_repository: + class: izi\prestashop\ObjectModel\Repository\ObjectRepository + public: false + tags: + - { name: inpost.izi.model_repository, model_class: Language } + arguments: + - Language + - '@izi\prestashop\ObjectModel\ObjectManagerInterface' + + inpost.izi.object_model.order_repository: + class: izi\prestashop\ObjectModel\Repository\ObjectRepository + public: false + tags: + - { name: inpost.izi.model_repository, model_class: Order } + arguments: + - Order + - '@izi\prestashop\ObjectModel\ObjectManagerInterface' + + inpost.izi.object_model.order_state_repository: + class: izi\prestashop\ObjectModel\Repository\ObjectRepository + public: false + tags: + - { name: inpost.izi.model_repository, model_class: OrderState } + arguments: + - OrderState + - '@izi\prestashop\ObjectModel\ObjectManagerInterface' + + inpost.izi.object_model.cart_repository: + class: izi\prestashop\ObjectModel\Repository\ObjectRepository + public: false + tags: + - { name: inpost.izi.model_repository, model_class: Cart } + arguments: + - Cart + - '@izi\prestashop\ObjectModel\ObjectManagerInterface' + + inpost.izi.object_model.country_repository: + class: izi\prestashop\ObjectModel\Repository\ObjectRepository + public: false + tags: + - { name: inpost.izi.model_repository, model_class: Country } + arguments: + - Country + - '@izi\prestashop\ObjectModel\ObjectManagerInterface' + + inpost.izi.object_model.address_repository: + class: izi\prestashop\ObjectModel\Repository\ObjectRepository + public: false + tags: + - { name: inpost.izi.model_repository, model_class: Address } + arguments: + - Address + - '@izi\prestashop\ObjectModel\ObjectManagerInterface' + + inpost.izi.object_model.shop_repository: + class: izi\prestashop\ObjectModel\Repository\ObjectRepository + public: false + tags: + - { name: inpost.izi.model_repository, model_class: Shop } + arguments: + - Shop + - '@izi\prestashop\ObjectModel\ObjectManagerInterface' + + izi\prestashop\Repository\OrderDataRepositoryInterface: '@izi\prestashop\Repository\BasketSessionRepository' + izi\prestashop\Repository\BasketSessionRepository: + class: izi\prestashop\Repository\BasketSessionRepository + public: false + arguments: + - '@inpost.izi.serializer' + - '@izi\prestashop\ObjectModel\ObjectManagerInterface' + + izi\prestashop\Repository\Product\CategoryRestrictionsRepositoryInterface: '@izi\prestashop\Repository\ProductRestrictionsRepository' + izi\prestashop\Repository\Product\ManufacturerRestrictionsRepositoryInterface: '@izi\prestashop\Repository\ProductRestrictionsRepository' + izi\prestashop\Repository\Product\AttributeRestrictionsRepositoryInterface: '@izi\prestashop\Repository\ProductRestrictionsRepository' + izi\prestashop\Repository\Product\FeatureRestrictionsRepositoryInterface: '@izi\prestashop\Repository\ProductRestrictionsRepository' + izi\prestashop\Repository\ProductRestrictionsRepositoryInterface: '@izi\prestashop\Repository\ProductRestrictionsRepository' + izi\prestashop\Repository\ProductRestrictionsRepository: + class: izi\prestashop\Repository\ProductRestrictionsRepository + tags: + - { name: inpost.izi.gui_configuration_dependency, key: izi\prestashop\Repository\ProductRestrictionsRepositoryInterface } + arguments: + - '@izi\prestashop\Database\Connection' + + izi\prestashop\PromoCode\CartRuleOptionsRepositoryInterface: '@izi\prestashop\PromoCode\CartRuleOptionsRepository' + izi\prestashop\Repository\CartRuleRepositoryInterface: '@izi\prestashop\PromoCode\CartRuleOptionsRepository' + + izi\prestashop\PromoCode\CartRuleOptionsRepository: + class: izi\prestashop\PromoCode\CartRuleOptionsRepository + public: false + arguments: + - '@izi\prestashop\Database\Connection' + - '@izi\prestashop\Configuration\Adapter\Configuration' + + izi\prestashop\Repository\CartRuleRepository: + class: izi\prestashop\Repository\CartRuleRepository + public: false + arguments: + - '@izi\prestashop\Database\Connection' + - '@izi\prestashop\Configuration\Adapter\Configuration' + deprecated: The "%service_id%" service is deprecated since version 2.1, use "izi\prestashop\PromoCode\CartRuleOptionsRepositoryInterface" instead. + + izi\prestashop\HotProduct\HotProductRepositoryInterface: '@izi\prestashop\HotProduct\HotProductRepository' + izi\prestashop\HotProduct\HotProductRepository: + class: izi\prestashop\HotProduct\HotProductRepository + public: false + arguments: + - '@izi\prestashop\Database\Connection' + - '@inpost.izi.clock' + + izi\prestashop\Hook\Common\ActionCartDeleteBefore: + class: izi\prestashop\Hook\Common\ActionCartDeleteBefore + public: false + tags: + - { name: inpost.izi.hook } + arguments: + - '@izi\prestashop\CommandBusInterface' + + izi\prestashop\Hook\Common\ActionCartUpdateAfter: + class: izi\prestashop\Hook\Common\ActionCartUpdateAfter + public: false + tags: + - { name: inpost.izi.hook } + arguments: + - '@inpost.izi.module' + - '@inpost.izi.context' + - '@izi\prestashop\Event\EventDispatcherInterface' + + # for whatever reason Sf 3.4 might try and fail to autowire this service in the admin container + izi\prestashop\Hook\Front\ActionCartControllerAjaxUpdateResponse: + class: izi\prestashop\Hook\Front\ActionCartControllerAjaxUpdateResponse + public: false + tags: + - { name: inpost.izi.hook } + arguments: + - '@inpost.izi.context' + - '@izi\prestashop\Event\EventDispatcherInterface' + + izi\prestashop\Hook\Common\ActionEmailSendBefore: + class: izi\prestashop\Hook\Common\ActionEmailSendBefore + public: false + tags: + - { name: inpost.izi.hook } + arguments: + - '@izi\prestashop\Event\EventDispatcherInterface' + + izi\prestashop\Hook\Common\ActionShipmentAddAfter: + class: izi\prestashop\Hook\Common\ActionShipmentAddAfter + public: false + tags: + - { name: inpost.izi.hook } + arguments: + - '@izi\prestashop\Event\EventDispatcherInterface' + + izi\prestashop\Hook\Common\ActionShipmentUpdateBefore: + class: izi\prestashop\Hook\Common\ActionShipmentUpdateBefore + public: false + tags: + - { name: inpost.izi.hook } + arguments: + - '@izi\prestashop\Event\EventDispatcherInterface' + + izi\prestashop\Hook\Common\ActionShipmentUpdateAfter: + class: izi\prestashop\Hook\Common\ActionShipmentUpdateAfter + public: false + tags: + - { name: inpost.izi.hook } + arguments: + - '@izi\prestashop\Event\EventDispatcherInterface' + + izi\prestashop\Hook\Common\ActionValidateOrder: + class: izi\prestashop\Hook\Common\ActionValidateOrder + public: false + tags: + - { name: inpost.izi.hook } + arguments: + - '@inpost.izi.module' + - '@izi\prestashop\CommandBusInterface' + - '@izi\prestashop\Event\EventDispatcherInterface' + + izi\prestashop\Hook\Common\ActionOrderStatusPostUpdate: + class: izi\prestashop\Hook\Common\ActionOrderStatusPostUpdate + public: false + tags: + - { name: inpost.izi.hook } + arguments: + - '@inpost.izi.module' + - '@inpost.izi.context' + - '@izi\prestashop\Event\EventDispatcherInterface' + + izi\prestashop\Hook\Common\ActionObjectOrderUpdateBefore: + class: izi\prestashop\Hook\Common\ActionObjectOrderUpdateBefore + public: false + tags: + - { name: inpost.izi.hook } + arguments: + - '@izi\prestashop\Event\EventDispatcherInterface' + - '@inpost.izi.context' + + izi\prestashop\Hook\Common\ActionObjectOrderUpdateAfter: + class: izi\prestashop\Hook\Common\ActionObjectOrderUpdateAfter + public: false + tags: + - { name: inpost.izi.hook } + arguments: + - '@izi\prestashop\Event\EventDispatcherInterface' + - '@inpost.izi.context' + + # Product hooks + izi\prestashop\Hook\Common\Product\ActionProductDeleteBefore: + class: izi\prestashop\Hook\Common\Product\ActionProductDeleteBefore + public: false + arguments: + - '@izi\prestashop\Event\EventDispatcherInterface' + tags: + - { name: inpost.izi.hook } + + izi\prestashop\Hook\Common\Product\ActionProductDeleteAfter: + class: izi\prestashop\Hook\Common\Product\ActionProductDeleteAfter + public: false + arguments: + - '@izi\prestashop\Event\EventDispatcherInterface' + tags: + - { name: inpost.izi.hook } + + izi\prestashop\Hook\Common\Product\ActionProductUpdateAfter: + class: izi\prestashop\Hook\Common\Product\ActionProductUpdateAfter + public: false + tags: + - { name: inpost.izi.hook } + arguments: + - '@izi\prestashop\Event\EventDispatcherInterface' + + izi\prestashop\Hook\Common\Product\ActionCombinationDeleteBefore: + class: izi\prestashop\Hook\Common\Product\ActionCombinationDeleteBefore + public: false + tags: + - { name: inpost.izi.hook } + arguments: + - '@izi\prestashop\Event\EventDispatcherInterface' + + izi\prestashop\Hook\Common\Product\ActionCombinationDeleteAfter: + class: izi\prestashop\Hook\Common\Product\ActionCombinationDeleteAfter + public: false + tags: + - { name: inpost.izi.hook } + arguments: + - '@izi\prestashop\Event\EventDispatcherInterface' + + izi\prestashop\Hook\Common\Product\ActionCombinationUpdateAfter: + class: izi\prestashop\Hook\Common\Product\ActionCombinationUpdateAfter + public: false + tags: + - { name: inpost.izi.hook } + arguments: + - '@izi\prestashop\Event\EventDispatcherInterface' + + izi\prestashop\Hook\Common\Product\ActionImageAddAfter: + class: izi\prestashop\Hook\Common\Product\ActionImageAddAfter + public: false + tags: + - { name: inpost.izi.hook } + arguments: + - '@izi\prestashop\Event\EventDispatcherInterface' + + izi\prestashop\Hook\Common\Product\ActionImageDeleteAfter: + class: izi\prestashop\Hook\Common\Product\ActionImageDeleteAfter + public: false + tags: + - { name: inpost.izi.hook } + arguments: + - '@izi\prestashop\Event\EventDispatcherInterface' + + izi\prestashop\Hook\Common\Product\ActionSpecificPriceAddAfter: + class: izi\prestashop\Hook\Common\Product\ActionSpecificPriceAddAfter + public: false + tags: + - { name: inpost.izi.hook } + arguments: + - '@izi\prestashop\Event\EventDispatcherInterface' + + izi\prestashop\Hook\Common\Product\ActionSpecificPriceUpdateAfter: + class: izi\prestashop\Hook\Common\Product\ActionSpecificPriceUpdateAfter + public: false + tags: + - { name: inpost.izi.hook } + arguments: + - '@izi\prestashop\Event\EventDispatcherInterface' + + izi\prestashop\Hook\Common\Product\ActionSpecificPriceDeleteAfter: + class: izi\prestashop\Hook\Common\Product\ActionSpecificPriceDeleteAfter + public: false + tags: + - { name: inpost.izi.hook } + arguments: + - '@izi\prestashop\Event\EventDispatcherInterface' + + izi\prestashop\Hook\Common\Product\ActionUpdateQuantity: + class: izi\prestashop\Hook\Common\Product\ActionUpdateQuantity + public: false + tags: + - { name: inpost.izi.hook } + arguments: + - '@izi\prestashop\Event\EventDispatcherInterface' + - '@inpost.izi.context' + + izi\prestashop\EventListener\ShipmentListener: + class: izi\prestashop\EventListener\ShipmentListener + tags: + - { name: kernel.event_subscriber } + arguments: + - '@izi\prestashop\Configuration\ApiConfigurationInterface' + - '@izi\prestashop\ObjectModel\Repository\ShipmentRepository' + - '@izi\prestashop\CommandBusInterface' + - '@inpost.izi.general_logger' + + izi\prestashop\Payment\PaymentCurrencyChecker: + class: izi\prestashop\Payment\PaymentCurrencyChecker + public: false + + izi\prestashop\EventListener\CartListener: + class: izi\prestashop\EventListener\CartListener + tags: + - { name: kernel.event_subscriber } + arguments: + - '@izi\prestashop\Configuration\ApiConfigurationInterface' + - '@inpost.izi.context' + - '@izi\prestashop\Repository\BasketSessionRepository' + - '@izi\prestashop\CommandBusInterface' + - '@inpost.izi.general_logger' + + izi\prestashop\EventListener\OrderListener: + class: izi\prestashop\EventListener\OrderListener + tags: + - { name: kernel.event_subscriber } + arguments: + - '@izi\prestashop\Configuration\ApiConfigurationInterface' + - '@inpost.izi.object_model.order_repository' + - '@izi\prestashop\CommandBusInterface' + - '@inpost.izi.general_logger' + + izi\prestashop\MerchantApi\EventListener\UpdateCartRulesListener: + class: izi\prestashop\MerchantApi\EventListener\UpdateCartRulesListener + tags: + - { name: kernel.event_subscriber } + arguments: + - '@inpost.izi.context' + + izi\prestashop\View\Templating\RendererInterface: '@izi\prestashop\View\Templating\SmartyRenderer' + izi\prestashop\View\Templating\SmartyRenderer: + class: izi\prestashop\View\Templating\SmartyRenderer + public: false + arguments: + - '@=service("inpost.izi.context").smarty' + + izi\prestashop\ContextManager: + class: izi\prestashop\ContextManager + public: false + arguments: + - '@inpost.izi.context' + - '@izi\prestashop\ObjectModel\ObjectManagerInterface' + - '@izi\prestashop\Configuration\PrestaShopConfiguration' + + inpost.izi.widget: + class: izi\prestashop\Hook\Widget + public: true + arguments: + - '@izi\prestashop\View\Templating\RendererInterface' + - '@izi\prestashop\Hook\WidgetParametersProviderInterface' + + izi\prestashop\Hook\WidgetParametersProviderInterface: '@izi\prestashop\Hook\WidgetParametersProvider' + izi\prestashop\Hook\WidgetParametersProvider: + class: izi\prestashop\Hook\WidgetParametersProvider + public: false + arguments: + - '@izi\prestashop\Configuration\ApiConfigurationInterface' + - '@?inpost.izi.security.authorization_checker' + - '@izi\prestashop\View\Widget\WidgetConfigurationResolverInterface' + - '@izi\prestashop\Repository\BasketSessionRepository' + - '@?inpost.izi.validator' + + izi\prestashop\View\Widget\WidgetConfigurationResolverInterface: '@izi\prestashop\View\Widget\WidgetConfigurationResolver' + izi\prestashop\View\Widget\WidgetConfigurationResolver: + class: izi\prestashop\View\Widget\WidgetConfigurationResolver + public: false + + izi\prestashop\Builder\Basket\BasketBuilderFactory: + class: izi\prestashop\Builder\Basket\BasketBuilderFactory + public: false + arguments: + - '@inpost.izi.clock' + - '@izi\prestashop\ContextManager' + - '@izi\prestashop\Configuration\ConsentsConfiguration' + - '@izi\prestashop\Configuration\ProductConfiguration' + - '@izi\prestashop\Builder\Basket\DeliveryFactory' + - '@izi\prestashop\Builder\Basket\ProductDeliveryFactory' + - '@izi\prestashop\Product\Price\LowestPriceProviderInterface' + - '@izi\prestashop\PromoCode\PromoCodeProviderInterface' + - '@izi\prestashop\PromoCode\AvailablePromotionsProviderInterface' + - '@inpost.izi.validator' + + izi\prestashop\Cache\ConfigurationCache: + class: izi\prestashop\Cache\ConfigurationCache + public: false + arguments: + - '@izi\prestashop\Configuration\Adapter\Configuration' + - '@inpost.izi.serializer' + - '@inpost.izi.clock' + + izi\prestashop\Event\EventDispatcherInterface: + alias: izi\prestashop\Event\Adapter\EventDispatcher + public: true + + izi\prestashop\Event\Adapter\EventDispatcher: + class: izi\prestashop\Event\Adapter\EventDispatcher + public: false + arguments: + - '@?inpost.izi.event_dispatcher' + + izi\prestashop\Builder\Basket\DeliveryFactory: + class: izi\prestashop\Builder\Basket\DeliveryFactory + public: false + arguments: + - '@izi\prestashop\Configuration\ShippingConfigurationInterface' + - '@izi\prestashop\ObjectModel\Repository\CarrierRepository' + - '@inpost.izi.clock' + - '@izi\prestashop\Translation\ServiceNameTranslator' + - '@izi\prestashop\Shipping\DeliveryPriceCalculator' + + izi\prestashop\Builder\Basket\ProductDeliveryFactory: + class: izi\prestashop\Builder\Basket\ProductDeliveryFactory + public: false + arguments: + - '@izi\prestashop\Configuration\ShippingConfigurationInterface' + - '@izi\prestashop\ObjectModel\Repository\CarrierRepository' + - '@izi\prestashop\Shipping\CartTotal\CartTotalDeliveryStrategyInterface' + - '@izi\prestashop\Shipping\CartWeight\CartWeightDeliveryStrategyInterface' + - '@izi\prestashop\Shipping\ProductDimensions\ProductDimensionsDeliveryStrategyInterface' + - '@izi\prestashop\Shipping\ProductRestriction\ProductRestrictionDeliveryInterface' + + izi\prestashop\Shipping\DeliveryPriceCalculator: + class: izi\prestashop\Shipping\DeliveryPriceCalculator + public: false + arguments: + - '@izi\prestashop\Configuration\PrestaShopConfiguration' + - '@inpost.izi.object_model.address_repository' + - '@izi\prestashop\Currency\PriceConverterInterface' + - '@izi\prestashop\Shipping\FreeDelivery\MinAmountCalculationStrategyInterface' + + izi\prestashop\Currency\PriceConverterInterface: '@izi\prestashop\Currency\PriceConverter' + izi\prestashop\Currency\PriceConverter: + class: izi\prestashop\Currency\PriceConverter + public: false + arguments: + - '@izi\prestashop\ObjectModel\Repository\CurrencyRepository' + - '@izi\prestashop\Configuration\PrestaShopConfiguration' + + izi\prestashop\Shipping\FreeDelivery\GenericStrategy: + class: izi\prestashop\Shipping\FreeDelivery\GenericStrategy + public: false + arguments: + - '@izi\prestashop\Configuration\PrestaShopConfiguration' + + izi\prestashop\Shipping\FreeDelivery\MinAmountCalculationStrategyInterface: '@izi\prestashop\Shipping\FreeDelivery\PriceRangeStrategy' + izi\prestashop\Shipping\FreeDelivery\PriceRangeStrategy: + class: izi\prestashop\Shipping\FreeDelivery\PriceRangeStrategy + public: false + arguments: + - '@izi\prestashop\Shipping\FreeDelivery\GenericStrategy' + - '@izi\prestashop\Configuration\PrestaShopConfiguration' + - '@izi\prestashop\ObjectModel\ObjectManagerInterface' + + izi\prestashop\Shipping\CartTotal\GenericStrategy: + class: izi\prestashop\Shipping\CartTotal\GenericStrategy + public: false + + izi\prestashop\Shipping\CartTotal\PriceRangeStrategy: + class: izi\prestashop\Shipping\CartTotal\PriceRangeStrategy + public: false + arguments: + - '@izi\prestashop\Shipping\CartTotal\GenericStrategy' + - '@izi\prestashop\ObjectModel\Repository\RangePriceRepository' + + izi\prestashop\Shipping\CartTotal\CartTotalDeliveryStrategyInterface: '@izi\prestashop\Shipping\CartTotal\PriceRangeStrategy' + + izi\prestashop\Shipping\CartWeight\GenericStrategy: + class: izi\prestashop\Shipping\CartWeight\GenericStrategy + public: false + + izi\prestashop\Shipping\CartWeight\WeightRangeStrategy: + class: izi\prestashop\Shipping\CartWeight\WeightRangeStrategy + public: false + arguments: + - '@izi\prestashop\Shipping\CartWeight\GenericStrategy' + - '@izi\prestashop\ObjectModel\Repository\RangeWeightRepository' + + izi\prestashop\Shipping\CartWeight\CartWeightDeliveryStrategyInterface: '@izi\prestashop\Shipping\CartWeight\WeightRangeStrategy' + + izi\prestashop\Shipping\ProductDimensions\GenericStrategy: + class: izi\prestashop\Shipping\ProductDimensions\GenericStrategy + public: false + + izi\prestashop\Shipping\ProductDimensions\ProductDimensionsDeliveryStrategyInterface: '@izi\prestashop\Shipping\ProductDimensions\GenericStrategy' + + izi\prestashop\Shipping\ProductRestriction\ProductRestrictionDelivery: + class: izi\prestashop\Shipping\ProductRestriction\ProductRestrictionDelivery + public: false + + izi\prestashop\Shipping\ProductRestriction\ProductRestrictionDeliveryInterface: '@izi\prestashop\Shipping\ProductRestriction\ProductRestrictionDelivery' + + izi\prestashop\Translation\LegacyTranslator: + class: izi\prestashop\Translation\LegacyTranslator + arguments: + - inpostizi + + izi\prestashop\Translation\ServiceNameTranslator: + class: izi\prestashop\Translation\ServiceNameTranslator + arguments: + - '@izi\prestashop\Translation\LegacyTranslator' + + izi\prestashop\Order\Message\MessageFormatterInterface: '@izi\prestashop\Order\Message\MessageFormatter' + izi\prestashop\Order\Message\MessageFormatter: + class: izi\prestashop\Order\Message\MessageFormatter + public: false + arguments: + - '@izi\prestashop\Order\Message\ParametersExtractorInterface' + - !tagged inpost.izi.order_message_processor + + izi\prestashop\Order\Message\ExpressionLanguage: + class: izi\prestashop\Order\Message\ExpressionLanguage + public: false + + izi\prestashop\Order\Message\ParametersExtractorInterface: '@izi\prestashop\Order\Message\ParametersExtractor' + izi\prestashop\Order\Message\ParametersExtractor: + class: izi\prestashop\Order\Message\ParametersExtractor + public: false + arguments: + - '@izi\prestashop\Translation\LegacyTranslator' + + izi\prestashop\Order\Message\Processor\ConditionalBlockProcessor: + class: izi\prestashop\Order\Message\Processor\ConditionalBlockProcessor + public: false + tags: + - { name: inpost.izi.order_message_processor, priority: 100 } + arguments: + - '@izi\prestashop\Order\Message\ExpressionLanguage' + + izi\prestashop\Order\Message\Processor\ExpressionLanguageProcessor: + class: izi\prestashop\Order\Message\Processor\ExpressionLanguageProcessor + public: false + tags: + - { name: inpost.izi.order_message_processor, priority: 50 } + arguments: + - '@izi\prestashop\Order\Message\ExpressionLanguage' + + izi\prestashop\Order\Message\Processor\ParameterReplacementProcessor: + class: izi\prestashop\Order\Message\Processor\ParameterReplacementProcessor + public: false + tags: + - { name: inpost.izi.order_message_processor } + + izi\prestashop\Configuration\PrestaShopConfiguration: + class: izi\prestashop\Configuration\PrestaShopConfiguration + public: false + arguments: + - '@izi\prestashop\Configuration\Adapter\Configuration' + + inpost.izi.shop_context: + class: PrestaShop\PrestaShop\Adapter\Shop\Context + public: false + tags: + - { name: inpost.izi.gui_configuration_dependency, key: context } + + izi\prestashop\Module\ModuleRepository: + class: izi\prestashop\Module\ModuleRepository + public: false + + izi\prestashop\Product\Price\LowestPriceProviderFactory: + class: izi\prestashop\Product\Price\LowestPriceProviderFactory + public: false + arguments: + - '@izi\prestashop\Module\ModuleRepository' + - '@inpost.izi.general_logger' + + izi\prestashop\Product\Price\LowestPriceProviderInterface: + class: izi\prestashop\Product\Price\LowestPriceProviderInterface + public: false + factory: [ '@izi\prestashop\Product\Price\LowestPriceProviderFactory', create ] + + izi\prestashop\PromoCode\PromoCodeProviderInterface: '@izi\prestashop\PromoCode\CartRulePromoCodeProvider' + izi\prestashop\PromoCode\CartRulePromoCodeProvider: + class: izi\prestashop\PromoCode\CartRulePromoCodeProvider + public: false + arguments: + - '@izi\prestashop\PromoCode\CartRuleOptionsRepositoryInterface' + + izi\prestashop\PromoCode\AvailablePromotionsProviderInterface: '@izi\prestashop\PromoCode\AvailableCartRulesProvider' + izi\prestashop\PromoCode\AvailableCartRulesProvider: + class: izi\prestashop\PromoCode\AvailableCartRulesProvider + public: false + arguments: + - '@izi\prestashop\PromoCode\CartRuleOptionsRepositoryInterface' + - '@izi\prestashop\Configuration\PromoCodesConfigurationInterface' + - '@izi\prestashop\ObjectModel\Repository\CmsPageRepository' + - '@izi\prestashop\ObjectModel\Repository\CartRuleRepository' + - '@inpost.izi.context' + + inpost.izi.request_stack: + class: Symfony\Component\HttpFoundation\RequestStack + factory: [ '@inpost.izi.module', getRequestStack ] + + inpost.izi.validator: + class: Symfony\Component\Validator\Validator\ValidatorInterface + public: true + factory: [ izi\prestashop\Validator\ValidatorFactory, create ] + tags: + - { name: inpost.izi.gui_configuration_dependency, key: validator } + arguments: + - '@inpost.izi.constraint_validator_locator' + + izi\prestashop\Validator\Cart\BindableValidator: + class: izi\prestashop\Validator\Cart\BindableValidator + tags: + - { name: validator.constraint_validator } + arguments: + - '@inpost.izi.module' + + izi\prestashop\Validator\Cart\HasProductsValidator: + class: izi\prestashop\Validator\Cart\HasProductsValidator + tags: + - { name: validator.constraint_validator } + + izi\prestashop\Validator\Cart\PaymentInCurrencyAvailableValidator: + class: izi\prestashop\Validator\Cart\PaymentInCurrencyAvailableValidator + tags: + - { name: validator.constraint_validator } + arguments: + - '@izi\prestashop\Payment\PaymentCurrencyChecker' + + izi\prestashop\Validator\Cart\HasUnrestrictedProductValidator: + class: izi\prestashop\Validator\Cart\HasUnrestrictedProductValidator + tags: + - { name: validator.constraint_validator } + + izi\prestashop\Validator\Product\NotInRestrictedCategoryValidator: + class: izi\prestashop\Validator\Product\NotInRestrictedCategoryValidator + tags: + - { name: validator.constraint_validator } + arguments: + - '@izi\prestashop\Repository\Product\CategoryRestrictionsRepositoryInterface' + + izi\prestashop\Validator\Product\NotFromRestrictedManufacturerValidator: + class: izi\prestashop\Validator\Product\NotFromRestrictedManufacturerValidator + tags: + - { name: validator.constraint_validator } + arguments: + - '@izi\prestashop\Repository\Product\ManufacturerRestrictionsRepositoryInterface' + + izi\prestashop\Validator\Product\NotWithRestrictedAttributesValidator: + class: izi\prestashop\Validator\Product\NotWithRestrictedAttributesValidator + tags: + - { name: validator.constraint_validator } + arguments: + - '@izi\prestashop\Repository\Product\AttributeRestrictionsRepositoryInterface' + - '@izi\prestashop\ObjectModel\Repository\CombinationRepository' + + izi\prestashop\Validator\Product\NotWithRestrictedFeaturesValidator: + class: izi\prestashop\Validator\Product\NotWithRestrictedFeaturesValidator + tags: + - { name: validator.constraint_validator } + arguments: + - '@izi\prestashop\Repository\Product\FeatureRestrictionsRepositoryInterface' + + izi\prestashop\Validator\Product\UnrestrictedValidator: + class: izi\prestashop\Validator\Product\UnrestrictedValidator + tags: + - { name: validator.constraint_validator } + arguments: + - '@izi\prestashop\Configuration\ProductRestrictionsConfigurationInterface' + + izi\prestashop\Environment\EnvironmentFactoryInterface: '@izi\prestashop\Environment\EnvironmentFactory' + izi\prestashop\Environment\EnvironmentFactory: + class: izi\prestashop\Environment\EnvironmentFactory + public: false + + izi\prestashop\HotProduct\HotProductDataMapperInterface: '@izi\prestashop\HotProduct\HotProductDataMapper' + izi\prestashop\HotProduct\HotProductDataMapper: + class: izi\prestashop\HotProduct\HotProductDataMapper + public: false + arguments: + - '@izi\prestashop\Configuration\PrestaShopConfiguration' + - '@inpost.izi.object_model.language_repository' + - '@izi\prestashop\ObjectModel\Repository\ProductRepository' + - '@izi\prestashop\ObjectModel\Repository\CombinationRepository' + - '@izi\prestashop\Product\Price\PriceCalculatorInterface' + - '@izi\prestashop\Product\Image\ImageUrlsProviderInterface' + - '@inpost.izi.context' + + izi\prestashop\Product\Price\PriceCalculatorInterface: '@izi\prestashop\Product\Price\PriceCalculator' + izi\prestashop\Product\Price\PriceCalculator: + class: izi\prestashop\Product\Price\PriceCalculator + public: false + arguments: + - '@izi\prestashop\Configuration\PrestaShopConfiguration' + - '@izi\prestashop\ObjectModel\Repository\CurrencyRepository' + - '@inpost.izi.object_model.country_repository' + + izi\prestashop\Product\Image\ImageUrlsProviderInterface: '@izi\prestashop\Product\Image\ImageUrlsProvider' + izi\prestashop\Product\Image\ImageUrlsProvider: + class: izi\prestashop\Product\Image\ImageUrlsProvider + public: false + arguments: + - '@inpost.izi.product.image_retriever' + - '@izi\prestashop\Configuration\ProductConfigurationInterface' + - '@inpost.izi.context' + - '@izi\prestashop\ObjectModel\Repository\ImageTypeRepository' + + inpost.izi.product.image_retriever: + class: PrestaShop\PrestaShop\Adapter\Image\ImageRetriever + public: false + arguments: + - '@=service("inpost.izi.context").link' + + izi\prestashop\HotProduct\MessageHandler\UpdateHotProductHandlerInterface: '@izi\prestashop\HotProduct\MessageHandler\UpdateHotProductHandler' + izi\prestashop\HotProduct\MessageHandler\UpdateHotProductHandler: + class: izi\prestashop\HotProduct\MessageHandler\UpdateHotProductHandler + public: false + tags: + - { name: inpost.izi.command_handler } + arguments: + - '@izi\prestashop\HotProduct\HotProductRepositoryInterface' + - '@izi\prestashop\HotProduct\HotProductDataMapperInterface' + - '@izi\prestashop\BasketApp\Product\ProductsApiClientInterface' + + izi\prestashop\HotProduct\MessageHandler\DeleteRemoteProductHandlerInterface: '@izi\prestashop\HotProduct\MessageHandler\DeleteRemoteProductHandler' + izi\prestashop\HotProduct\MessageHandler\DeleteRemoteProductHandler: + class: izi\prestashop\HotProduct\MessageHandler\DeleteRemoteProductHandler + public: false + tags: + - { name: inpost.izi.command_handler } + arguments: + - '@izi\prestashop\HotProduct\HotProductRepositoryInterface' + - '@izi\prestashop\BasketApp\Product\ProductsApiClientInterface' + + izi\prestashop\HotProduct\EventListener\UpdateHotProductsListener: + class: izi\prestashop\HotProduct\EventListener\UpdateHotProductsListener + tags: + - { name: kernel.event_subscriber } + arguments: + - '@inpost.izi.shop_context' + - '@izi\prestashop\HotProduct\HotProductRepositoryInterface' + - '@izi\prestashop\Product\Price\PriceCalculatorInterface' + - '@izi\prestashop\CommandBusInterface' + - '@inpost.izi.general_logger' + - '@izi\prestashop\HotProduct\HotProductValidator' + + izi\prestashop\HotProduct\HotProductValidator: + class: izi\prestashop\HotProduct\HotProductValidator + public: false + arguments: + - '@izi\prestashop\ObjectModel\Repository\ProductRepository' + - '@izi\prestashop\Translation\LegacyTranslator' + + izi\prestashop\Mail\Resolver\OrderMailRecipientResolver: + class: izi\prestashop\Mail\Resolver\OrderMailRecipientResolver + public: false + arguments: + - '@izi\prestashop\Repository\OrderDataRepositoryInterface' + - '@inpost.izi.object_model.order_repository' + + izi\prestashop\Mail\EventListener\ReplaceOrderNotificationRecipientListener: + class: izi\prestashop\Mail\EventListener\ReplaceOrderNotificationRecipientListener + tags: + - { name: kernel.event_subscriber } + arguments: + - '@izi\prestashop\Mail\Resolver\OrderMailRecipientResolver' + + izi\prestashop\Analytics\Cookie\Repository\CookieRepositoryInterface: '@izi\prestashop\Analytics\Cookie\Repository\CookieRepository' + izi\prestashop\Analytics\Cookie\Repository\CookieRepository: + class: izi\prestashop\Analytics\Cookie\Repository\CookieRepository + + izi\prestashop\Analytics\Cookie\Factory\CookieFactoryInterface: '@izi\prestashop\Analytics\Cookie\Factory\CookieFactory' + izi\prestashop\Analytics\Cookie\Factory\CookieFactory: + class: izi\prestashop\Analytics\Cookie\Factory\CookieFactory + + izi\prestashop\Analytics\Cookie\FacebookClickIdCookie: + class: izi\prestashop\Analytics\Cookie\FacebookClickIdCookie + arguments: + - '@izi\prestashop\Analytics\Cookie\Factory\CookieFactory' + - '@izi\prestashop\Analytics\Cookie\Repository\CookieRepository' + tags: + - { name: inpost.izi.analytics.cookie_erase } + - { name: inpost.izi.analytics.cookie_persist } + + izi\prestashop\Analytics\Cookie\GoogleClickIdCookie: + class: izi\prestashop\Analytics\Cookie\GoogleClickIdCookie + arguments: + - '@izi\prestashop\Analytics\Cookie\Factory\CookieFactory' + - '@izi\prestashop\Analytics\Cookie\Repository\CookieRepository' + tags: + - { name: inpost.izi.analytics.cookie_erase } + - { name: inpost.izi.analytics.cookie_persist } + + izi\prestashop\Analytics\Cookie\GoogleClientIdCookie: + class: izi\prestashop\Analytics\Cookie\GoogleClientIdCookie + + izi\prestashop\Analytics\Cookie\Executor\CookieEraseExecutor: + class: izi\prestashop\Analytics\Cookie\Executor\CookieEraseExecutor + arguments: + - !tagged inpost.izi.analytics.cookie_erase + + izi\prestashop\Analytics\Cookie\Executor\CookiePersisterExecutor: + class: izi\prestashop\Analytics\Cookie\Executor\CookiePersisterExecutor + arguments: + - !tagged inpost.izi.analytics.cookie_persist + + izi\prestashop\Analytics\Factory\BasketAnalyticsFactoryInterface: '@izi\prestashop\Analytics\Factory\BasketAnalyticsFactory' + izi\prestashop\Analytics\Factory\BasketAnalyticsFactory: + class: izi\prestashop\Analytics\Factory\BasketAnalyticsFactory + public: false + arguments: + - '@izi\prestashop\Analytics\Cookie\GoogleClickIdCookie' + - '@izi\prestashop\Analytics\Cookie\FacebookClickIdCookie' + - '@izi\prestashop\Analytics\Cookie\GoogleClientIdCookie' + + izi\prestashop\Analytics\EventListener\UpdateBasketAnalyticsListener: + class: izi\prestashop\Analytics\EventListener\UpdateBasketAnalyticsListener + tags: + - { name: kernel.event_subscriber } + arguments: + - '@izi\prestashop\CommandBusInterface' + - '@inpost.izi.request_stack' + - '@izi\prestashop\Analytics\Factory\BasketAnalyticsFactory' + - '@izi\prestashop\Analytics\Cookie\Executor\CookieEraseExecutor' + - '@izi\prestashop\Configuration\GeneralConfigurationInterface' + + izi\prestashop\Analytics\Handler\UpdateCartAnalyticsHandlerInterface: '@izi\prestashop\Analytics\Handler\UpdateCartAnalyticsHandler' + izi\prestashop\Analytics\Handler\UpdateCartAnalyticsHandler: + class: izi\prestashop\Analytics\Handler\UpdateCartAnalyticsHandler + public: false + tags: + - { name: inpost.izi.command_handler } + arguments: + - '@izi\prestashop\Analytics\BasketAnalyticsRepositoryInterface' + + izi\prestashop\Analytics\BasketAnalyticsRepositoryInterface: '@izi\prestashop\Analytics\BasketAnalyticsRepository' + izi\prestashop\Analytics\BasketAnalyticsRepository: + class: izi\prestashop\Analytics\BasketAnalyticsRepository + public: false + arguments: + - '@izi\prestashop\Database\Connection' diff --git a/modules/inpostizi/config/services/common_admin.yml b/modules/inpostizi/config/services/common_admin.yml new file mode 100644 index 00000000..94b8626f --- /dev/null +++ b/modules/inpostizi/config/services/common_admin.yml @@ -0,0 +1,47 @@ +# common BO configuration +imports: + - { resource: admin/form.yml } + - { resource: admin/form.php, ignore_errors: true } + +services: + izi\prestashop\View\Asset\AssetManagerInterface: '@izi\prestashop\View\Asset\AdminAssetManager' + izi\prestashop\View\Asset\AdminAssetManager: + class: izi\prestashop\View\Asset\AdminAssetManager + public: false + arguments: + - '@inpost.izi.module' + - '@inpost.izi.context' + + izi\prestashop\View\Asset\Provider\Admin\CartRulesAssetsProvider: + class: izi\prestashop\View\Asset\Provider\Admin\CartRulesAssetsProvider + public: false + tags: + - { name: inpost.izi.admin_assets_provider } + arguments: + - '@inpost.izi.context' + - '@inpost.izi.request_stack' + + izi\prestashop\Handler\Config\UpdateCartRuleOptionsHandlerInterface: '@izi\prestashop\Handler\Config\UpdateCartRuleOptionsHandler' + izi\prestashop\Handler\Config\UpdateCartRuleOptionsHandler: + class: izi\prestashop\Handler\Config\UpdateCartRuleOptionsHandler + public: false + tags: + - { name: inpost.izi.command_handler } + arguments: + - '@izi\prestashop\PromoCode\CartRuleOptionsRepositoryInterface' + + izi\prestashop\Form\Type\CartRuleOptionsType: + class: izi\prestashop\Form\Type\CartRuleOptionsType + tags: + - { name: form.type } + arguments: + - '@izi\prestashop\Translation\LegacyTranslator' + - '@inpost.izi.context' + + izi\prestashop\Form\Type\ObjectModelType: + class: izi\prestashop\Form\Type\ObjectModelType + tags: + - { name: form.type } + arguments: + - '@izi\prestashop\ObjectModel\ObjectManagerInterface' + - '@inpost.izi.context' diff --git a/modules/inpostizi/config/services/common_front.yml b/modules/inpostizi/config/services/common_front.yml new file mode 100644 index 00000000..52858783 --- /dev/null +++ b/modules/inpostizi/config/services/common_front.yml @@ -0,0 +1,490 @@ +# common FO configuration +services: + izi\prestashop\View\Asset\FrontAssetManager: + class: izi\prestashop\View\Asset\FrontAssetManager + public: false + arguments: + - '@inpost.izi.module' + - '@inpost.izi.context' + + izi\prestashop\Hook\Front\ActionFrontControllerSetMedia: + class: izi\prestashop\Hook\Front\ActionFrontControllerSetMedia + public: false + tags: + - { name: inpost.izi.hook } + arguments: + - '@izi\prestashop\View\Asset\FrontAssetManager' + - '@inpost.izi.security.authorization_checker' + - !tagged inpost.izi.front_assets_provider + - '@izi\prestashop\Configuration\ApiConfigurationInterface' + + izi\prestashop\Hook\Front\DisplayHeader: + class: izi\prestashop\Hook\Front\DisplayHeader + public: false + tags: + - { name: inpost.izi.hook } + arguments: + - '@inpost.izi.context' + - '@izi\prestashop\Configuration\GeneralConfigurationInterface' + - '@izi\prestashop\Configuration\ApiConfigurationInterface' + - '@inpost.izi.security.authorization_checker' + - '@izi\prestashop\Analytics\Cookie\Executor\CookiePersisterExecutor' + - '@izi\prestashop\Repository\BasketSessionRepository' + - '@izi\prestashop\Event\EventDispatcherInterface' + + izi\prestashop\Hook\Front\ActionGetPaymentOptions: + class: izi\prestashop\Hook\Front\ActionGetPaymentOptions + public: false + tags: + - { name: inpost.izi.hook } + arguments: + - '@inpost.izi.module' + - '@izi\prestashop\Payment\PaymentCurrencyChecker' + - '@izi\prestashop\Configuration\GuiConfiguration' + - '@izi\prestashop\View\Templating\RendererInterface' + + izi\prestashop\Hook\Front\DisplayIziThankYou: + class: izi\prestashop\Hook\Front\DisplayIziThankYou + public: false + tags: + - { name: inpost.izi.hook } + arguments: + - '@inpost.izi.module' + - '@izi\prestashop\Configuration\GeneralConfiguration' + + izi\prestashop\Hook\Front\DisplayOrderConfirmation: + class: izi\prestashop\Hook\Front\DisplayOrderConfirmation + public: false + tags: + - { name: inpost.izi.hook } + arguments: + - '@izi\prestashop\Repository\BasketSessionRepository' + - '@inpost.izi.context' + - '@inpost.izi.module' + - '@izi\prestashop\Configuration\GeneralConfiguration' + + izi\prestashop\Hook\Front\DisplayPaymentReturn: + class: izi\prestashop\Hook\Front\DisplayPaymentReturn + public: false + tags: + - { name: inpost.izi.hook } + arguments: + - '@inpost.izi.module' + - '@izi\prestashop\Configuration\GeneralConfiguration' + + izi\prestashop\Hook\Front\DisplayProductActions: + class: izi\prestashop\Hook\Front\DisplayProductActions + public: false + tags: + - { name: inpost.izi.hook } + arguments: + - '@izi\prestashop\Configuration\GuiConfiguration' + - '@izi\prestashop\Configuration\GeneralConfiguration' + - '@inpost.izi.module' + - '@izi\prestashop\View\Templating\RendererInterface' + - '@inpost.izi.context' + - '@izi\prestashop\Repository\BasketSessionRepository' + + izi\prestashop\Hook\Front\DisplayProductAdditionalInfo: + class: izi\prestashop\Hook\Front\DisplayProductAdditionalInfo + public: false + tags: + - { name: inpost.izi.hook } + arguments: + - '@izi\prestashop\Configuration\GuiConfiguration' + - '@izi\prestashop\Configuration\GeneralConfiguration' + - '@inpost.izi.module' + - '@izi\prestashop\View\Templating\RendererInterface' + - '@inpost.izi.context' + - '@izi\prestashop\Repository\BasketSessionRepository' + + izi\prestashop\Hook\Front\DisplayShoppingCart: + class: izi\prestashop\Hook\Front\DisplayShoppingCart + public: false + tags: + - { name: inpost.izi.hook } + arguments: + - '@izi\prestashop\Configuration\GuiConfiguration' + - '@inpost.izi.module' + - '@izi\prestashop\View\Templating\RendererInterface' + + izi\prestashop\Hook\Front\DisplayShoppingCartFooter: + class: izi\prestashop\Hook\Front\DisplayShoppingCartFooter + public: false + tags: + - { name: inpost.izi.hook } + arguments: + - '@izi\prestashop\Configuration\GuiConfiguration' + - '@inpost.izi.module' + - '@izi\prestashop\View\Templating\RendererInterface' + + izi\prestashop\Hook\Front\DisplayExpressCheckout: + class: izi\prestashop\Hook\Front\DisplayExpressCheckout + public: false + tags: + - { name: inpost.izi.hook } + arguments: + - '@izi\prestashop\Configuration\GuiConfiguration' + - '@inpost.izi.module' + - '@izi\prestashop\View\Templating\RendererInterface' + + izi\prestashop\Hook\Front\DisplayCheckoutSummaryTop: + class: izi\prestashop\Hook\Front\DisplayCheckoutSummaryTop + public: false + tags: + - { name: inpost.izi.hook } + arguments: + - '@izi\prestashop\Configuration\GuiConfiguration' + - '@izi\prestashop\Configuration\GeneralConfiguration' + - '@inpost.izi.module' + - '@izi\prestashop\View\Templating\RendererInterface' + + izi\prestashop\Hook\Front\DisplayCustomerAccountFormTop: + class: izi\prestashop\Hook\Front\DisplayCustomerAccountFormTop + public: false + tags: + - { name: inpost.izi.hook } + arguments: + - '@izi\prestashop\Configuration\GuiConfiguration' + - '@inpost.izi.module' + - '@izi\prestashop\View\Templating\RendererInterface' + + izi\prestashop\Hook\Front\DisplayCustomerLoginFormAfter: + class: izi\prestashop\Hook\Front\DisplayCustomerLoginFormAfter + public: false + tags: + - { name: inpost.izi.hook } + arguments: + - '@izi\prestashop\Configuration\GuiConfiguration' + - '@inpost.izi.module' + - '@izi\prestashop\View\Templating\RendererInterface' + + izi\prestashop\Hook\Front\DisplayIziCartPreviewButton: + class: izi\prestashop\Hook\Front\DisplayIziCartPreviewButton + public: false + tags: + - { name: inpost.izi.hook } + arguments: + - '@izi\prestashop\Configuration\GuiConfiguration' + - '@inpost.izi.module' + - '@izi\prestashop\View\Templating\RendererInterface' + + izi\prestashop\Hook\Front\DisplayIziCheckoutButton: + class: izi\prestashop\Hook\Front\DisplayIziCheckoutButton + public: false + tags: + - { name: inpost.izi.hook } + arguments: + - '@izi\prestashop\Configuration\GuiConfiguration' + - '@izi\prestashop\Configuration\GeneralConfiguration' + - '@inpost.izi.module' + - '@izi\prestashop\View\Templating\RendererInterface' + + izi\prestashop\Controller\WidgetController: + class: izi\prestashop\Controller\WidgetController + public: true + arguments: + - '@inpost.izi.module' + - '@inpost.izi.context' + - '@izi\prestashop\CommandBus' + - '@inpost.izi.widget_controller_locator' + + izi\prestashop\Controller\Api\BasketController: + class: izi\prestashop\Controller\Api\BasketController + public: true + arguments: + - '@inpost.izi.serializer' + - '@izi\prestashop\CommandBus' + + izi\prestashop\Controller\Api\OrderController: + class: izi\prestashop\Controller\Api\OrderController + public: true + arguments: + - '@inpost.izi.serializer' + - '@izi\prestashop\CommandBus' + + izi\prestashop\Controller\Api\ProductController: + class: izi\prestashop\Controller\Api\ProductController + public: true + arguments: + - '@inpost.izi.serializer' + - '@izi\prestashop\CommandBus' + + izi\prestashop\Handler\GetBasketBindingKeyHandlerInterface: '@izi\prestashop\Handler\GetBasketBindingKeyHandler' + izi\prestashop\Handler\GetBasketBindingKeyHandler: + class: izi\prestashop\Handler\GetBasketBindingKeyHandler + public: false + tags: + - { name: inpost.izi.command_handler } + arguments: + - '@izi\prestashop\Repository\BasketSessionRepository' + - '@izi\prestashop\BasketApp\Basket\BasketsApiClientInterface' + - '@inpost.izi.context' + + izi\prestashop\Handler\GetOrderConfirmationUrlHandlerInterface: '@izi\prestashop\Handler\GetOrderConfirmationUrlHandler' + izi\prestashop\Handler\GetOrderConfirmationUrlHandler: + class: izi\prestashop\Handler\GetOrderConfirmationUrlHandler + public: false + tags: + - { name: inpost.izi.command_handler } + arguments: + - '@izi\prestashop\Repository\BasketSessionRepository' + - '@izi\prestashop\Order\ContextCustomerUpdater' + + izi\prestashop\MerchantApi\Handler\ConfirmBasketBindingHandlerInterface: '@izi\prestashop\MerchantApi\Handler\ConfirmBasketBindingHandler' + izi\prestashop\MerchantApi\Handler\ConfirmBasketBindingHandler: + class: izi\prestashop\MerchantApi\Handler\ConfirmBasketBindingHandler + public: false + tags: + - { name: inpost.izi.command_handler } + arguments: + - '@izi\prestashop\Repository\BasketSessionRepository' + - '@izi\prestashop\Builder\Basket\BasketBuilderFactory' + + izi\prestashop\MerchantApi\Handler\DeleteBasketBindingHandlerInterface: '@izi\prestashop\MerchantApi\Handler\DeleteBasketBindingHandler' + izi\prestashop\MerchantApi\Handler\DeleteBasketBindingHandler: + class: izi\prestashop\MerchantApi\Handler\DeleteBasketBindingHandler + public: false + tags: + - { name: inpost.izi.command_handler } + arguments: + - '@izi\prestashop\Repository\BasketSessionRepository' + + izi\prestashop\MerchantApi\Handler\GetBasketHandlerInterface: '@izi\prestashop\MerchantApi\Handler\GetBasketHandler' + izi\prestashop\MerchantApi\Handler\GetBasketHandler: + class: izi\prestashop\MerchantApi\Handler\GetBasketHandler + public: false + tags: + - { name: inpost.izi.command_handler } + arguments: + - '@izi\prestashop\Repository\BasketSessionRepository' + - '@izi\prestashop\Builder\Basket\BasketBuilderFactory' + + izi\prestashop\MerchantApi\Handler\UpdateBasketHandlerInterface: '@izi\prestashop\MerchantApi\Handler\UpdateBasketHandler' + izi\prestashop\MerchantApi\Handler\UpdateBasketHandler: + class: izi\prestashop\MerchantApi\Handler\UpdateBasketHandler + public: false + tags: + - { name: inpost.izi.command_handler } + arguments: + - '@izi\prestashop\Repository\BasketSessionRepository' + - '@izi\prestashop\MerchantApi\Handler\Basket\BasketEventHandler' + - '@izi\prestashop\Builder\Basket\BasketBuilderFactory' + + izi\prestashop\MerchantApi\Handler\GetProductsHandlerInterface: '@izi\prestashop\MerchantApi\Handler\GetProductsHandler' + izi\prestashop\MerchantApi\Handler\GetProductsHandler: + class: izi\prestashop\MerchantApi\Handler\GetProductsHandler + public: false + tags: + - { name: inpost.izi.command_handler } + arguments: + - '@izi\prestashop\HotProduct\HotProductRepositoryInterface' + - '@izi\prestashop\HotProduct\HotProductDataMapperInterface' + - '@izi\prestashop\BasketApp\Product\ProductsApiClientInterface' + - '@izi\prestashop\Cache\ConfigurationCache' + - '@izi\prestashop\ObjectModel\Repository\ProductRepository' + + izi\prestashop\MerchantApi\Handler\AddProductToBasketHandlerInterface: '@izi\prestashop\MerchantApi\Handler\AddProductToBasketHandler' + izi\prestashop\MerchantApi\Handler\AddProductToBasketHandler: + class: izi\prestashop\MerchantApi\Handler\AddProductToBasketHandler + public: false + tags: + - { name: inpost.izi.command_handler } + arguments: + - '@izi\prestashop\Repository\BasketSessionRepository' + - '@izi\prestashop\ContextManager' + - '@izi\prestashop\CommandBusInterface' + - '@izi\prestashop\Event\EventDispatcherInterface' + - '@izi\prestashop\Builder\Basket\BasketBuilderFactory' + + izi\prestashop\MerchantApi\Handler\CreateOrderHandlerInterface: '@izi\prestashop\MerchantApi\Handler\CreateOrderHandler' + izi\prestashop\MerchantApi\Handler\CreateOrderHandler: + class: izi\prestashop\MerchantApi\Handler\CreateOrderHandler + public: false + tags: + - { name: inpost.izi.command_handler } + arguments: + - '@izi\prestashop\Repository\BasketSessionRepository' + - '@izi\prestashop\CommandBusInterface' + + izi\prestashop\MerchantApi\Handler\GetOrderHandlerInterface: '@izi\prestashop\MerchantApi\Handler\GetOrderHandler' + izi\prestashop\MerchantApi\Handler\GetOrderHandler: + class: izi\prestashop\MerchantApi\Handler\GetOrderHandler + public: false + tags: + - { name: inpost.izi.command_handler } + arguments: + - '@inpost.izi.object_model.order_repository' + - '@izi\prestashop\Repository\BasketSessionRepository' + - '@izi\prestashop\Analytics\BasketAnalyticsRepositoryInterface' + + izi\prestashop\MerchantApi\Handler\UpdateOrderHandlerInterface: '@izi\prestashop\MerchantApi\Handler\UpdateOrderHandler' + izi\prestashop\MerchantApi\Handler\UpdateOrderHandler: + class: izi\prestashop\MerchantApi\Handler\UpdateOrderHandler + public: false + tags: + - { name: inpost.izi.command_handler } + arguments: + - '@inpost.izi.object_model.order_repository' + - '@izi\prestashop\Configuration\OrdersConfiguration' + - '@izi\prestashop\Builder\Order\OrderStatusDescriptionProvider' + - '@inpost.izi.general_logger' + + izi\prestashop\MerchantApi\Handler\Order\UpdateCartMessageHandlerInterface: '@izi\prestashop\MerchantApi\Handler\Order\UpdateCartMessageHandler' + izi\prestashop\MerchantApi\Handler\Order\UpdateCartMessageHandler: + class: izi\prestashop\MerchantApi\Handler\Order\UpdateCartMessageHandler + public: false + tags: + - { name: inpost.izi.command_handler } + arguments: + - '@izi\prestashop\ObjectModel\ObjectManagerInterface' + - '@izi\prestashop\Configuration\OrdersConfiguration' + - '@izi\prestashop\Order\Message\MessageFormatterInterface' + + izi\prestashop\MerchantApi\Handler\Basket\CreateCartHandlerInterface: '@izi\prestashop\MerchantApi\Handler\Basket\CreateCartHandler' + izi\prestashop\MerchantApi\Handler\Basket\CreateCartHandler: + class: izi\prestashop\MerchantApi\Handler\Basket\CreateCartHandler + public: false + tags: + - { name: inpost.izi.command_handler } + arguments: + - '@inpost.izi.context' + - '@izi\prestashop\Configuration\PrestaShopConfiguration' + - '@izi\prestashop\ObjectModel\ObjectManagerInterface' + + izi\prestashop\MerchantApi\Handler\Basket\AddProductToCartHandlerInterface: '@izi\prestashop\MerchantApi\Handler\Basket\AddProductToCartHandler' + izi\prestashop\MerchantApi\Handler\Basket\AddProductToCartHandler: + class: izi\prestashop\MerchantApi\Handler\Basket\AddProductToCartHandler + public: false + tags: + - { name: inpost.izi.command_handler } + arguments: + - '@inpost.izi.context' + - '@izi\prestashop\ObjectModel\Repository\ProductRepository' + - '@izi\prestashop\Translation\LegacyTranslator' + - '@inpost.izi.general_logger' + + izi\prestashop\MerchantApi\Handler\Basket\IncrementCartQuantityHandlerInterface: '@izi\prestashop\MerchantApi\Handler\Basket\IncrementCartQuantityHandler' + izi\prestashop\MerchantApi\Handler\Basket\IncrementCartQuantityHandler: + class: izi\prestashop\MerchantApi\Handler\Basket\IncrementCartQuantityHandler + public: false + tags: + - { name: inpost.izi.command_handler } + arguments: + - '@izi\prestashop\ObjectModel\Repository\ProductRepository' + - '@izi\prestashop\Translation\LegacyTranslator' + - '@inpost.izi.general_logger' + + izi\prestashop\MerchantApi\Handler\Basket\BasketEventHandlerInterface: '@izi\prestashop\MerchantApi\Handler\Basket\BasketEventHandler' + izi\prestashop\MerchantApi\Handler\Basket\BasketEventHandler: + class: izi\prestashop\MerchantApi\Handler\Basket\BasketEventHandler + public: false + arguments: + - '@inpost.izi.basket_event_handler_locator' + - '@izi\prestashop\ContextManager' + - '@izi\prestashop\Event\EventDispatcherInterface' + + izi\prestashop\MerchantApi\Handler\Basket\ProductsQuantityEventHandler: + class: izi\prestashop\MerchantApi\Handler\Basket\ProductsQuantityEventHandler + public: false + tags: + - { name: inpost.izi.basket_event_handler } + arguments: + - '@inpost.izi.module' + - '@inpost.izi.context' + - '@izi\prestashop\ObjectModel\ObjectManager' + - '@inpost.izi.general_logger' + + izi\prestashop\MerchantApi\Handler\Basket\PromoCodesEventHandler: + class: izi\prestashop\MerchantApi\Handler\Basket\PromoCodesEventHandler + public: false + tags: + - { name: inpost.izi.basket_event_handler } + arguments: + - '@inpost.izi.module' + - '@inpost.izi.context' + - '@izi\prestashop\ObjectModel\ObjectManager' + - '@inpost.izi.general_logger' + + izi\prestashop\MerchantApi\Handler\Basket\RelatedProductsEventHandler: + class: izi\prestashop\MerchantApi\Handler\Basket\RelatedProductsEventHandler + public: false + tags: + - { name: inpost.izi.basket_event_handler } + arguments: + - '@izi\prestashop\CommandBusInterface' + - '@inpost.izi.context' + + izi\prestashop\MerchantApi\Firewall\MerchantApiAuthenticator: + class: izi\prestashop\MerchantApi\Firewall\MerchantApiAuthenticator + public: true # todo: make private after refactoring the front controller + arguments: + - '@izi\prestashop\MerchantApi\Firewall\SigningKeysService' + - '@inpost.izi.clock' + + izi\prestashop\MerchantApi\Firewall\SigningKeysService: + class: izi\prestashop\MerchantApi\Firewall\SigningKeysService + public: false + arguments: + - '@izi\prestashop\BasketApp\Signature\SigningKeysApiClientInterface' + - '@izi\prestashop\Cache\ConfigurationCache' + + inpost.izi.security.access.decision_manager: + class: Symfony\Component\Security\Core\Authorization\AccessDecisionManager + public: false + arguments: + - !tagged inpost.izi.security_voter + + inpost.izi.security.authorization_checker: + class: izi\prestashop\Security\AuthorizationChecker + public: false + tags: + - { name: inpost.izi.widget_controller_dependency, key: Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface } + arguments: + - '@inpost.izi.security.access.decision_manager' + + izi\prestashop\Security\Voter\BindingWidgetVoter: + class: izi\prestashop\Security\Voter\BindingWidgetVoter + public: false + tags: + - { name: inpost.izi.security_voter } + arguments: + - '@izi\prestashop\Configuration\GeneralConfigurationInterface' + - '@inpost.izi.context' + + izi\prestashop\View\Asset\Provider\Front\CommonAssetsProvider: + class: izi\prestashop\View\Asset\Provider\Front\CommonAssetsProvider + public: false + tags: + - { name: inpost.izi.front_assets_provider } + arguments: + - '@inpost.izi.module' + - '@inpost.izi.context' + - '@inpost.izi.environment' + + izi\prestashop\View\Asset\Provider\Front\ProductPageAssetsProvider: + class: izi\prestashop\View\Asset\Provider\Front\ProductPageAssetsProvider + public: false + tags: + - { name: inpost.izi.front_assets_provider } + arguments: + - '@izi\prestashop\Configuration\GeneralConfigurationInterface' + - '@inpost.izi.context' + + izi\prestashop\View\Asset\Provider\Front\WidgetConfigurationProvider: + class: izi\prestashop\View\Asset\Provider\Front\WidgetConfigurationProvider + public: false + tags: + - { name: inpost.izi.front_assets_provider } + arguments: + - '@inpost.izi.context' + - '@izi\prestashop\Configuration\ApiConfigurationInterface' + - '@izi\prestashop\Configuration\GeneralConfigurationInterface' + - '@izi\prestashop\Repository\BasketSessionRepository' + + izi\prestashop\Order\ContextCustomerUpdater: + class: izi\prestashop\Order\ContextCustomerUpdater + public: false + arguments: + - '@inpost.izi.context' + - '@izi\prestashop\ObjectModel\ObjectManagerInterface' diff --git a/modules/inpostizi/config/services/sf28.yml b/modules/inpostizi/config/services/sf28.yml new file mode 100644 index 00000000..a8cfb805 --- /dev/null +++ b/modules/inpostizi/config/services/sf28.yml @@ -0,0 +1,131 @@ +# PS < 1.7.4 configuration +imports: + - { resource: common.yml } + - { resource: common_admin.yml } + - { resource: common_front.yml } + +parameters: + inpost.izi.logs_dir: '%kernel.root_dir%/logs/inpost' + +services: + inpost.izi.event_dispatcher: + class: Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher + public: false + arguments: + - '@service_container' + + izi\prestashop\CommandBus: + class: izi\prestashop\CommandBus + arguments: + - '@inpost.izi.command_handler_locator' + + inpost.izi.command_handler_locator: + class: izi\prestashop\DependencyInjection\ServiceLocator + autowire: true + tags: + - { name: inpost.izi.service_locator, tag: inpost.izi.command_handler, index_by: command_class, default_index_method: getHandledCommandClass } + + izi\prestashop\Hook\HookExecutor: + class: izi\prestashop\Hook\HookExecutor + arguments: + - '@inpost.izi.hook_locator' + - '@inpost.izi.module' + + inpost.izi.hook_locator: + class: izi\prestashop\DependencyInjection\ServiceLocator + autowire: true + tags: + - { name: inpost.izi.service_locator, tag: inpost.izi.hook, index_by: hook_name, default_index_method: getHookName } + + inpost.izi.repository_locator: + class: izi\prestashop\DependencyInjection\ServiceLocator + autowire: true + tags: + - { name: inpost.izi.service_locator, tag: inpost.izi.model_repository, index_by: model_class } + + inpost.izi.basket_event_handler_locator: + class: izi\prestashop\DependencyInjection\ServiceLocator + autowire: true + tags: + - { name: inpost.izi.service_locator, tag: inpost.izi.basket_event_handler, index_by: event_type, default_index_method: getHandledEventType } + + izi\prestashop\Hook\Admin\ActionAdminCartRuleSaveAfter: + class: izi\prestashop\Hook\Admin\ActionAdminCartRuleSaveAfter + public: false + tags: + - { name: inpost.izi.hook } + arguments: + - '@inpost.izi.form_factory' + - '@izi\prestashop\CommandBusInterface' + + izi\prestashop\Hook\Admin\ActionAdminControllerSetMedia: + class: izi\prestashop\Hook\Admin\ActionAdminControllerSetMedia + public: false + tags: + - { name: inpost.izi.hook } + arguments: + - '@izi\prestashop\View\Asset\AdminAssetManager' + - !tagged inpost.izi.admin_assets_provider + + izi\prestashop\Hook\Admin\DisplayBackOfficeHeader: + class: izi\prestashop\Hook\Admin\DisplayBackOfficeHeader + public: false + tags: + - { name: inpost.izi.hook } + arguments: + - '@inpost.izi.context' + - '@izi\prestashop\Repository\CartRuleRepositoryInterface' + - '@inpost.izi.form_factory' + - '@izi\prestashop\View\Templating\RendererInterface' + + izi\prestashop\Hook\Admin\DisplayAdminOrderLeft: + class: izi\prestashop\Hook\Admin\DisplayAdminOrderLeft + public: false + autowire: true + tags: + - { name: inpost.izi.hook } + + inpost.izi.constraint_validator_locator: + class: izi\prestashop\DependencyInjection\ServiceLocator + autowire: true + tags: + - { name: inpost.izi.service_locator, tag: validator.constraint_validator } + + inpost.izi.gui_configuration_locator: + class: izi\prestashop\DependencyInjection\ServiceLocator + autowire: true + tags: + - { name: inpost.izi.service_locator, tag: inpost.izi.gui_configuration_dependency, index_by: key } + + izi\prestashop\Form\FormFactoryFactory: + class: izi\prestashop\Form\FormFactoryFactory + public: false + arguments: + - '@inpost.izi.validator' + + inpost.izi.form_factory: + class: Symfony\Component\Form\FormFactoryInterface + factory: [ '@izi\prestashop\Form\FormFactoryFactory', create ] + public: false + arguments: + - '@inpost.izi.form_type_locator' + - + Symfony\Component\Form\Extension\Core\Type\ChoiceType: ['@izi\prestashop\Form\TypeExtension\ChoicesAsValuesTypeExtension'] + Symfony\Component\Form\Extension\Core\Type\FormType: ['@izi\prestashop\Form\TypeExtension\HelpTextExtension'] + + inpost.izi.form_type_locator: + class: izi\prestashop\DependencyInjection\ServiceLocator + autowire: true + tags: + - { name: inpost.izi.service_locator, tag: form.type } + + izi\prestashop\Form\TypeExtension\ChoicesAsValuesTypeExtension: + class: izi\prestashop\Form\TypeExtension\ChoicesAsValuesTypeExtension + tags: + - { name: form.type_extension, extended_type: Symfony\Component\Form\Extension\Core\Type\ChoiceType } + + inpost.izi.widget_controller_locator: + class: izi\prestashop\DependencyInjection\ServiceLocator + autowire: true + tags: + - { name: inpost.izi.service_locator, tag: inpost.izi.widget_controller_dependency, index_by: key } diff --git a/modules/inpostizi/config/services/sf34.yml b/modules/inpostizi/config/services/sf34.yml new file mode 100644 index 00000000..8800120a --- /dev/null +++ b/modules/inpostizi/config/services/sf34.yml @@ -0,0 +1,72 @@ +# PS >= 1.7.4 common FO/BO configuration +imports: + - { resource: common.yml } + +services: + _defaults: + public: false + + inpost.izi.repository_locator: + class: Symfony\Component\DependencyInjection\ServiceLocator + tags: + - { name: container.service_locator } + arguments: + - Cart: '@inpost.izi.object_model.cart_repository' + Country: '@inpost.izi.object_model.country_repository' + Currency: '@izi\prestashop\ObjectModel\Repository\CurrencyRepository' + Carrier: '@izi\prestashop\ObjectModel\Repository\CarrierRepository' + CartRule: '@izi\prestashop\ObjectModel\Repository\CartRuleRepository' + Combination: '@izi\prestashop\ObjectModel\Repository\CombinationRepository' + Product: '@izi\prestashop\ObjectModel\Repository\ProductRepository' + Configuration: '@izi\prestashop\ObjectModel\Repository\ConfigurationRepository' + Hook: '@izi\prestashop\ObjectModel\Repository\HookRepository' + Language: '@inpost.izi.object_model.language_repository' + Order: '@inpost.izi.object_model.order_repository' + OrderState: '@inpost.izi.object_model.order_state_repository' + Shop: '@inpost.izi.object_model.shop_repository' + InPostShipmentModel: '@izi\prestashop\ObjectModel\Repository\ShipmentRepository' + + inpost.izi.event_dispatcher: + class: Symfony\Component\EventDispatcher\EventDispatcherInterface + factory: [ '@izi\prestashop\Event\EventDispatcherFactory', create ] + + inpost.izi.event_subscriber_locator: + class: Symfony\Component\DependencyInjection\ServiceLocator + tags: + - { name: container.service_locator } + arguments: + - izi\prestashop\EventListener\ShipmentListener: '@izi\prestashop\EventListener\ShipmentListener' + izi\prestashop\EventListener\CartListener: '@izi\prestashop\EventListener\CartListener' + izi\prestashop\EventListener\OrderListener: '@izi\prestashop\EventListener\OrderListener' + izi\prestashop\Form\BasketAppClientProvider: '@?izi\prestashop\Form\BasketAppClientProvider' + izi\prestashop\MerchantApi\EventListener\UpdateCartRulesListener: '@?izi\prestashop\MerchantApi\EventListener\UpdateCartRulesListener' + izi\prestashop\HotProduct\EventListener\UpdateHotProductsListener: '@izi\prestashop\HotProduct\EventListener\UpdateHotProductsListener' + izi\prestashop\EventListener\CreateShipmentListener: '@?izi\prestashop\EventListener\CreateShipmentListener' + izi\prestashop\Mail\EventListener\ReplaceOrderNotificationRecipientListener: '@izi\prestashop\Mail\EventListener\ReplaceOrderNotificationRecipientListener' + izi\prestashop\Analytics\EventListener\UpdateBasketAnalyticsListener: '@izi\prestashop\Analytics\EventListener\UpdateBasketAnalyticsListener' + + izi\prestashop\Event\EventDispatcherFactory: + class: izi\prestashop\Event\EventDispatcherFactory + arguments: + - '@inpost.izi.event_subscriber_locator' + + inpost.izi.constraint_validator_locator: + class: Symfony\Component\DependencyInjection\ServiceLocator + tags: [ container.service_locator ] + arguments: + - izi\prestashop\Validator\Cart\BindableValidator: '@?izi\prestashop\Validator\Cart\BindableValidator' + izi\prestashop\Validator\Cart\HasProductsValidator: '@?izi\prestashop\Validator\Cart\HasProductsValidator' + izi\prestashop\Validator\Cart\PaymentInCurrencyAvailableValidator: '@?izi\prestashop\Validator\Cart\PaymentInCurrencyAvailableValidator' + izi\prestashop\Validator\Product\NotInRestrictedCategoryValidator: '@izi\prestashop\Validator\Product\NotInRestrictedCategoryValidator' + izi\prestashop\Validator\Product\NotFromRestrictedManufacturerValidator: '@izi\prestashop\Validator\Product\NotFromRestrictedManufacturerValidator' + izi\prestashop\Validator\Product\NotWithRestrictedAttributesValidator: '@izi\prestashop\Validator\Product\NotWithRestrictedAttributesValidator' + izi\prestashop\Validator\Product\NotWithRestrictedFeaturesValidator: '@izi\prestashop\Validator\Product\NotWithRestrictedFeaturesValidator' + izi\prestashop\Validator\Product\UnrestrictedValidator: '@izi\prestashop\Validator\Product\UnrestrictedValidator' + + inpost.izi.gui_configuration_locator: + class: Symfony\Component\DependencyInjection\ServiceLocator + tags: [ container.service_locator ] + arguments: + - validator: '@inpost.izi.validator' + context: '@inpost.izi.shop_context' + izi\prestashop\Repository\ProductRestrictionsRepositoryInterface: '@izi\prestashop\Repository\ProductRestrictionsRepositoryInterface' diff --git a/modules/inpostizi/config/webservice/services.yml b/modules/inpostizi/config/webservice/services.yml new file mode 100644 index 00000000..ee7f5405 --- /dev/null +++ b/modules/inpostizi/config/webservice/services.yml @@ -0,0 +1,48 @@ +imports: + - { resource: ../services/sf34.yml } + +services: + izi\prestashop\CommandBus: + class: izi\prestashop\CommandBus + arguments: + - '@inpost.izi.command_handler_locator' + + inpost.izi.command_handler_locator: + class: Symfony\Component\DependencyInjection\ServiceLocator + tags: [ container.service_locator ] + arguments: + - izi\prestashop\Command\UpdateOrderTrackingNumbersCommand: '@izi\prestashop\Handler\UpdateOrderTrackingNumbersHandlerInterface' + izi\prestashop\Command\UpdateOrderStatusCommand: '@izi\prestashop\Handler\UpdateOrderStatusHandlerInterface' + izi\prestashop\Command\UpdateBasketCommand: '@izi\prestashop\Handler\UpdateBasketHandlerInterface' + izi\prestashop\Command\UnbindBasketCommand: '@izi\prestashop\Handler\UnbindBasketHandlerInterface' + izi\prestashop\Command\UpdateOrderAddressDeliveryCommand: '@izi\prestashop\Handler\UpdateOrderAddressDeliveryHandlerInterface' + izi\prestashop\HotProduct\Message\DeleteRemoteProductCommand: '@izi\prestashop\HotProduct\MessageHandler\DeleteRemoteProductHandlerInterface' + izi\prestashop\HotProduct\Message\UpdateHotProductCommand: '@izi\prestashop\HotProduct\MessageHandler\UpdateHotProductHandlerInterface' + + inpost.izi.hook_locator: + class: Symfony\Component\DependencyInjection\ServiceLocator + tags: [ container.service_locator ] + arguments: + - actionObjectCartDeleteBefore: '@izi\prestashop\Hook\Common\ActionCartDeleteBefore' + actionObjectCartUpdateAfter: '@izi\prestashop\Hook\Common\ActionCartUpdateAfter' + actionObjectInPostShipmentModelAddAfter: '@izi\prestashop\Hook\Common\ActionShipmentAddAfter' + actionObjectInPostShipmentModelUpdateBefore: '@izi\prestashop\Hook\Common\ActionShipmentUpdateBefore' + actionObjectInPostShipmentModelUpdateAfter: '@izi\prestashop\Hook\Common\ActionShipmentUpdateAfter' + actionValidateOrder: '@izi\prestashop\Hook\Common\ActionValidateOrder' + actionOrderStatusPostUpdate: '@izi\prestashop\Hook\Common\ActionOrderStatusPostUpdate' + actionObjectOrderUpdateBefore: '@izi\prestashop\Hook\Common\ActionObjectOrderUpdateBefore' + actionObjectOrderUpdateAfter: '@izi\prestashop\Hook\Common\ActionObjectOrderUpdateAfter' + actionEmailSendBefore: '@izi\prestashop\Hook\Common\ActionEmailSendBefore' + # products + actionObjectProductDeleteBefore: '@izi\prestashop\Hook\Common\Product\ActionProductDeleteBefore' + actionObjectProductDeleteAfter: '@izi\prestashop\Hook\Common\Product\ActionProductDeleteAfter' + actionObjectProductUpdateAfter: '@izi\prestashop\Hook\Common\Product\ActionProductUpdateAfter' + actionObjectCombinationDeleteBefore: '@izi\prestashop\Hook\Common\Product\ActionCombinationDeleteBefore' + actionObjectCombinationDeleteAfter: '@izi\prestashop\Hook\Common\Product\ActionCombinationDeleteAfter' + actionObjectCombinationUpdateAfter: '@izi\prestashop\Hook\Common\Product\ActionCombinationUpdateAfter' + actionObjectImageAddAfter: '@izi\prestashop\Hook\Common\Product\ActionImageAddAfter' + actionObjectImageDeleteAfter: '@izi\prestashop\Hook\Common\Product\ActionImageDeleteAfter' + actionObjectSpecificPriceAddAfter: '@izi\prestashop\Hook\Common\Product\ActionSpecificPriceAddAfter' + actionObjectSpecificPriceUpdateAfter: '@izi\prestashop\Hook\Common\Product\ActionSpecificPriceUpdateAfter' + actionObjectSpecificPriceDeleteAfter: '@izi\prestashop\Hook\Common\Product\ActionSpecificPriceDeleteAfter' + actionUpdateQuantity: '@izi\prestashop\Hook\Common\Product\ActionUpdateQuantity' diff --git a/modules/inpostizi/config_pl.xml b/modules/inpostizi/config_pl.xml new file mode 100644 index 00000000..6bd454a2 --- /dev/null +++ b/modules/inpostizi/config_pl.xml @@ -0,0 +1,12 @@ + + + inpostizi + + + + + + 1 + 1 + + \ No newline at end of file diff --git a/modules/inpostizi/controllers/front/backend.php b/modules/inpostizi/controllers/front/backend.php new file mode 100644 index 00000000..9a42112e --- /dev/null +++ b/modules/inpostizi/controllers/front/backend.php @@ -0,0 +1,421 @@ + '/inpost/v2/izi/merchant/basket/binding-key', + 'methods' => ['GET'], + 'controller' => [WidgetController::class, 'getBindingKey'], + ], + [ + 'path' => '/inpost/v2/izi/merchant/order/confirmation-url', + 'methods' => ['GET'], + 'controller' => [WidgetController::class, 'getOrderConfirmationUrl'], + ], + ]; + + private const API_ROUTES = [ + [ + 'path' => '/inpost/v1/izi/products', + 'methods' => ['GET'], + 'controller' => [ProductController::class, 'getProducts'], + ], + [ + 'path' => '/inpost/v1/izi/order', + 'methods' => ['POST'], + 'controller' => [OrderController::class, 'create'], + ], + [ + 'path' => '/inpost/v1/izi/order/{orderId}', + 'methods' => ['GET'], + 'prefix' => '/inpost/v1/izi/order/', + 'regex' => '#^/inpost/v1/izi/order/(?\d+)$#', + 'controller' => [OrderController::class, 'get'], + ], + [ + 'path' => '/inpost/v1/izi/order/{orderId}/event', + 'methods' => ['POST'], + 'prefix' => '/inpost/v1/izi/order/', + 'regex' => '#^/inpost/v1/izi/order/(?\d+)/event$#', + 'controller' => [OrderController::class, 'update'], + ], + [ + 'path' => '/inpost/v1/izi/basket/{basketId}', + 'methods' => ['GET'], + 'prefix' => '/inpost/v1/izi/basket/', + 'regex' => '#^/inpost/v1/izi/basket/(?.+)$#', + 'controller' => [BasketController::class, 'get'], + ], + [ + 'path' => '/inpost/v1/izi/basket/{basketId}/confirmation', + 'methods' => ['POST'], + 'prefix' => '/inpost/v1/izi/basket/', + 'regex' => '#^/inpost/v1/izi/basket/(?.+)/confirmation$#', + 'controller' => [BasketController::class, 'confirm'], + ], + [ + 'path' => '/inpost/v1/izi/basket/{basketId}/event', + 'methods' => ['POST'], + 'prefix' => '/inpost/v1/izi/basket/', + 'regex' => '#^/inpost/v1/izi/basket/(?.+)/event$#', + 'controller' => [BasketController::class, 'update'], + ], + [ + 'path' => '/inpost/v1/izi/basket/{basketId}/binding', + 'methods' => ['DELETE'], + 'prefix' => '/inpost/v1/izi/basket/', + 'regex' => '#^/inpost/v1/izi/basket/(?.+)/binding$#', + 'controller' => [BasketController::class, 'deleteBinding'], + ], + [ + 'path' => '/inpost/v1/izi/basket/{basketId}/binding/delete', + 'methods' => ['POST'], + 'prefix' => '/inpost/v1/izi/basket/', + 'regex' => '#^/inpost/v1/izi/basket/(?.+)/binding/delete$#', + 'controller' => [BasketController::class, 'deleteBinding'], + ], + [ + 'path' => '/inpost/v1/izi/basket/product/{productId}', + 'methods' => ['POST'], + 'prefix' => '/inpost/v1/izi/basket/product/', + 'regex' => '#^/inpost/v1/izi/basket/product/(?.+)$#', + 'controller' => [BasketController::class, 'addProduct'], + ], + ]; + + /** + * @var InPostIzi + */ + public $module; + + protected $content_only = true; + + private $outputBuffer = ''; + + public function postProcess() + { + $this->registerErrorHandler(); + ob_start([$this, 'handleOutput'], 1); + + $request = $this->module->getCurrentRequest(); + $request->attributes->set('_inpost_izi_shop_id', (int) $this->context->shop->id); + + $response = $this->handle($request); + Response::closeOutputBuffers(0, false); + $response->send(); + + exit; + } + + /** + * @param Country $defaultCountry + */ + protected function geolocationManagement($defaultCountry): bool + { + return false; + } + + protected function displayRestrictedCountryPage(): void + { + } + + // TODO use own Sf Kernel? + private function handle(Request $request): Response + { + $path = $this->getPath($request); + + try { + if (!str_contains($path, '/merchant/')) { + return $this->handleApiRequest($request, $path); + } + + $response = $this->handleCustomerRequest($request, $path); + $response->prepare($request); + + $this->context->cookie->write(); + + return $response; + } catch (Throwable $throwable) { + http_response_code(500); + $this->logError($throwable); + + throw $throwable; + } + } + + private function handleCustomerRequest(Request $request, string $path): Response + { + [$controller, $params] = $this->resolveController($request, $path, self::MERCHANT_ROUTES); + + return null === $controller + ? $this->createNotFoundResponse($request, $path) + : $this->callController($controller, $request, $params); + } + + private function handleApiRequest(Request $request, string $path): Response + { + $method = $request->getMethod(); + + /** @var LoggerInterface $logger */ + $logger = $this->module->get('inpost.izi.merchant_api_logger'); + $logger->info('Request "{method} {path}"', [ + 'method' => $method, + 'path' => $path, + ]); + + if ($body = $request->getContent()) { + $logger->debug('Request body: "{body}"', ['body' => $body]); + } + + try { + $this->module->get(MerchantApiAuthenticator::class)->authenticate($request); + [$controller, $params] = $this->resolveController($request, $path, self::API_ROUTES); + + /** @var JsonResponse $response */ + $response = null === $controller + ? $this->createNotFoundResponse($request, $path, true) + : $this->callController($controller, $request, $params); + } catch (Throwable $throwable) { + $response = $this->handleApiError($throwable); + } + + $logger->info('Response "{method} {path}": {status_code}', [ + 'method' => $method, + 'path' => $path, + 'status_code' => $response->getStatusCode(), + ]); + + if (!$response->isSuccessful()) { + $logger->error('Response body: "{body}"', ['body' => $response->getContent()]); + } elseif ($body = $response->getContent()) { + $logger->debug('Response body: "{body}"', ['body' => $body]); + } + + $response->headers->set(ModuleVersionInfoProvidingClient::HEADER_NAME, $this->module->version); + + return $response; + } + + private function handleApiError(Throwable $throwable): Response + { + $exception = $this->convertApiError($throwable); + + if ($exception instanceof InternalServerErrorException && $previous = $exception->getPrevious()) { + $this->logError($previous); + } + + return new JsonResponse([ + 'error_code' => $exception->getErrorCode(), + 'error_message' => $exception->getMessage(), + ], $exception->getStatusCode()); + } + + private function convertApiError(Throwable $throwable): ApiException + { + if ($throwable instanceof ApiException) { + return $throwable; + } + + if ($throwable instanceof NetworkExceptionInterface) { + $message = $throwable instanceof OAuth2ExceptionInterface + ? 'Could not connect to the authorization server' + : 'Could not connect to the basket app API'; + + return BadGatewayException::create($throwable, $message); + } + + if ($throwable instanceof OAuth2ExceptionInterface) { + return BadGatewayException::create($throwable, 'Could not obtain access token'); + } + + if ($throwable instanceof BasketAppException) { + return BadGatewayException::create($throwable, sprintf('Basket app API error: "%s"', $throwable->getError()->getCode())); + } + + if ($throwable instanceof ServerException && 503 === $throwable->getCode()) { + return ServiceUnavailableException::create($throwable, 'Basket app API is unavailable'); + } + + if ($throwable instanceof HttpExceptionInterface) { + return BadGatewayException::create($throwable, sprintf('Unexpected basket app API response status code: %d', $throwable->getCode())); + } + + return InternalServerErrorException::create($throwable); + } + + private function getPath(Request $request): string + { + if (null === $path = $request->query->get('path')) { + return '/'; + } + + $path = rawurldecode($path); + + if ('/' !== $path[0]) { + $path = '/' . $path; + } + + return rtrim($path, '/'); + } + + private function logError(Throwable $throwable): void + { + $this->module->getLogger()->error('Error processing request: {error}', ['error' => $throwable]); + } + + private function resolveController(Request $request, string $path, array $routes): array + { + $method = $request->getMethod(); + + foreach ($routes as $route) { + if (isset($route['methods']) && !in_array($method, $route['methods'], true)) { + continue; + } + + if (isset($route['prefix']) && !str_starts_with($path, $route['prefix'])) { + continue; + } + + if (!isset($route['regex']) && $path !== $route['path']) { + continue; + } + + if (isset($route['regex']) && !preg_match($route['regex'], $path, $params)) { + continue; + } + + return [$route['controller'], $params ?? []]; + } + + return [null, []]; + } + + private function callController(array $controller, Request $request, array $pathParams): Response + { + $arguments = $this->resolveControllerArguments($controller, $request, $pathParams); + $controller = [$this->createController($controller[0]), $controller[1]]; + + return $controller(...$arguments); + } + + private function createController(string $className) + { + try { + return $this->module->get($className); + } catch (ServiceNotFoundException $e) { + return new $className(); + } + } + + private function resolveControllerArguments(array $controller, Request $request, array $pathParams): array + { + $reflection = new ReflectionMethod($controller[0], $controller[1]); + + return array_map(function (ReflectionParameter $param) use ($request, $pathParams) { + return $this->resolveControllerArgument($param, $request, $pathParams); + }, $reflection->getParameters()); + } + + private function resolveControllerArgument(ReflectionParameter $param, Request $request, array $pathParams) + { + $type = $param->getType(); + + if (null !== $type && Request::class === $type->getName()) { + return $request; + } + + $paramName = $param->getName(); + + if (isset($pathParams[$paramName])) { + return $pathParams[$paramName]; + } + + if ($param->isDefaultValueAvailable()) { + return $param->getDefaultValue(); + } + + throw new LogicException(sprintf('Cannot determine controller parameter value for argument "%s".', $paramName)); + } + + private function createNotFoundResponse(Request $request, string $path, bool $json = false): Response + { + $message = sprintf('No route found for "%s %s"', $request->getMethod(), $path); + + return $json || in_array('application/json', $request->getAcceptableContentTypes(), true) + ? new JsonResponse([ + 'error_code' => 'NOT_FOUND', + 'error_message' => $message, + ], 404) + : new Response($message, 404); + } + + private function registerErrorHandler(): void + { + $handler = class_exists(ErrorHandler::class) + ? ErrorHandler::register() + : LegacyErrorHandler::register(); + + $handler->throwAt(E_ERROR, true); + $handler->setDefaultLogger($this->module->getLogger(), [ + E_DEPRECATED => LogLevel::DEBUG, + E_NOTICE => LogLevel::DEBUG, + E_STRICT => LogLevel::DEBUG, + E_WARNING => LogLevel::DEBUG, + E_USER_WARNING => LogLevel::DEBUG, + E_USER_ERROR => LogLevel::CRITICAL, + E_RECOVERABLE_ERROR => LogLevel::CRITICAL, + E_ERROR => LogLevel::CRITICAL, + ]); + } + + private function handleOutput(string $buffer, int $phase): string + { + if (PHP_OUTPUT_HANDLER_FINAL & $phase && '' !== $this->outputBuffer) { + $this->module->getLogger()->warning('Output buffer content: "{buffer}".', [ + 'buffer' => $this->outputBuffer, + ]); + + $this->outputBuffer = ''; + } + + if ('' === $buffer) { + return ''; + } + + $this->outputBuffer .= $buffer; + + if (PHP_OUTPUT_HANDLER_START & $phase) { + $this->module->getLogger()->warning(sprintf( + "Output started before sending response.\n[stacktrace]\n%s\n", + (new \Exception())->getTraceAsString() + )); + } + + return $buffer; + } +} diff --git a/modules/inpostizi/inpostizi.php b/modules/inpostizi/inpostizi.php new file mode 100644 index 00000000..d10c3dd6 --- /dev/null +++ b/modules/inpostizi/inpostizi.php @@ -0,0 +1,644 @@ +name = 'inpostizi'; + $this->version = '2.2.3'; + $this->author = 'InPost S.A.'; + $this->tab = 'payments_gateways'; + + $this->ps_versions_compliancy = [ + 'min' => '1.7.1.0', + 'max' => '8.2.99', + ]; + + parent::__construct(); + + $this->displayName = $this->l('InPost Pay'); + } + + /** + * @return false + */ + public function isUsingNewTranslationSystem() + { + return false; + } + + /** + * @return bool + */ + public function install() + { + if (70103 > PHP_VERSION_ID) { + $this->_errors[] = $this->l('This module requires PHP 7.1.3 or later.'); + + return false; + } + + try { + (new DatabaseInstaller())->install($this); + } catch (Exception $e) { + $this->_errors[] = $this->l('Could not update the database schema.'); + $this->getLogger()->critical('Installer error: {exception}.', [ + 'exception' => $e, + ]); + + return false; + } + + $this->setUpRoutingLoaderResolver(); + + return parent::install() + && $this->registerHook(HookExecutor::getHooksToInstall(_PS_VERSION_)); + } + + /** + * @return bool + */ + public function uninstall() + { + $this->setUpRoutingLoaderResolver(); + + return parent::uninstall(); + } + + /** + * @return array + */ + public function runUpgradeModule() + { + try { + $result = parent::runUpgradeModule(); + + if (!$result['available_upgrade']) { + return $result; + } + + if ($result['success']) { + $this->getLogger()->info('Upgraded module to version {version}.', [ + 'version' => $result['upgraded_to'], + ]); + } else { + $this->getLogger()->error('Could not upgrade module.', [ + 'details' => $result, + ]); + } + + return $result; + } catch (Throwable $e) { + $this->getLogger()->critical('Upgrade error: {exception}.', [ + 'exception' => $e, + ]); + + throw $e; + } + } + + /** + * @param int[] $shops shop IDs + * + * @return bool + */ + public function addCheckboxCurrencyRestrictionsForModule(array $shops = []) + { + if ([] === $shops) { + $shops = Shop::getShops(true, null, true); + } + + $data = []; + + foreach ($shops as $shopId) { + foreach (Currency::cases() as $currency) { + if (0 >= $currencyId = (int) \Currency::getIdByIsoCode($currency->value, $shopId)) { + continue; + } + + $data[] = [ + 'id_module' => (int) $this->id, + 'id_shop' => (int) $shopId, + 'id_currency' => $currencyId, + ]; + } + } + + return Db::getInstance()->insert('module_currency', $data); + } + + public function getContent() + { + if ($this->isContainerCacheStale() && $this->isContainerConfigLoaded()) { + $this->clearCacheAndRedirectBackToConfigPage(); + } + + try { + /** @var UrlGeneratorInterface $router */ + $router = $this->get('router'); + + Tools::redirectAdmin($router->generate('admin_inpost_izi_config_general')); + } catch (ServiceNotFoundException $e) { + $this->handleConfigPageRequest(); + } catch (RouteNotFoundException $e) { + if ($this->isContainerConfigLoaded()) { + throw $e; + } + + $this->addFlash($this->l('To access the configuration page, the module must be active.')); + Tools::redirectAdmin($router->generate('admin_module_manage')); + } + } + + /** + * Handles hook calls. + * + * @param string $methodName + * + * @return mixed hook result + */ + public function __call($methodName, array $arguments) + { + $hookName = 0 === strpos($methodName, 'hook') + ? lcfirst(Tools::substr($methodName, 4)) + : $methodName; + + try { + $parameters = $this->normalizeHookParameters(isset($arguments[0]) ? $arguments[0] : []); + + return $this + ->get(HookExecutorInterface::class) + ->execute($hookName, $parameters); + } catch (ModuleErrorInterface $e) { + throw $e; + } catch (HookNotImplementedException $e) { + $this->getLogger()->warning('Hook "{hookName}" is not implemented.', [ + 'hookName' => $e->getHookName(), + ]); + + return null; + } catch (Throwable $e) { + if ($this->isContainerCacheRelated($e)) { + $this->getLogger()->error('Error executing hook "{hookName}": container cache is stale.', [ + 'hookName' => $hookName, + ]); + + return null; + } + + $this->getLogger()->critical('Error executing hook "{hookName}": {exception}', [ + 'hookName' => $hookName, + 'exception' => $e, + ]); + + if (_PS_MODE_DEV_ && $this->isDebugEnabled()) { + throw $e; + } + + return null; + } + } + + /** + * @template T + * + * @param string|class-string $serviceName + * + * @phpstan-return ($serviceName is class-string ? T : object) + * + * @return T|object + */ + public function get($serviceName) + { + return $this->doGetContainer()->get($serviceName); + } + + /** + * @param string|null $hookName + * + * @return string + */ + public function renderWidget($hookName, array $configuration) + { + $configuration = $this->normalizeHookParameters($configuration); + + return $this->getWidget()->renderWidget($hookName, $configuration); + } + + /** + * @param string|null $hookName + * + * @return array + */ + public function getWidgetVariables($hookName, array $configuration) + { + $configuration = $this->normalizeHookParameters($configuration); + + return $this->getWidget()->getWidgetVariables($hookName, $configuration); + } + + /** + * @return Request + * + * @internal + */ + public function getCurrentRequest() + { + return $this->getRequestStack()->getCurrentRequest() ?: Request::createFromGlobals(); + } + + /** + * @return RequestStack + * + * @internal + */ + public function getRequestStack() + { + if (isset($this->requestStack)) { + return $this->requestStack; + } + + try { + /** @var RequestStack $requestStack */ + $requestStack = $this->get('request_stack'); + if (null !== $requestStack->getCurrentRequest()) { + return $this->requestStack = $requestStack; + } + } catch (ServiceNotFoundException $e) { + } + + $this->requestStack = new RequestStack(); + $this->requestStack->push(Request::createFromGlobals()); + + return $this->requestStack; + } + + /** + * @return LoggerInterface + * + * @internal + */ + public function getLogger() + { + try { + return $this->get(self::$loggerServiceId); + } catch (ContainerNotFoundException $e) { + return $this->getLegacyContainer()->get(self::$loggerServiceId); + } catch (Exception $e) { + return new Psr\Log\NullLogger(); + } + } + + /** + * @return ContainerInterface + */ + private function doGetContainer() + { + if (Tools::version_compare(_PS_VERSION_, '1.7.6')) { + return $this->getLegacyContainer(); + } + + if (Tools::version_compare(_PS_VERSION_, '1.7.7')) { + return $this->getPS176Container(); + } + + if (null !== $container = SymfonyContainer::getInstance()) { + return $container; + } + + try { + return $this->getContainer(); + } catch (PrestaShopContainerNotFoundException $e) { + return $this->getFrontOfficeLegacyContainer(); + } + } + + /** + * @return ContainerInterface + */ + private function getLegacyContainer() + { + if (!isset($this->legacyContainer)) { + $this->legacyContainer = $this->createContainer(); + } + + return $this->legacyContainer; + } + + /** + * @return ContainerInterface + */ + private function createContainer() + { + $cacheDir = sprintf('%s/inpost/izi/', rtrim(_PS_CACHE_DIR_, '/')); + + if (Tools::version_compare(_PS_VERSION_, '1.7.4')) { + $className = sprintf('InPost\\Izi\\Container_%s', str_replace('.', '_', $this->version)); + $resources = $this->getSf28ConfigResources(); + } else { + $type = $this->context->controller instanceof AdminControllerCore ? 'admin' : 'front'; + $className = sprintf('InPost\\Izi\\%sContainer_%s', ucfirst($type), str_replace('.', '_', $this->version)); + $resources = [sprintf('%s/config/%s/services.yml', rtrim($this->getLocalPath(), '/'), $type)]; + } + + return (new ContainerFactory($cacheDir))->create($className, $resources); + } + + /** + * @return iterable + */ + private function getSf28ConfigResources() + { + yield sprintf('%s/config/services/sf28.yml', rtrim($this->getLocalPath(), '/')); + yield static function (ContainerBuilder $container) { + $container->addResource(new FileResource(__FILE__)); + $container->addCompilerPass(new RegisterListenersPass('inpost.izi.event_dispatcher'), PassConfig::TYPE_BEFORE_REMOVING); + $container->addCompilerPass(new ProvideServiceLocatorFactoriesPass('inpost.izi.service_locator')); + $container->addCompilerPass(new TaggedIteratorsCollectorPass()); + AnalyzeServiceReferencesPass::decorateRemovingPasses($container, 'inpost.izi.service_locator'); + }; + } + + /** + * Accesses the public "routing.loader" service to provide a @see \Symfony\Component\Config\Loader\LoaderResolverInterface + * to the routing configuration loader used by @see \PrestaShop\PrestaShop\Adapter\Module\Tab\ModuleTabRegister + */ + private function setUpRoutingLoaderResolver() + { + if (Tools::version_compare(_PS_VERSION_, '1.7.7')) { + return; + } + + try { + $this->get('routing.loader'); + } catch (Exception $e) { + // ignore silently + } + } + + private function handleConfigPageRequest() + { + $request = $this->getCurrentRequest(); + $request->query->remove('controllerUri'); + + $kernel = $this->getAdminKernel(); + $response = $kernel->handle($request); + + $this->context->cookie->write(); + $response->send(); + + if ($kernel instanceof TerminableInterface) { + $kernel->terminate($request, $response); + } + + exit; + } + + /** + * @return KernelInterface + */ + private function getAdminKernel() + { + if (isset($this->adminKernel)) { + return $this->adminKernel; + } + + global $kernel; + + if (!$kernel instanceof KernelInterface) { + throw new RuntimeException('PS application kernel instance was not found.'); + } + + $this->adminKernel = new AdminKernel($kernel, _PS_VERSION_); + $this->adminKernel->boot(); + + $psContainer = $kernel->getContainer(); + $this->adminKernel->getContainer()->set('prestashop.security.admin.provider', $psContainer->get('prestashop.security.admin.provider')); + + // In some very early 1.7 versions, the session may not have yet been started by PS application. + $psContainer->get('session')->start(); + + return $this->adminKernel; + } + + /** + * @return bool + */ + private function isDebugEnabled() + { + try { + return $this->get(AdvancedConfigurationInterface::class)->isDebugEnabled(); + } catch (Exception $e) { + return false; + } + } + + /** + * {@see \Module::get()} does not check if {@see \Controller::$container} is set on PS 1.7.6. + * + * @return ContainerInterface + */ + private function getPS176Container() + { + if (isset($this->context->container)) { + return $this->context->container; + } + + if (null !== $container = SymfonyContainer::getInstance()) { + return $container; + } + + if ($this->context->controller instanceof Controller && null !== $container = $this->context->controller->getContainer()) { + return $container; + } + + return $this->getFrontOfficeLegacyContainer(); + } + + /** + * Access container before {@see \FrontController::$container} is set in {@see \Controller::init()}. + * + * @return ContainerInterface + */ + private function getFrontOfficeLegacyContainer() + { + if (!$this->context->controller instanceof FrontController || !class_exists(PrestaShopContainerBuilder::class)) { + throw ContainerNotFoundException::create(); + } + + try { + return PrestaShopContainerBuilder::getContainer('front', _PS_MODE_DEV_); + } catch (Exception $e) { + throw ContainerNotFoundException::create($e); + } + } + + /** + * @return array + */ + private function normalizeHookParameters(array $parameters) + { + if (!isset($parameters['request'])) { + $parameters['request'] = $this->getCurrentRequest(); + } + + if (!isset($parameters['cart'])) { + $parameters['cart'] = $this->context->cart; + } + + return $parameters; + } + + /** + * @return WidgetInterface + */ + private function getWidget() + { + if (isset($this->widget)) { + return $this->widget; + } + + return $this->widget = $this->get('inpost.izi.widget'); + } + + /** + * @return bool + */ + private function isContainerCacheRelated(Throwable $e) + { + if (!$e instanceof HookNotFoundException && !$e instanceof ServiceNotFoundException && !$e instanceof TypeError) { + return false; + } + + return $this->isContainerCacheStale(); + } + + /** + * @return bool + */ + private function isContainerCacheStale() + { + try { + $container = $this->doGetContainer(); + } catch (ContainerNotFoundException $e) { + return false; // fingers crossed + } + + if (!$container->hasParameter('inpost.izi.container_version')) { + return true; + } + + return $this->version !== $container->getParameter('inpost.izi.container_version'); + } + + /** + * @return bool whether PS automatically loads the module's container configuration + */ + private function isContainerConfigLoaded() + { + if ($this->active) { + return true; + } + + if (Tools::version_compare(_PS_VERSION_, '8.0.0')) { + return true; + } + + return $this->hasShopAssociations(); + } + + private function clearCacheAndRedirectBackToConfigPage() + { + if (Tools::getValue('cache_cleared')) { + $this->addFlash($this->l('An attempt to clear the cache may have failed. Please try to clear the cache manually.')); + + return; + } + + $this->getLogger()->warning('Container cache is stale, attempting to clear.'); + + Tools::clearSf2Cache(); + Tools::redirectAdmin($this->context->link->getAdminLink('AdminModules', true, null, [ + 'configure' => $this->name, + 'cache_cleared' => true, + ])); + } + + /** + * @param string $message + * @param string $type + */ + private function addFlash($message, $type = 'error') + { + try { + /** @var Session $session */ + $session = $this->get('session'); + $session->getFlashBag()->add($type, $message); + } catch (ServiceNotFoundException $e) { + // ignore silently + } + } +} diff --git a/modules/inpostizi/logo.png b/modules/inpostizi/logo.png new file mode 100644 index 00000000..88384053 Binary files /dev/null and b/modules/inpostizi/logo.png differ diff --git a/modules/inpostizi/logo.webp b/modules/inpostizi/logo.webp new file mode 100644 index 00000000..1d33b1f4 Binary files /dev/null and b/modules/inpostizi/logo.webp differ diff --git a/modules/inpostizi/src/AdminKernel.php b/modules/inpostizi/src/AdminKernel.php new file mode 100644 index 00000000..cf374813 --- /dev/null +++ b/modules/inpostizi/src/AdminKernel.php @@ -0,0 +1,155 @@ +rootDir = $kernel->getRootDir(); + $this->logDir = $kernel->getLogDir() . '/inpost/izi'; + $this->cacheDir = $kernel->getCacheDir() . '/inpost/izi'; + $this->secret = $kernel->getContainer()->getParameter('kernel.secret'); + + $this->psVersion = $psVersion; + $this->name = 'inpostizi'; + + parent::__construct($kernel->getEnvironment(), $kernel->isDebug()); + } + + public function registerBundles(): iterable + { + yield new FrameworkBundle(); + yield new TwigBundle(); + yield new SecurityBundle(); + + if (class_exists(SensioFrameworkExtraBundle::class)) { + yield new SensioFrameworkExtraBundle(); + } + } + + /** + * @override for the purpose of locating Twig templates included by PS using legacy bundle path syntax. + */ + public function getBundle($name, $first = true) + { + if ('PrestaShopBundle' === $name) { + $bundle = $this->getPrestaShopBundle(); + + return $first ? $bundle : [$bundle]; + } + + return parent::getBundle(...func_get_args()); + } + + public function getLogDir(): string + { + return $this->logDir; + } + + public function getCacheDir(): string + { + return $this->cacheDir; + } + + protected function configureRoutes(RouteCollectionBuilder $routes): void + { + $configDir = $this->getConfigDir(); + + $routes->import(sprintf('%s/routes.yml', $configDir)); + $routes->addRoute($this->getConfigIndexRoute(), 'admin_inpost_izi_config_general'); + } + + protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void + { + $configDir = $this->getConfigDir(); + + $loader->load(sprintf('%s/services.yml', $configDir)); + $loader->load(__DIR__ . '/Resources/config/admin.yml'); + + $container->loadFromExtension('framework', [ + 'secret' => $this->secret, + ]); + + if (\Tools::version_compare($this->psVersion, '1.7.4', '>=')) { + $loader->load(sprintf('%s/services/sf34.yml', $configDir)); + } else { + $loader->load(sprintf('%s/services/sf28.yml', $configDir)); + $loader->load(__DIR__ . '/Resources/config/admin28.yml'); + + $container->addCompilerPass(new ProvideServiceLocatorFactoriesPass('inpost.izi.service_locator')); + AnalyzeServiceReferencesPass::decorateRemovingPasses($container, 'inpost.izi.service_locator'); + $container->addCompilerPass(new TaggedIteratorsCollectorPass()); + } + } + + protected function dumpContainer(ConfigCache $cache, ContainerBuilder $container, $class, $baseClass): void + { + if (\Tools::version_compare($this->psVersion, '1.7.4', '>=')) { + parent::dumpContainer(...func_get_args()); + + return; + } + + $dumper = new PhpDumper($container); + + $content = $dumper->dump([ + 'class' => $class, + 'base_class' => $baseClass, + 'file' => $cache->getPath(), + 'debug' => $this->debug, + ]); + + $cache->write($content, $container->getResources()); + } + + private function getPrestaShopBundle(): BundleInterface + { + return $this->prestaShopBundle ?? ($this->prestaShopBundle = new PrestaShopBundle()); + } + + private function getConfigIndexRoute(): Route + { + return (new Route('/')) + ->setMethods(['GET', 'POST']) + ->setDefault('_controller', 'izi\prestashop\Controller\Admin\ConfigurationController:generalConfig'); + } + + private function getConfigDir(): string + { + return __DIR__ . '/../config'; + } +} diff --git a/modules/inpostizi/src/Analytics/BasketAnalytics.php b/modules/inpostizi/src/Analytics/BasketAnalytics.php new file mode 100644 index 00000000..806fd249 --- /dev/null +++ b/modules/inpostizi/src/Analytics/BasketAnalytics.php @@ -0,0 +1,61 @@ +cartId = $cartId; + $this->gclid = $gclid; + $this->fbclid = $fbclid; + $this->client_id = $client_id; + } + + public function getCartId(): int + { + return $this->cartId; + } + + public function getGclid(): ?string + { + return $this->gclid; + } + + public function getFbclid(): ?string + { + return $this->fbclid; + } + + public function getClientId(): ?string + { + return $this->client_id; + } + + public function isEmpty(): bool + { + return null === $this->gclid && null === $this->fbclid && null === $this->client_id; + } +} diff --git a/modules/inpostizi/src/Analytics/BasketAnalyticsInterface.php b/modules/inpostizi/src/Analytics/BasketAnalyticsInterface.php new file mode 100644 index 00000000..6498e331 --- /dev/null +++ b/modules/inpostizi/src/Analytics/BasketAnalyticsInterface.php @@ -0,0 +1,16 @@ +gclid = $gclid; + $this->fbclid = $fbclid; + $this->client_id = $client_id; + } + + public function getGclid(): ?string + { + return $this->gclid; + } + + public function getFbclid(): ?string + { + return $this->fbclid; + } + + public function getClientId(): ?string + { + return $this->client_id; + } + + public function isEmpty(): bool + { + return null === $this->gclid && null === $this->fbclid && null === $this->client_id; + } +} diff --git a/modules/inpostizi/src/Analytics/BasketAnalyticsRepository.php b/modules/inpostizi/src/Analytics/BasketAnalyticsRepository.php new file mode 100644 index 00000000..9ad5e99b --- /dev/null +++ b/modules/inpostizi/src/Analytics/BasketAnalyticsRepository.php @@ -0,0 +1,89 @@ +connection = $connection; + } + + public function add(BasketAnalytics $basketAnalytics): void + { + $this->connection->insert(self::TABLE_NAME, [ + 'cart_id' => $basketAnalytics->getCartId(), + 'gclid' => $basketAnalytics->getGclid(), + 'fbclid' => $basketAnalytics->getFbclid(), + 'client_id' => $basketAnalytics->getClientId(), + ]); + } + + public function find(int $id): ?BasketAnalyticsInterface + { + $qb = $this->createQueryBuilder()->where('cart_id = ' . $id); + + return $this->getOneOrNullResult($qb); + } + + public function remove(int $id): void + { + $this->connection->delete(self::TABLE_NAME, [ + 'cart_id' => $id, + ]); + } + + public function save(BasketAnalytics $basketAnalytics): void + { + $exists = $this->find($basketAnalytics->getCartId()); + + if (null === $exists) { + $this->add($basketAnalytics); + + return; + } + + $this->connection->update(self::TABLE_NAME, [ + 'gclid' => $basketAnalytics->getGclid(), + 'fbclid' => $basketAnalytics->getFbclid(), + 'client_id' => $basketAnalytics->getClientId(), + ], [ + 'cart_id' => $basketAnalytics->getCartId(), + ]); + } + + protected function createQueryBuilder(): \DbQuery + { + return (new \DbQuery())->from(self::TABLE_NAME); + } + + protected function getOneOrNullResult(\DbQuery $qb): ?BasketAnalytics + { + if (false === $row = $this->connection->fetchAssociative((string) $qb)) { + return null; + } + + return $this->hydrate($row); + } + + protected function hydrate(array $row): BasketAnalytics + { + return new BasketAnalytics( + (int)$row['cart_id'], + $row['gclid'], + $row['fbclid'], + $row['client_id'] + ); + } +} diff --git a/modules/inpostizi/src/Analytics/BasketAnalyticsRepositoryInterface.php b/modules/inpostizi/src/Analytics/BasketAnalyticsRepositoryInterface.php new file mode 100644 index 00000000..b4ec3691 --- /dev/null +++ b/modules/inpostizi/src/Analytics/BasketAnalyticsRepositoryInterface.php @@ -0,0 +1,14 @@ +cartId = $cartId; + $this->basketAnalytics = $basketAnalytics; + } + + public function getCartId(): int + { + return $this->cartId; + } + + public function getBasketAnalytics(): BasketAnalyticsInterface + { + return $this->basketAnalytics; + } +} diff --git a/modules/inpostizi/src/Analytics/Cookie/CookieEraserInterface.php b/modules/inpostizi/src/Analytics/Cookie/CookieEraserInterface.php new file mode 100644 index 00000000..2d9fb0e3 --- /dev/null +++ b/modules/inpostizi/src/Analytics/Cookie/CookieEraserInterface.php @@ -0,0 +1,12 @@ + + */ + private $erasers; + + /** + * @param iterable $erasers + */ + public function __construct(iterable $erasers) + { + $this->erasers = $erasers; + } + + public function erase(Request $request): void + { + foreach ($this->erasers as $eraser) { + $eraser->erase($request); + } + } +} diff --git a/modules/inpostizi/src/Analytics/Cookie/Executor/CookiePersisterExecutor.php b/modules/inpostizi/src/Analytics/Cookie/Executor/CookiePersisterExecutor.php new file mode 100644 index 00000000..242bc334 --- /dev/null +++ b/modules/inpostizi/src/Analytics/Cookie/Executor/CookiePersisterExecutor.php @@ -0,0 +1,31 @@ + + */ + private $persisters; + + /** + * @param iterable $persisters + */ + public function __construct(iterable $persisters) + { + $this->persisters = $persisters; + } + + public function persist(Request $request): void + { + foreach ($this->persisters as $persister) { + $persister->persist($request); + } + } +} diff --git a/modules/inpostizi/src/Analytics/Cookie/FacebookClickIdCookie.php b/modules/inpostizi/src/Analytics/Cookie/FacebookClickIdCookie.php new file mode 100644 index 00000000..b212aa97 --- /dev/null +++ b/modules/inpostizi/src/Analytics/Cookie/FacebookClickIdCookie.php @@ -0,0 +1,66 @@ +cookieFactory = $cookieFactory; + $this->cookieRepository = $cookieRepository; + } + + public function extract(Request $request): ?string + { + if ($request->cookies->has(self::COOKIE_NAME)) { + return $request->cookies->get(self::COOKIE_NAME); + } + + return null; + } + + public function persist(Request $request): void + { + $parameter = $request->query->get(self::PARAMETER); + + if (null === $parameter) { + return; + } + + $cookie = $this->cookieFactory->create(self::COOKIE_NAME, $parameter, time() + self::COOKIE_EXPIRE_TIME); + + $this->cookieRepository->persist($cookie); + } + + public function erase(Request $request): void + { + if ($request->cookies->has(self::COOKIE_NAME)) { + $cookie = $this->cookieFactory->create(self::COOKIE_NAME, '', -1); + + unset($_COOKIE[self::COOKIE_NAME]); + $this->cookieRepository->persist($cookie); + } + } +} diff --git a/modules/inpostizi/src/Analytics/Cookie/Factory/CookieFactory.php b/modules/inpostizi/src/Analytics/Cookie/Factory/CookieFactory.php new file mode 100644 index 00000000..6c02db8e --- /dev/null +++ b/modules/inpostizi/src/Analytics/Cookie/Factory/CookieFactory.php @@ -0,0 +1,30 @@ +cookieFactory = $cookieFactory; + $this->cookieRepository = $cookieRepository; + } + + public function extract(Request $request): ?string + { + if ($request->cookies->has(self::COOKIE_NAME)) { + return $request->cookies->get(self::COOKIE_NAME); + } + + return null; + } + + public function persist(Request $request): void + { + $parameter = $request->query->get(self::PARAMETER); + + if (null === $parameter) { + return; + } + + $cookie = $this->cookieFactory->create(self::COOKIE_NAME, $parameter, time() + self::COOKIE_EXPIRE_TIME); + + $this->cookieRepository->persist($cookie); + } + + public function erase(Request $request): void + { + if ($request->cookies->has(self::COOKIE_NAME)) { + $cookie = $this->cookieFactory->create(self::COOKIE_NAME, '', -1); + + unset($_COOKIE[self::COOKIE_NAME]); + $this->cookieRepository->persist($cookie); + } + } +} diff --git a/modules/inpostizi/src/Analytics/Cookie/GoogleClientIdCookie.php b/modules/inpostizi/src/Analytics/Cookie/GoogleClientIdCookie.php new file mode 100644 index 00000000..46993dcd --- /dev/null +++ b/modules/inpostizi/src/Analytics/Cookie/GoogleClientIdCookie.php @@ -0,0 +1,26 @@ +cookies->has(self::COOKIE_NAME)) { + $gaCookie = $request->cookies->get(self::COOKIE_NAME); + $parts = explode('.', $gaCookie); + + if (count($parts) >= 3) { + return implode('.', array_slice($parts, 2)); + } + } + + return null; + } +} diff --git a/modules/inpostizi/src/Analytics/Cookie/Repository/CookieRepository.php b/modules/inpostizi/src/Analytics/Cookie/Repository/CookieRepository.php new file mode 100644 index 00000000..ab35fb3b --- /dev/null +++ b/modules/inpostizi/src/Analytics/Cookie/Repository/CookieRepository.php @@ -0,0 +1,23 @@ +getName(), + $cookie->getValue(), + $cookie->getExpiresTime(), + $cookie->getPath(), + $cookie->getDomain() ?? '', + $cookie->isSecure(), + $cookie->isHttpOnly() + ); + } +} diff --git a/modules/inpostizi/src/Analytics/Cookie/Repository/CookieRepositoryInterface.php b/modules/inpostizi/src/Analytics/Cookie/Repository/CookieRepositoryInterface.php new file mode 100644 index 00000000..6376c852 --- /dev/null +++ b/modules/inpostizi/src/Analytics/Cookie/Repository/CookieRepositoryInterface.php @@ -0,0 +1,12 @@ +commandBus = $commandBus; + $this->requestStack = $requestStack; + $this->basketAnalyticsFactory = $basketAnalyticsFactory; + $this->cookieEraser = $cookieEraser; + $this->generalConfiguration = $generalConfiguration; + } + + public static function getSubscribedEvents(): array + { + return [ + CartUpdatedEvent::class => 'onCartUpdated', + ]; + } + + public function onCartUpdated(CartUpdatedEvent $event): void + { + $request = $this->requestStack->getCurrentRequest(); + + if (null === $request || !$this->generalConfiguration->isSendAnalyticsData()) { + return; + } + + $basketAnalytics = $this->basketAnalyticsFactory->createFromRequest($request); + + if ($basketAnalytics->isEmpty()) { + return; + } + + $this->commandBus->handle(new UpdateCartAnalyticsCommand((int) $event->getCart()->id, $basketAnalytics)); + $this->cookieEraser->erase($request); + } +} diff --git a/modules/inpostizi/src/Analytics/Factory/BasketAnalyticsFactory.php b/modules/inpostizi/src/Analytics/Factory/BasketAnalyticsFactory.php new file mode 100644 index 00000000..a681b617 --- /dev/null +++ b/modules/inpostizi/src/Analytics/Factory/BasketAnalyticsFactory.php @@ -0,0 +1,47 @@ +gclidExtractor = $gclidExtractor; + $this->fbclidExtractor = $fbclidExtractor; + $this->clientIdExtractor = $clientIdExtractor; + } + + public function createFromRequest(Request $request): BasketAnalyticsInterface + { + $gclid = $this->gclidExtractor->extract($request); + $fbclid = $this->fbclidExtractor->extract($request); + $clientId = $this->clientIdExtractor->extract($request); + + return new BasketAnalyticsParams($gclid, $fbclid, $clientId); + } +} diff --git a/modules/inpostizi/src/Analytics/Factory/BasketAnalyticsFactoryInterface.php b/modules/inpostizi/src/Analytics/Factory/BasketAnalyticsFactoryInterface.php new file mode 100644 index 00000000..bb49585a --- /dev/null +++ b/modules/inpostizi/src/Analytics/Factory/BasketAnalyticsFactoryInterface.php @@ -0,0 +1,13 @@ +repository = $repository; + } + + private function getNewFieldValue(?string $oldKey, ?string $newKey): ?string + { + if (null === $oldKey || $oldKey !== $newKey) { + return $newKey; + } + + return $oldKey; + } + + public function __invoke(UpdateCartAnalyticsCommand $command) + { + $currentBasketRepository = $this->repository->find($command->getCartId()); + + if (null === $currentBasketRepository) { + $basketAnalytics = new BasketAnalytics( + $command->getCartId(), + $command->getBasketAnalytics()->getGclid(), + $command->getBasketAnalytics()->getFbclid(), + $command->getBasketAnalytics()->getClientId() + ); + + $this->repository->save($basketAnalytics); + + return; + } + + $basketAnalytics = new BasketAnalytics( + $command->getCartId(), + $this->getNewFieldValue($currentBasketRepository->getGclid(), $command->getBasketAnalytics()->getGclid()), + $this->getNewFieldValue($currentBasketRepository->getFbclid(), $command->getBasketAnalytics()->getFbclid()), + $this->getNewFieldValue($currentBasketRepository->getClientId(), $command->getBasketAnalytics()->getClientId()) + ); + + $this->repository->save($basketAnalytics); + } +} diff --git a/modules/inpostizi/src/Analytics/Handler/UpdateCartAnalyticsHandlerInterface.php b/modules/inpostizi/src/Analytics/Handler/UpdateCartAnalyticsHandlerInterface.php new file mode 100644 index 00000000..a08a3c68 --- /dev/null +++ b/modules/inpostizi/src/Analytics/Handler/UpdateCartAnalyticsHandlerInterface.php @@ -0,0 +1,12 @@ +requestFactory = $requestFactory; + $this->streamFactory = $streamFactory; + $this->clientFactory = $clientFactory ?? new GuzzleClientFactory(); + } + + public function create(UriCollectionInterface $uriCollection, ClientCredentialsInterface $credentials, ?AccessTokenRepositoryInterface $tokenRepository = null): AuthorizationProviderInterface + { + $authSeverClient = $this->createAuthServerClient($uriCollection); + + return new AuthorizationProvider( + $authSeverClient, + new ClientCredentialsGrant(), + $credentials, + $tokenRepository + ); + } + + private function createAuthServerClient(UriCollectionInterface $uriCollection): AuthorizationServerClientInterface + { + $httpClient = $this->clientFactory->create(); + + return new AuthorizationServerClient( + $httpClient, + $this->requestFactory, + $this->streamFactory, + $uriCollection, + new ClientSecretPost() + ); + } +} diff --git a/modules/inpostizi/src/BasketApp/Basket/BasketsApiClientInterface.php b/modules/inpostizi/src/BasketApp/Basket/BasketsApiClientInterface.php new file mode 100644 index 00000000..4f107d8b --- /dev/null +++ b/modules/inpostizi/src/BasketApp/Basket/BasketsApiClientInterface.php @@ -0,0 +1,36 @@ +summary = $summary; + $this->delivery = $delivery; + $this->promo_codes = $promo_codes; + $this->products = $products; + $this->related_products = $related_products; + $this->consents = $consents; + $this->promotions_available = $promotions_available; + } + + public function getSummary(): Summary + { + return $this->summary; + } + + /** + * @return DeliveryOption[] + */ + public function getDelivery(): array + { + return $this->delivery; + } + + /** + * @return PromoCode[] + */ + public function getPromoCodes(): array + { + return $this->promo_codes; + } + + /** + * @return Product[] + */ + public function getProducts(): array + { + return $this->products; + } + + /** + * @return Product[] + */ + public function getRelatedProducts(): array + { + return $this->related_products; + } + + /** + * @return Consent[] + */ + public function getConsents(): array + { + return $this->consents; + } + + /** + * @return AvailablePromotion[] + */ + public function getPromotionsAvailable(): array + { + return $this->promotions_available; + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/BasketApp/Basket/Response/BasketBindingKeyResponse.php b/modules/inpostizi/src/BasketApp/Basket/Response/BasketBindingKeyResponse.php new file mode 100644 index 00000000..2ca6213e --- /dev/null +++ b/modules/inpostizi/src/BasketApp/Basket/Response/BasketBindingKeyResponse.php @@ -0,0 +1,28 @@ +basket_binding_api_key = $basket_binding_api_key; + } + + public function getBindingKey(): string + { + return $this->basket_binding_api_key; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/BasketApp/Basket/Response/BasketBindingResponse.php b/modules/inpostizi/src/BasketApp/Basket/Response/BasketBindingResponse.php new file mode 100644 index 00000000..a593cecf --- /dev/null +++ b/modules/inpostizi/src/BasketApp/Basket/Response/BasketBindingResponse.php @@ -0,0 +1,61 @@ +basket_linked = $basket_linked; + $this->browser_trusted = $browser_trusted; + $this->inpost_basket_id = $inpost_basket_id; + $this->client_details = $client_details; + } + + public function isBasketLinked(): bool + { + return $this->basket_linked; + } + + public function isBrowserTrusted(): bool + { + return $this->browser_trusted; + } + + public function getInPostBasketId(): ?string + { + return $this->inpost_basket_id; + } + + public function getClientDetails(): ?ClientDetails + { + return $this->client_details; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/BasketApp/Basket/Response/ClientDetails.php b/modules/inpostizi/src/BasketApp/Basket/Response/ClientDetails.php new file mode 100644 index 00000000..1a070903 --- /dev/null +++ b/modules/inpostizi/src/BasketApp/Basket/Response/ClientDetails.php @@ -0,0 +1,63 @@ +phone_number = $phone_number; + $this->masked_phone_number = $masked_phone_number; + $this->name = $name; + $this->surname = $surname; + } + + public function getPhoneNumber(): PhoneNumber + { + return $this->phone_number; + } + + public function getMaskedPhoneNumber(): string + { + return $this->masked_phone_number; + } + + public function getName(): string + { + return $this->name; + } + + public function getSurname(): string + { + return $this->surname; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/BasketApp/Basket/Response/UpdateBasketResponse.php b/modules/inpostizi/src/BasketApp/Basket/Response/UpdateBasketResponse.php new file mode 100644 index 00000000..1a62d7fb --- /dev/null +++ b/modules/inpostizi/src/BasketApp/Basket/Response/UpdateBasketResponse.php @@ -0,0 +1,28 @@ +inpost_basket_id = $inpost_basket_id; + } + + public function getInPostBasketId(): string + { + return $this->inpost_basket_id; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/BasketApp/BasketAppClient.php b/modules/inpostizi/src/BasketApp/BasketAppClient.php new file mode 100644 index 00000000..d7d7596a --- /dev/null +++ b/modules/inpostizi/src/BasketApp/BasketAppClient.php @@ -0,0 +1,298 @@ +client = $client; + $this->requestFactory = $requestFactory; + $this->streamFactory = $streamFactory; + $this->serializer = $serializer; + $this->baseUri = $baseUri ?? (new ProductionEnvironment())->getBasketAppApiUri(); + } + + public function updateBasket(string $basketId, Basket $basket): UpdateBasketResponse + { + $request = $this->createRequest('PUT', sprintf('/v2/izi/basket/%s', $basketId), $basket); + $response = $this->sendRequest($request); + + return $this->deserialize($response, UpdateBasketResponse::class); + } + + public function initializeBasketBinding(string $basketId): BasketBindingKeyResponse + { + $request = $this->createRequest('PUT', sprintf('/v2/izi/basket/%s/binding', $basketId)); + $response = $this->sendRequest($request); + + return $this->deserialize($response, BasketBindingKeyResponse::class); + } + + public function deleteBasketBinding(string $basketId, bool $orderCompleted = false): void + { + $uri = sprintf('/v1/izi/basket/%s/binding', $basketId); + if ($orderCompleted) { + $uri .= '?' . self::buildQuery(['if_basket_realized' => (int) $orderCompleted]); + } + + $request = $this->createRequest('DELETE', $uri); + $this->sendRequest($request, 204); + } + + public function getBasketBinding(string $basketId, ?string $browserId = null): BasketBindingResponse + { + $uri = sprintf('/v1/izi/basket/%s/binding', $basketId); + if (null !== $browserId) { + $uri .= '?' . self::buildQuery(['browser_id' => $browserId]); + } + + $request = $this->createRequest('GET', $uri); + $response = $this->sendRequest($request); + + return $this->deserialize($response, BasketBindingResponse::class); + } + + public function updateOrder(string $orderId, OrderEvent $event): void + { + $request = $this->createRequest('POST', sprintf('/v1/izi/order/%s/event', $orderId), $event); + // currently the API returns 201 on success instead of 200 given in the documentation + $this->sendRequest($request, 200, 201); + } + + public function getSigningKey(string $version): SigningKey + { + $request = $this->createRequest('GET', sprintf('/v1/izi/signing-keys/public/%s', $version)); + $response = $this->sendRequest($request); + + return $this->deserialize($response, SigningKey::class); + } + + public function getSigningKeys(): SigningKeys + { + $request = $this->createRequest('GET', '/v1/izi/signing-keys/public'); + $response = $this->sendRequest($request); + + return $this->deserialize($response, SigningKeys::class); + } + + public function getAvailablePaymentOptions(): AvailablePaymentOptions + { + $request = $this->createRequest('GET', '/v1/izi/payment-methods'); + $response = $this->sendRequest($request); + + return $this->deserialize($response, AvailablePaymentOptions::class); + } + + public function createProducts(CreateProductsRequest $products): CreateProductsResponse + { + $request = $this->createRequest('POST', '/v1/izi/products', $products); + $response = $this->sendRequest($request, 201); + + return $this->deserialize($response, CreateProductsResponse::class); + } + + /** + * @param string[] $productIds + * + * @return PaginationPage + */ + public function getProductsPage(array $productIds = [], ?int $pageSize = null, ?int $pageIndex = null): PaginationPage + { + $params = []; + + if (null !== $pageIndex) { + $params['page_index'] = $pageIndex; + } + + if (null !== $pageSize) { + $params['page_size'] = $pageSize; + } + + if ([] !== $productIds) { + $params['product_ids'] = implode(',', $productIds); + } + + $uri = '/v1/izi/products'; + if ([] !== $params) { + $uri .= '?' . self::buildQuery($params); + } + + $request = $this->createRequest('GET', $uri); + $response = $this->sendRequest($request); + + return $this->deserialize($response, PaginationPage::class, [ + BasketAppPaginationPageDenormalizer::ITEM_TYPE_KEY => ResponseProduct::class, + ]); + } + + /** + * @param string[] $productIds + * + * @return \Generator + */ + public function getProducts(array $productIds = [], ?int $pageSize = null): \Traversable + { + $pageIndex = 0; + + do { + $page = $this->getProductsPage($productIds, $pageSize, $pageIndex++); + $pageSize = $page->getPageSize(); + $totalCount = $page->getTotalCount(); + + foreach ($page as $product) { + yield $product; + } + } while ($totalCount > $pageSize * $pageIndex); + } + + public function updateProduct(string $productId, Product $product): ResponseProduct + { + $request = $this->createRequest('PUT', sprintf('/v1/izi/product/%s', $productId), $product); + $response = $this->sendRequest($request); + + return $this->deserialize($response, ResponseProduct::class); + } + + public function deleteProduct(string $productId): void + { + $request = $this->createRequest('DELETE', sprintf('/v1/izi/product/%s', $productId)); + $this->sendRequest($request, 204); + } + + private function createRequest(string $method, string $uri, $payload = null): RequestInterface + { + $uri = UriResolver::resolve($uri, $this->baseUri); + + $request = $this->requestFactory + ->createRequest($method, $uri) + ->withHeader('Accept', 'application/json'); + + if (null === $payload) { + return $request; + } + + $payload = $this->serializer->serialize($payload, 'json', [ + 'datetime_format' => self::DATETIME_FORMAT, + 'datetime_timezone' => self::DATETIME_ZONE, + ]); + + $body = $this->streamFactory->createStream($payload); + + return $request + ->withBody($body) + ->withHeader('Content-Type', 'application/json'); + } + + private function sendRequest(RequestInterface $request, int $expectedStatusCode = 200, int ...$allowedStatusCodes): ResponseInterface + { + $response = $this->client->sendRequest($request); + $statusCode = $response->getStatusCode(); + + if (300 <= $statusCode) { + $this->handleUnsuccessfulResponse($request, $response); + } + + if ($expectedStatusCode === $statusCode || in_array($statusCode, $allowedStatusCodes, true)) { + return $response; + } + + throw new \UnexpectedValueException(sprintf('Unexpected server response code: %d', $statusCode)); + } + + /** + * @template T + * + * @param class-string $class + * + * @return T + */ + private function deserialize(ResponseInterface $response, string $class, array $context = []) + { + return $this->serializer->deserialize((string) $response->getBody(), $class, 'json', $context); + } + + private function handleUnsuccessfulResponse(RequestInterface $request, ResponseInterface $response): void + { + $statusCode = $response->getStatusCode(); + + try { + $error = $this->deserialize($response, Error::class); + + throw BasketAppException::create($request, $error, $statusCode); // TODO? replace with exception factory service + } catch (ExceptionInterface $e) { + // ignore deserialization errors + } + + if (500 <= $statusCode) { + throw new ServerException($request, $response); + } + + if (400 <= $statusCode) { + throw new ClientException($request, $response); + } + + throw new RedirectionException($request, $response); + } + + private static function buildQuery(array $params): string + { + return http_build_query($params, '', '&', PHP_QUERY_RFC3986); + } +} diff --git a/modules/inpostizi/src/BasketApp/BasketAppClientFactory.php b/modules/inpostizi/src/BasketApp/BasketAppClientFactory.php new file mode 100644 index 00000000..9b5dfd7c --- /dev/null +++ b/modules/inpostizi/src/BasketApp/BasketAppClientFactory.php @@ -0,0 +1,74 @@ +requestFactory = $requestFactory; + $this->streamFactory = $streamFactory; + $this->serializer = $serializer; + $this->clientFactory = $clientFactory ?? new GuzzleClientFactory(); + $this->authorizationProviderFactory = $authorizationProviderFactory ?? new AuthorizationProviderFactory($requestFactory, $streamFactory, $this->clientFactory); + } + + public function create(ApiConfigurationInterface $configuration): BasketAppClient + { + $httpClient = $this->createHttpClient($configuration); + $baseUri = $configuration->getEnvironment()->getBasketAppApiUri(); + + return new BasketAppClient($httpClient, $this->requestFactory, $this->streamFactory, $this->serializer, $baseUri); + } + + private function createHttpClient(ApiConfigurationInterface $configuration): ClientInterface + { + if (null === $credentials = $configuration->getClientCredentials()) { + throw new \RuntimeException('Client credentials are not available.'); + } + + $uriCollection = new AuthServerUriCollection($configuration->getEnvironment()); + $authorizationProvider = $this->authorizationProviderFactory->create($uriCollection, $credentials); + $client = $this->clientFactory->create(); + + return new AuthorizingClient($client, $authorizationProvider); + } +} diff --git a/modules/inpostizi/src/BasketApp/BasketAppClientInterface.php b/modules/inpostizi/src/BasketApp/BasketAppClientInterface.php new file mode 100644 index 00000000..19bed19a --- /dev/null +++ b/modules/inpostizi/src/BasketApp/BasketAppClientInterface.php @@ -0,0 +1,20 @@ +> exception class names by error codes + */ + private const CLASS_MAP = [ + BadRequestException::ERROR_CODE => BadRequestException::class, + MalformedRequestException::ERROR_CODE => MalformedRequestException::class, + UnauthorizedException::ERROR_CODE => UnauthorizedException::class, + ForbiddenException::ERROR_CODE => ForbiddenException::class, + ResourceNotFoundException::ERROR_CODE => ResourceNotFoundException::class, + BasketNotFoundException::ERROR_CODE => BasketNotFoundException::class, + PublicKeyNotFoundException::ERROR_CODE => PublicKeyNotFoundException::class, + OrderNotFoundException::ERROR_CODE => OrderNotFoundException::class, + MerchantDisabledException::ERROR_CODE => MerchantDisabledException::class, + BasketAlreadyBoundException::ERROR_CODE => BasketAlreadyBoundException::class, + PhoneBindingUnavailableException::ERROR_CODE => PhoneBindingUnavailableException::class, + BasketNotBoundException::ERROR_CODE => BasketNotBoundException::class, + BasketExpiredException::ERROR_CODE => BasketExpiredException::class, + CannotChangeOrderStatusException::ERROR_CODE => CannotChangeOrderStatusException::class, + InternalServerErrorException::ERROR_CODE => InternalServerErrorException::class, + Product\ProductNotFoundException::ERROR_CODE => Product\ProductNotFoundException::class, + Product\ProductExistsException::ERROR_CODE => Product\ProductExistsException::class, + Product\MaxProductLimitReachedException::ERROR_CODE => Product\MaxProductLimitReachedException::class, + ]; + + /** + * @var RequestInterface + */ + private $request; + + /** + * @var Error + */ + private $error; + + public function __construct(RequestInterface $request, Error $error, int $statusCode) + { + $this->request = $request; + $this->error = $error; + + parent::__construct($error->getMessage(), $statusCode); + } + + public static function create(RequestInterface $request, Error $error, int $statusCode): self + { + $class = self::CLASS_MAP[$error->getCode()] ?? self::class; + + return new $class($request, $error, $statusCode); + } + + public function getRequest(): RequestInterface + { + return $this->request; + } + + public function getError(): Error + { + return $this->error; + } +} diff --git a/modules/inpostizi/src/BasketApp/Exception/BasketExpiredException.php b/modules/inpostizi/src/BasketApp/Exception/BasketExpiredException.php new file mode 100644 index 00000000..e2547741 --- /dev/null +++ b/modules/inpostizi/src/BasketApp/Exception/BasketExpiredException.php @@ -0,0 +1,10 @@ +delivery_date = $delivery_date; + $this->mail = $mail; + $this->phone_number = $phone_number; + $this->delivery_point = $delivery_point; + $this->delivery_address = $delivery_address; + $this->courier_note = $courier_note; + } + + public function getDeliveryDate(): ?\DateTimeImmutable + { + return $this->delivery_date; + } + + public function getEmail(): ?string + { + return $this->mail; + } + + public function getPhoneNumber(): ?PhoneNumber + { + return $this->phone_number; + } + + public function getPoint(): ?string + { + return $this->delivery_point; + } + + public function getAddress(): ?DeliveryAddress + { + return $this->delivery_address; + } + + public function getCourierNote(): ?string + { + return $this->courier_note; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/BasketApp/Order/Request/OrderEvent.php b/modules/inpostizi/src/BasketApp/Order/Request/OrderEvent.php new file mode 100644 index 00000000..36ddfe28 --- /dev/null +++ b/modules/inpostizi/src/BasketApp/Order/Request/OrderEvent.php @@ -0,0 +1,63 @@ +event_id = $event_id; + $this->event_data_time = $event_data_time; + $this->phone_number = $phone_number; + $this->event_data = $event_data; + } + + public function getId(): string + { + return $this->event_id; + } + + public function getDateTime(): \DateTimeImmutable + { + return $this->event_data_time; + } + + public function getPhoneNumber(): ?PhoneNumber + { + return $this->phone_number; + } + + public function getData(): OrderEventData + { + return $this->event_data; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/BasketApp/Order/Request/OrderEventData.php b/modules/inpostizi/src/BasketApp/Order/Request/OrderEventData.php new file mode 100644 index 00000000..2d7bb30f --- /dev/null +++ b/modules/inpostizi/src/BasketApp/Order/Request/OrderEventData.php @@ -0,0 +1,69 @@ +order_status = $order_status; + $this->order_merchant_status_description = $order_merchant_status_description; + $this->delivery_references_list = $delivery_references_list; + $this->delivery = $delivery; + } + + public function getStatus(): ?MerchantOrderStatus + { + return $this->order_status; + } + + public function getStatusDescription(): ?string + { + return $this->order_merchant_status_description; + } + + /** + * @return string[]|null + */ + public function getDeliveryReferencesList(): ?array + { + return $this->delivery_references_list; + } + + public function getDelivery(): ?Delivery + { + return $this->delivery; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/BasketApp/PaginationPage.php b/modules/inpostizi/src/BasketApp/PaginationPage.php new file mode 100644 index 00000000..b110c1dc --- /dev/null +++ b/modules/inpostizi/src/BasketApp/PaginationPage.php @@ -0,0 +1,83 @@ + + */ +final class PaginationPage implements \IteratorAggregate, \JsonSerializable +{ + /** + * @var T[] + */ + private $content; + + /** + * @var int + */ + private $total_items; + + /** + * @var int + */ + private $page_index; + + /** + * @var int + */ + private $page_size; + + /** + * @param T[] $content + */ + public function __construct(array $content, int $total_items, int $page_index, int $page_size) + { + $this->content = $content; + $this->total_items = $total_items; + $this->page_index = $page_index; + $this->page_size = $page_size; + } + + /** + * @return T[] + */ + public function getItems(): array + { + return $this->content; + } + + public function getTotalCount(): int + { + return $this->total_items; + } + + public function getPageIndex(): int + { + return $this->page_index; + } + + public function getPageSize(): int + { + return $this->page_size; + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return get_object_vars($this); + } + + /** + * @return \Traversable + */ + public function getIterator(): \Traversable + { + return new \ArrayIterator($this->content); + } +} diff --git a/modules/inpostizi/src/BasketApp/Payment/PaymentsApiClientInterface.php b/modules/inpostizi/src/BasketApp/Payment/PaymentsApiClientInterface.php new file mode 100644 index 00000000..53310733 --- /dev/null +++ b/modules/inpostizi/src/BasketApp/Payment/PaymentsApiClientInterface.php @@ -0,0 +1,12 @@ + + */ +final class AvailablePaymentOptions implements \JsonSerializable, \IteratorAggregate +{ + /** + * @var PaymentType[] + */ + private $payment_type; + + /** + * @param PaymentType[] $payment_type + */ + public function __construct(array $payment_type) + { + $this->payment_type = $payment_type; + } + + /** + * @return PaymentType[] + */ + public function getPaymentTypes(): array + { + return $this->payment_type; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } + + public function getIterator(): \Iterator + { + return new \ArrayIterator($this->payment_type); + } +} diff --git a/modules/inpostizi/src/BasketApp/Product/Exception/MaxProductLimitReachedException.php b/modules/inpostizi/src/BasketApp/Product/Exception/MaxProductLimitReachedException.php new file mode 100644 index 00000000..ad93f9c4 --- /dev/null +++ b/modules/inpostizi/src/BasketApp/Product/Exception/MaxProductLimitReachedException.php @@ -0,0 +1,12 @@ + + */ + public function getProductsPage(array $productIds = [], ?int $pageSize = null, ?int $pageIndex = null): PaginationPage; + + /** + * Get InPost Pay products iterator. + * + * @param string[] $productIds optional list of product IDs to filter by + * + * @return \Traversable + */ + public function getProducts(array $productIds = [], ?int $pageSize = null): \Traversable; + + /** + * Update a product in InPost Pay. + * + * @throws ProductNotFoundException + */ + public function updateProduct(string $productId, Product $product): ResponseProduct; + + /** + * Delete a product from InPost Pay. + * + * @throws ProductNotFoundException + */ + public function deleteProduct(string $productId): void; +} diff --git a/modules/inpostizi/src/BasketApp/Product/Request/CreateProductsRequest.php b/modules/inpostizi/src/BasketApp/Product/Request/CreateProductsRequest.php new file mode 100644 index 00000000..b0eec1a9 --- /dev/null +++ b/modules/inpostizi/src/BasketApp/Product/Request/CreateProductsRequest.php @@ -0,0 +1,39 @@ +content = $content; + } + + /** + * @return IdentifiableProduct[] + */ + public function getProducts(): array + { + return $this->content; + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/BasketApp/Product/Response/CreateProductsResponse.php b/modules/inpostizi/src/BasketApp/Product/Response/CreateProductsResponse.php new file mode 100644 index 00000000..5d007bd8 --- /dev/null +++ b/modules/inpostizi/src/BasketApp/Product/Response/CreateProductsResponse.php @@ -0,0 +1,37 @@ +content = $content; + } + + /** + * @return ProductId[] + */ + public function getProductIds(): array + { + return $this->content; + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/BasketApp/Product/Response/Product.php b/modules/inpostizi/src/BasketApp/Product/Response/Product.php new file mode 100644 index 00000000..147031eb --- /dev/null +++ b/modules/inpostizi/src/BasketApp/Product/Response/Product.php @@ -0,0 +1,191 @@ +product_id = $product_id; + $this->status = $status; + $this->product_name = $product_name; + $this->product_description = $product_description; + $this->product_image = $product_image; + $this->price = $price; + $this->currency = $currency; + $this->quantity = $quantity; + $this->ean = $ean; + $this->qr_code = $qr_code; + $this->deep_link = $deep_link; + $this->product_availability = $product_availability; + $this->additional_product_images = $additional_product_images; + $this->product_attributes = $product_attributes; + } + + public function getId(): string + { + return $this->product_id; + } + + public function getStatus(): Status + { + return $this->status; + } + + public function getEan(): ?string + { + return $this->ean; + } + + public function getQrCode(): ?string + { + return $this->qr_code; + } + + public function getDeepLink(): ?string + { + return $this->deep_link; + } + + public function getAvailability(): ?ProductAvailability + { + return $this->product_availability; + } + + public function getName(): string + { + return $this->product_name; + } + + public function getDescription(): string + { + return $this->product_description; + } + + public function getImageUrl(): string + { + return $this->product_image; + } + + /** + * @return ProductImage[] + */ + public function getAdditionalImages(): array + { + return $this->additional_product_images; + } + + public function getPrice(): Price + { + return $this->price; + } + + public function getCurrency(): Currency + { + return $this->currency; + } + + public function getQuantity(): Quantity + { + return $this->quantity; + } + + /** + * @return ProductAttribute[] + */ + public function getAttributes(): array + { + return $this->product_attributes; + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/BasketApp/Product/Response/ProductId.php b/modules/inpostizi/src/BasketApp/Product/Response/ProductId.php new file mode 100644 index 00000000..9fbcbc1d --- /dev/null +++ b/modules/inpostizi/src/BasketApp/Product/Response/ProductId.php @@ -0,0 +1,53 @@ +product_id = $product_id; + $this->qr_code = $qr_code; + $this->deep_link = $deep_link; + } + + public function getId(): string + { + return $this->product_id; + } + + public function getQrCode(): ?string + { + return $this->qr_code; + } + + public function getDeepLink(): ?string + { + return $this->deep_link; + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/BasketApp/Product/Response/Status.php b/modules/inpostizi/src/BasketApp/Product/Response/Status.php new file mode 100644 index 00000000..d11d6476 --- /dev/null +++ b/modules/inpostizi/src/BasketApp/Product/Response/Status.php @@ -0,0 +1,17 @@ +public_key_base64 = $public_key_base64; + $this->version = $version; + } + + public static function denormalize(array $data): self + { + if (!isset($data['public_key_base64'], $data['version'])) { + throw new UnexpectedValueException('Array data does not contain all of the required parameters.'); + } + + $key = new self($data['public_key_base64'], $data['version']); + if (isset($data['hash'])) { + $key->hash = $data['hash']; + } + + return $key; + } + + public function getBase64Encoded(): string + { + return $this->public_key_base64; + } + + public function getVersion(): string + { + return $this->version; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } + + public function getHash(): string + { + return $this->hash ?? ($this->hash = hash('sha256', $this->public_key_base64)); + } + + public function getPemFormatted(): string + { + return "-----BEGIN PUBLIC KEY-----\n" . $this->public_key_base64 . "\n-----END PUBLIC KEY-----"; + } +} diff --git a/modules/inpostizi/src/BasketApp/Signature/Response/SigningKey.php b/modules/inpostizi/src/BasketApp/Signature/Response/SigningKey.php new file mode 100644 index 00000000..309322cc --- /dev/null +++ b/modules/inpostizi/src/BasketApp/Signature/Response/SigningKey.php @@ -0,0 +1,39 @@ +merchant_external_id = $merchant_external_id; + $this->public_key = $public_key; + } + + public function getMerchantId(): string + { + return $this->merchant_external_id; + } + + public function getPublicKey(): PublicKey + { + return $this->public_key; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/BasketApp/Signature/Response/SigningKeys.php b/modules/inpostizi/src/BasketApp/Signature/Response/SigningKeys.php new file mode 100644 index 00000000..316c12fa --- /dev/null +++ b/modules/inpostizi/src/BasketApp/Signature/Response/SigningKeys.php @@ -0,0 +1,64 @@ + + */ +final class SigningKeys implements \IteratorAggregate, \JsonSerializable +{ + /** + * @var string + */ + private $merchant_external_id; + + /** + * @var PublicKey[] + */ + private $public_keys; + + /** + * @param PublicKey[] $public_keys + */ + public function __construct(string $merchant_external_id, array $public_keys) + { + $this->merchant_external_id = $merchant_external_id; + $this->public_keys = $public_keys; + } + + public function getMerchantId(): string + { + return $this->merchant_external_id; + } + + /** + * @return PublicKey[] + */ + public function getPublicKeys(): array + { + return $this->public_keys; + } + + public function getIterator(): \Iterator + { + return new \ArrayIterator($this->public_keys); + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } + + public function getPublicKey(string $version): ?PublicKey + { + foreach ($this->public_keys as $publicKey) { + if ($version === $publicKey->getVersion()) { + return $publicKey; + } + } + + return null; + } +} diff --git a/modules/inpostizi/src/BasketApp/Signature/SigningKeysApiClientInterface.php b/modules/inpostizi/src/BasketApp/Signature/SigningKeysApiClientInterface.php new file mode 100644 index 00000000..11eecd5b --- /dev/null +++ b/modules/inpostizi/src/BasketApp/Signature/SigningKeysApiClientInterface.php @@ -0,0 +1,19 @@ +cart = $cart; + $this->contextManager = $contextManager; + $this->consentsConfiguration = $consentsConfiguration; + $this->deliveryFactory = $deliveryFactory; + $this->productConfiguration = $productConfiguration; + $this->productDeliveryFactory = $deliveryRelatedProductFactory; + $this->imageRetriever = $imageRetriever; + $this->lowestPriceProvider = $lowestPriceProvider ?? new NullLowestPriceProvider(); + $this->promoCodeProvider = $promoCodeProvider ?? CartRulePromoCodeProvider::create(); + $this->availablePromotionsProvider = $availablePromotionsProvider ?? new NullAvailablePromotionsProvider(); + $this->validator = $validator; + $this->shopId = (int) $this->cart->id_shop; + } + + public function setShopId(int $shopId): void + { + $this->shopId = $shopId; + } + + /** + * @return static + */ + public function setExpirationDate(?\DateTimeImmutable $expirationDate): BasketBuilderInterface + { + $this->expirationDate = $expirationDate; + + return $this; + } + + /** + * @return static + */ + public function setNotice(?Notice $notice): BasketBuilderInterface + { + $this->notice = $notice; + + return $this; + } + + /** + * @return static + */ + public function setAdditionalInformation(?string $info): BasketBuilderInterface + { + $this->additionalInformation = $info; + + return $this; + } + + public function build() + { + try { + $this->contextManager->changeContext($this->cart, [ + 'shop_id' => $this->shopId, + ]); + + $cartProducts = $this->cart->getProducts(false, false, null, true, true); + $products = $this->createCartProducts($cartProducts); + $this->cartSummary = $this->createSummary($products); + + return $this->doBuild( + $this->cartSummary, + $this->getAvailableDeliveryOptions($this->cart), + $products, + $this->getConsents(), + array_values($this->promoCodeProvider->getPromoCodes($this->cart)), + $this->getRelatedProducts($cartProducts), + array_values($this->availablePromotionsProvider->getAvailablePromotions($this->cart)) + ); + } finally { + $this->contextManager->restoreContext(); + } + } + + /** + * @param DeliveryOption[] $delivery + * @param Product[] $products + * @param Consent[] $consents + * @param PromoCode[] $promoCodes + * @param Product[] $relatedProducts + * @param AvailablePromotion[] $availablePromotions + */ + abstract protected function doBuild(Summary $summary, array $delivery, array $products, array $consents, array $promoCodes, array $relatedProducts/*, array $availablePromotions = []*/); + + /** + * @return Product[] + */ + private function createCartProducts(array $products): array + { + if (!$this->lowestPriceProvider instanceof BatchLowestPriceProviderInterface) { + return array_map([$this, 'createCartProduct'], $products); + } + + $queries = $this->getLowestPriceQueries($products, static function (array $product): bool { + return (bool) $product['reduction_applies']; + }); + + try { + $this->lowestPriceProvider->preparePrices(...$queries); + + return array_map([$this, 'createCartProduct'], $products); + } finally { + $this->lowestPriceProvider->reset(); + } + } + + private function createCartProduct(array $product): Product + { + $model = new \Product($product['id_product'], false, $this->cart->id_lang, $product['id_shop']); + + return $this->createProduct( + $model, + $product, + $this->createQuantity($product, (int) $product['cart_quantity']), + $this->getProductBasePrice($product), + $this->getCartProductPromoPrice($product) + ); + } + + private function getProductBasePrice(array $product): Price + { + $gross = $product['price_without_reduction'] ?? $this->calculateProductPrice( + (int) $product['id_product'], + (int) $product['id_product_attribute'], + true, + false, + (int) $product['cart_quantity'], + (int) $product['id_customization'], + (int) $product['id_shop'] + ); + + $net = $product['price_without_reduction_without_tax'] ?? $this->calculateProductPrice( + (int) $product['id_product'], + (int) $product['id_product_attribute'], + false, + false, + (int) $product['cart_quantity'], + (int) $product['id_customization'], + (int) $product['id_shop'] + ); + + return PriceFactory::create((float) $net, (float) $gross); + } + + private function getCartProductPromoPrice(array $product): ?Price + { + if (!$product['reduction_applies']) { + return null; + } + + $gross = $product['price_with_reduction'] ?? $this->calculateProductPrice( + (int) $product['id_product'], + (int) $product['id_product_attribute'], + true, + true, + (int) $product['cart_quantity'], + (int) $product['id_customization'], + (int) $product['id_shop'] + ); + + $net = $product['price_with_reduction_without_tax'] ?? $this->calculateProductPrice( + (int) $product['id_product'], + (int) $product['id_product_attribute'], + false, + true, + (int) $product['cart_quantity'], + (int) $product['id_customization'], + (int) $product['id_shop'] + ); + + return PriceFactory::create((float) $net, (float) $gross); + } + + private function createProduct(\Product $model, array $product, Quantity $quantity, Price $basePrice, ?Price $promoPrice, bool $related = false): Product + { + $combinationId = array_key_exists('id_product_attribute', $product) ? (int) $product['id_product_attribute'] : null; + $customizationId = array_key_exists('id_customization', $product) ? (int) $product['id_customization'] : 0; + $shopId = array_key_exists('id_shop', $product) ? (int) $product['id_shop'] : null; + + $category = $model->id_category_default ?: $model->getDefaultCategory(); + $description = DescriptionFormatter::formatDescription($model); + $link = $this->contextManager->getContext()->link->getProductLink($model, null, null, null, $this->cart->id_lang, $shopId, $combinationId); + $imageUrls = $this->getImageProvider()->getImageUrls((int) $model->id, $combinationId); + + return new Product( + (string) ReferenceId::create($model->id, $combinationId, $customizationId), + $product['name'], + $basePrice, + $quantity, + is_array($category) ? (string) current($category) : (string) $category, + $product['ean13'] ?? $model->ean13, + $description, + $link, + $imageUrls->getMainImageUrl(), + $promoPrice, + null === $promoPrice ? null : $this->getLowestPrice((int) $model->id, $combinationId, $shopId), + $this->getProductAttributes($product), + $this->getProductVariants($product), + $imageUrls->getAdditionalImages(), + $related ? null : $this->getDeliveryProduct($model, $promoPrice ?? $basePrice, $quantity, (float) $product['weight'], $product), + $related ? $this->getDeliveryRelatedProducts($model, $promoPrice ?? $basePrice, $quantity) : null + ); + } + + /** + * @todo unused for now + * + * @return ProductVariant[] + */ + private function getProductVariants(array $product): array + { + return []; + } + + /** + * @return ProductAttribute[] + */ + private function getProductAttributes(array $product): array + { + return array_map([$this, 'createProductAttribute'], $this->getAttributes($product)); + } + + private function createProductAttribute(array $attribute): ProductAttribute + { + return new ProductAttribute($attribute['group'], $attribute['name']); + } + + private function getAttributes(array $product): array + { + if (!isset($product['id_product_attribute']) || 0 >= (int) $product['id_product_attribute']) { + return []; + } + + $attributes = $product['attributes'] ?? null; + + if (is_array($attributes)) { + return array_values($attributes); + } + + /* @see \CartCore::cacheSomeAttributesLists() */ + if (!is_string($attributes) || '' === $attributes) { + return []; + } + + return $this->getAttributeListParser()->parse($attributes, (int) $this->shopId); + } + + private function createQuantity(array $product, int $quantity): Quantity + { + $availableQuantity = $this->getAvailableQuantity($product); + + return Quantity::integer( + $quantity, + $availableQuantity, + $availableQuantity + ); + } + + private function getAvailableQuantity(array $product): int + { + if (!isset($product['allow_oosp'])) { + $outOfStock = \StockAvailable::outOfStock($product['id_product']); + $product['allow_oosp'] = \Product::isAvailableWhenOutOfStock($outOfStock); + } + + if ($product['allow_oosp']) { + $availableQuantity = isset($product['quantity_available']) ? (int) $product['quantity_available'] : 0; + + return max(9999, $availableQuantity); + } + + $availableQuantity = isset($product['quantity_available']) + ? (int) $product['quantity_available'] + : \StockAvailable::getQuantityAvailableByProduct($product['id_product'], $product['id_product_attribute']); + + return max(0, $availableQuantity); + } + + /** + * @param Product[] $products + */ + private function createSummary(array $products): Summary + { + $basePrice = $this->getBasePrice($products); + $finalPrice = $this->getFinalPrice(); + + if ( + [] !== $this->cart->getCartRules(\CartRule::FILTER_ACTION_REDUCTION, false, true) + || [] !== $this->cart->getCartRules(\CartRule::FILTER_ACTION_GIFT, false, true) + ) { + $promoPrice = $this->getPromoPrice(); + } else { + $promoPrice = clone $finalPrice; // avoid inconsistent rounding by \Cart::getOrderTotal() + } + + return new Summary( + $basePrice, + Currency::Pln(), + $this->getPaymentOptions(), + $finalPrice, + $promoPrice, + $this->expirationDate, + $this->additionalInformation, + $this->notice + ); + } + + /** + * @todo this actually makes no sense since products' prices are already rounded + * + * @param Product[] $products + */ + private function getBasePrice(array $products): Price + { + $net = array_reduce($products, function ($sum, Product $product) { + return $sum + $this->calculateRowTotal($product->getBasePrice()->getNet(), $product->getQuantity()->getQuantity()); + }, 0.); + + $gross = array_reduce($products, function ($sum, Product $product) { + return $sum + $this->calculateRowTotal($product->getBasePrice()->getGross(), $product->getQuantity()->getQuantity()); + }, 0.); + + return PriceFactory::create($net, $gross); + } + + private function getPromoPrice(): Price + { + $gross = (float) $this->cart->getOrderTotal(true, \Cart::ONLY_PRODUCTS, null, null, false, true); + $net = (float) $this->cart->getOrderTotal(false, \Cart::ONLY_PRODUCTS, null, null, false, true); + + return PriceFactory::create($net, $gross); + } + + private function getFinalPrice(): Price + { + // between PS 1.7.4 and 1.7.6 \Cart::BOTH_WITHOUT_SHIPPING calculation type does not take cart rules into the account + if (\Tools::version_compare(_PS_VERSION_, '1.7.4', '>=') && \Tools::version_compare(_PS_VERSION_, '1.7.6')) { + return $this->getCartTotalWithoutShipping(); + } + + $gross = (float) $this->cart->getOrderTotal(true, \Cart::BOTH_WITHOUT_SHIPPING, null, null, false, true); + $net = (float) $this->cart->getOrderTotal(false, \Cart::BOTH_WITHOUT_SHIPPING, null, null, false, true); + + return PriceFactory::create($net, $gross); + } + + /** + * @return Product[] + */ + private function getRelatedProducts(array $cartProducts): array + { + if ([] === $relatedProducts = $this->prepareRelatedProducts($cartProducts)) { + return []; + } + + if (!$this->lowestPriceProvider instanceof BatchLowestPriceProviderInterface) { + return array_map([$this, 'createRelatedProduct'], $relatedProducts); + } + + $queries = $this->getLowestPriceQueries($relatedProducts, static function (array $product): bool { + return 0. !== (float) $product['reduction']; + }); + + try { + $this->lowestPriceProvider->preparePrices(...$queries); + + return array_map([$this, 'createRelatedProduct'], $relatedProducts); + } finally { + $this->lowestPriceProvider->reset(); + } + } + + private function createRelatedProduct(array $product): Product + { + $model = new \Product($product['id_product'], false, $this->cart->id_lang); + + $quantity = !empty($product['id_product_attribute']) + ? (int) (new \Combination((int) $product['id_product_attribute']))->minimal_quantity + : (int) $model->minimal_quantity; + + if (isset($product['quantity'])) { + $product['quantity_available'] = $product['quantity']; + } + + return $this->createProduct( + $model, + $product, + $this->createQuantity($product, $quantity), + $this->getRelatedProductBasePrice($product, $quantity), + $this->getRelatedProductPromoPrice($product, $quantity), + true + ); + } + + private function getRelatedProductBasePrice(array $product, int $quantity): Price + { + $gross = $product['price_without_reduction'] ?? $this->calculateProductPrice( + (int) $product['id_product'], + (int) $product['id_product_attribute'], + true, + false, + $quantity + ); + + $net = $product['price_without_reduction_without_tax'] ?? $this->calculateProductPrice( + (int) $product['id_product'], + (int) $product['id_product_attribute'], + false, + false, + $quantity + ); + + return PriceFactory::create((float) $net, (float) $gross); + } + + private function getRelatedProductPromoPrice(array $product, int $quantity): ?Price + { + if (0. === (float) $product['reduction']) { + return null; + } + + $gross = $product['price'] ?? $this->calculateProductPrice( + (int) $product['id_product'], + (int) $product['id_product_attribute'], + true, + true, + $quantity + ); + + $net = $product['price_tax_exc'] ?? $this->calculateProductPrice( + (int) $product['id_product'], + (int) $product['id_product_attribute'], + false, + true, + $quantity + ); + + return PriceFactory::create((float) $net, (float) $gross); + } + + /** + * @return Consent[] + */ + private function getConsents(): array + { + $configConsents = $this->consentsConfiguration->getConsents($shopId = (int) $this->shopId); + + if ([] === $configConsents) { + return []; + } + + $languageId = (int) $this->cart->id_lang; + + $cmsPages = \CMS::getCMSPages($languageId, null, true, $shopId); + + $cmsPagesById = []; + foreach ($cmsPages as $page) { + $cmsPagesById[$page['id_cms']] = $page; + } + + $getLinkUrl = function (DTO\ConsentLink $link) use (&$cmsPagesById, $languageId, $shopId): ?string { + if (!isset($cmsPagesById[$cmsId = $link->getCmsPageId()])) { + return null; + } + + $page = &$cmsPagesById[$cmsId]; + + return $page['url'] ?? $page['url'] = $this->contextManager->getContext()->link->getCMSLink( + $cmsId, + $page['link_rewrite'], + null, + $languageId, + $shopId + ); + }; + + $consents = []; + + foreach ($configConsents as $consent) { + if (null === $mainUrl = $getLinkUrl($consent->getLink())) { + continue; + } + + $additionalLinks = []; + foreach ($consent->getAdditionalLinks() as $link) { + if (null === $additionalUrl = $getLinkUrl($link)) { + continue; + } + + $additionalLinks[] = new ConsentLink( + $link->getId(), + $additionalUrl, + $link->getLabel($languageId) + ); + } + + $consents[] = new Consent( + $consent->getId(), + $mainUrl, + $this->getConsentDescription($consent, $languageId), + $consent->getVersion(), + $consent->getRequirementType() ?? ConsentRequirementType::Optional(), + $consent->getLinkLabel($languageId), + $additionalLinks + ); + } + + return $consents; + } + + private function getConsentDescription(DTO\Consent $consent, int $languageId): string + { + if ($description = $consent->getDescription($languageId)) { + return $description; + } + + $defaultLanguageId = (int) $this->getConfiguration('PS_LANG_DEFAULT'); + + return $consent->getDescription($defaultLanguageId); + } + + /** + * @return PaymentType[] + */ + private function getPaymentOptions(): array + { + /** @var \InPostIzi $module */ + $module = \Module::getInstanceByName('inpostizi'); + $configuration = $module->get(OrdersConfigurationInterface::class); + + return array_values($configuration->getAvailablePaymentOptions((int) $this->shopId)); + } + + private function calculateProductPrice(int $productId, ?int $combinationId = null, bool $withTax = true, bool $withReduction = true, int $quantity = 1, ?int $customizationId = null, ?int $shopId = null): ?float + { + if (null === $shopId || (int) $this->contextManager->getContext()->shop->id === $shopId) { + $context = null; + } else { + $context = $this->contextManager->getContext()->cloneContext(); + $context->shop = new \Shop($shopId); + } + + return \Product::getPriceStatic( + $productId, + $withTax, + $combinationId, + 6, + null, + false, + $withReduction, + $quantity, + false, + $this->cart->id_customer, + $this->cart->id, + null, + $specificPrice, + true, + true, + $context, + true, + $customizationId + ); + } + + private function calculateRowTotal(float $price, int $quantity): float + { + switch ($this->getConfiguration('PS_ROUND_TYPE')) { + case \Order::ROUND_TOTAL: + return (float) $price * $quantity; + case \Order::ROUND_LINE: + return (float) \Tools::ps_round($price * $quantity, $this->getPriceComputingPrecision()); + case \Order::ROUND_ITEM: + default: + return (float) \Tools::ps_round($price, $this->getPriceComputingPrecision()) * $quantity; + } + } + + private function getPriceComputingPrecision(): int + { + $context = $this->contextManager->getContext(); + + if (is_callable([$context, 'getComputingPrecision'])) { + return $context->getComputingPrecision(); + } + + if (defined('_PS_PRICE_COMPUTE_PRECISION_')) { + return (int) _PS_PRICE_COMPUTE_PRECISION_; + } + + return 2; + } + + /** + * @return false|string + */ + private function getConfiguration(string $key) + { + return \Configuration::get($key, null, null, $this->shopId); + } + + private function prepareRelatedProducts(array $cartProducts): array + { + if ([] === $cartProducts) { + return []; + } + + $limit = $this->getRelatedProductsLimit(); + + if (null !== $limit && 0 >= $limit) { + return []; + } + + $cartProductsById = $relatedProductsById = []; + + foreach ($cartProducts as $cartProduct) { + $cartProductsById[$cartProduct['id_product']] = $cartProduct; + } + + foreach ($cartProductsById as $productId => $cartProduct) { + $product = new \Product($productId, false, $this->cart->id_lang, $cartProduct['id_shop']); + + if (false === $accessories = $product->getAccessories($this->cart->id_lang)) { + continue; + } + + foreach ($accessories as $accessory) { + $accessoryId = $accessory['id_product']; + if (isset($relatedProductsById[$accessoryId]) || isset($cartProductsById[$accessoryId])) { + continue; + } + + if (!$accessory['available_for_order']) { + continue; + } + + if ($accessory['customizable'] > 1) { + continue; // product requires customization + } + + if (!$accessory['allow_oosp'] && $accessory['quantity'] < $accessory['minimal_quantity']) { + continue; + } + + $relatedProductsById[$accessoryId] = $accessory; + if (null !== $limit && 0 === --$limit) { + return array_values($relatedProductsById); + } + } + } + + return array_values($relatedProductsById); + } + + private function getRelatedProductsLimit(): ?int + { + $config = $this->getConfiguration('INPOST_PAY_related_count'); + + return false === $config || '' === $config ? null : (int) $config; + } + + private function getCartTotalWithoutShipping(): Price + { + $calculator = $this->getCartCalculator(); + + $calculator->calculateRows(); + $calculator->calculateCartRules(); + + $amount = $calculator->getRowTotal(); + + return PriceFactory::create( + $amount->getTaxExcluded(), + $amount->getTaxIncluded() + ); + } + + private function getCartCalculator(): Calculator + { + return (\Closure::bind(function (): Calculator { + $products = $this->getProducts(); + $cartRules = $this->getTotalCalculationCartRules(self::BOTH_WITHOUT_SHIPPING, false); + + /** @var array{obj: \CartRule} $cartRule */ + foreach ($cartRules as $cartRule) { + $cartRule['obj']->free_shipping = false; + } + + return $this->newCalculator($products, $cartRules, null); + }, $this->cart, \CartCore::class))(); + } + + /** + * @return DeliveryOption[] + */ + private function getAvailableDeliveryOptions(\Cart $cart): array + { + if (null === $this->availableDeliveryOptions) { + $this->availableDeliveryOptions = array_values($this->deliveryFactory->getAvailableDeliveryOptions($cart, $this->shopId)); + } + + return $this->availableDeliveryOptions; + } + + /** + * @return DeliveryRelatedProducts[]|null + */ + private function getDeliveryRelatedProducts(\Product $productModel, Price $price, Quantity $quantity): ?array + { + $productDeliveryDetails = []; + + if (null === $this->cartSummary) { + return null; + } + + foreach (DeliveryType::cases() as $deliveryType) { + $freeDeliveryAmount = $this->getFreeDeliveryAmount($deliveryType); + $productDelivery = $this->productDeliveryFactory->createForRelatedProduct($deliveryType, $this->cartSummary, $this->cart, $productModel, $price, $quantity, $freeDeliveryAmount, $this->shopId); + $productDeliveryDetails[] = $productDelivery; + } + + return $productDeliveryDetails; + } + + /** + * @return DeliveryProduct[]|null + */ + private function getDeliveryProduct(\Product $product, Price $price, Quantity $quantity, float $weight, array $productData): ?array + { + $hasUnavailable = false; + $productDeliveryDetails = []; + + $isRestricted = false; + if (!empty($this->validator)) { + $violations = $this->validator->validate($productData, new Unrestricted((int) $productData['id_shop'])); + $isRestricted = $violations->count() > 0; + } + + foreach (DeliveryType::cases() as $deliveryType) { + if ($isRestricted) { + $productDelivery = new DeliveryProduct($deliveryType, false); + } else { + $productDelivery = $this->createCartProductDeliveryDetails($deliveryType, $product, $price, $quantity, $weight); + } + $productDeliveryDetails[] = $productDelivery; + + if (!$productDelivery->isDeliveryAvailable()) { + $hasUnavailable = true; + } + } + + if (!$hasUnavailable) { + return null; + } + + return $productDeliveryDetails; + } + + private function createCartProductDeliveryDetails(DeliveryType $deliveryType, \Product $product, Price $price, Quantity $quantity, float $weight): DeliveryProduct + { + foreach ($this->getAvailableDeliveryOptions($this->cart) as $deliveryOption) { + if ($deliveryType === $deliveryOption->getType()) { + return new DeliveryProduct($deliveryType, true); + } + } + + return $this->productDeliveryFactory->createForCartProduct($deliveryType, $this->cart, $product, $price, $weight, $quantity, $this->shopId); + } + + private function getFreeDeliveryAmount(DeliveryType $deliveryType): ?float + { + foreach ($this->getAvailableDeliveryOptions($this->cart) as $deliveryOption) { + if ($deliveryType === $deliveryOption->getType()) { + if (null === $amount = $deliveryOption->getFreeDeliveryMinimumGrossPrice()) { + return null; + } + + return $amount->getPriceAmount(); + } + } + + return null; + } + + private function getLowestPrice(int $productId, ?int $combinationId = null, ?int $shopId = null): ?Price + { + $query = $this->createLowestPriceQuery($productId, $combinationId, $shopId); + + return $this->lowestPriceProvider->getPrice($query); + } + + private function getLowestPriceQueries(array $products, \Closure $reductionChecker): array + { + $queries = []; + + foreach ($products as $product) { + if (!$reductionChecker($product)) { + continue; + } + + $productId = (int) $product['id_product']; + $combinationId = 0 < $product['id_product_attribute'] ? (int) $product['id_product_attribute'] : null; + + $idShop = array_key_exists('id_shop', $product) ? (int) $product['id_shop'] : null; + + $queries[] = $this->createLowestPriceQuery($productId, $combinationId, $idShop); + } + + return $queries; + } + + private function createLowestPriceQuery(int $productId, ?int $combinationId = null, ?int $shopId = null): LowestPriceQuery + { + $context = $this->contextManager->getContext(); + + $customer = $context->customer; + $customerGroupId = 0 < (int) $customer->id + ? (int) $customer->id_default_group + : (int) $this->getConfiguration('PS_UNIDENTIFIED_GROUP'); + + return new LowestPriceQuery( + $productId, + $shopId ?? (int) $this->shopId, + (int) $this->cart->id_currency, + (int) $context->country->id, + $customerGroupId, + $combinationId + ); + } + + private function getAttributeListParser(): AttributeListParser + { + return $this->attributeListParser ?? $this->attributeListParser = new AttributeListParser( + new PrestaShopConfiguration(new Configuration()), + $this->contextManager->getContext(), + _PS_VERSION_ + ); + } + + private function getImageProvider(): ImageUrlsProvider + { + return $this->imageProvider ?? $this->imageProvider = ImageUrlsProvider::create( + $this->imageRetriever, + $this->contextManager->getContext(), + $this->productConfiguration + ); + } +} diff --git a/modules/inpostizi/src/Builder/Basket/BasketAppRequestBuilder.php b/modules/inpostizi/src/Builder/Basket/BasketAppRequestBuilder.php new file mode 100644 index 00000000..5a237800 --- /dev/null +++ b/modules/inpostizi/src/Builder/Basket/BasketAppRequestBuilder.php @@ -0,0 +1,42 @@ + + */ +interface BasketAppRequestBuilderInterface extends BasketBuilderInterface +{ + public function build(): Basket; +} diff --git a/modules/inpostizi/src/Builder/Basket/BasketBuilderFactory.php b/modules/inpostizi/src/Builder/Basket/BasketBuilderFactory.php new file mode 100644 index 00000000..978a5e27 --- /dev/null +++ b/modules/inpostizi/src/Builder/Basket/BasketBuilderFactory.php @@ -0,0 +1,176 @@ +clock = $clock; + $this->contextManager = $contextManager; + $this->consentsConfiguration = $consentsConfiguration; + $this->productConfiguration = $productConfiguration; + $this->deliveryFactory = $deliveryFactory; + $this->deliveryRelatedProductFactory = $deliveryRelatedProductFactory; + $this->lowestPriceProvider = $lowestPriceProvider ?? $this->createLowestPriceProvider(); + $this->promoCodeProvider = $promoCodeProvider ?? CartRulePromoCodeProvider::create(); + $this->availablePromotionsProvider = $availablePromotionsProvider ?? new NullAvailablePromotionsProvider(); + $this->validator = $validator; + } + + public function createRequestBuilder(BasketInterface $basket, ?int $shopId = null): RequestBuilder + { + $cart = $this->getCart($basket); + + $builder = new BasketAppRequestBuilder( + $cart, + $this->contextManager, + $this->consentsConfiguration, + $this->productConfiguration, + $this->deliveryFactory, + $this->deliveryRelatedProductFactory, + null, + $this->lowestPriceProvider, + $this->promoCodeProvider, + $this->availablePromotionsProvider, + $this->validator + ); + + if (null !== $shopId) { + $builder->setShopId($shopId); + } + + return $builder->setExpirationDate($this->getExpirationDate()); + } + + public function createResponseBuilder(BasketInterface $basket, ?int $shopId = null): ResponseBuilder + { + $cart = $this->getCart($basket); + + $builder = new MerchantApiResponseBuilder( + $cart, + $this->contextManager, + $this->consentsConfiguration, + $this->productConfiguration, + $this->deliveryFactory, + $this->deliveryRelatedProductFactory, + null, + $this->lowestPriceProvider, + $this->promoCodeProvider, + $this->availablePromotionsProvider, + $this->validator + ); + + if (null !== $shopId) { + $builder->setShopId($shopId); + } + + return $builder->setExpirationDate($this->getExpirationDate()); + } + + private function getCart(BasketInterface $basket): \Cart + { + $cart = $basket->getEntity(); + + if (!$cart instanceof \Cart) { + throw new \InvalidArgumentException(sprintf('Expected basket entity to be an instance of "%s", "%s" given.', \Cart::class, get_class($cart))); + } + + return $cart; + } + + // TODO configurable? + private function getExpirationDate(): \DateTimeImmutable + { + return $this->clock->now()->add(new \DateInterval('P2D')); + } + + private function createLowestPriceProvider(): LowestPriceProviderInterface + { + $repository = new ModuleRepository(); + + $module = $repository->findByName('inpostizi'); + $logger = $module ? $module->getLogger() : new NullLogger(); + + return (new LowestPriceProviderFactory($repository, $logger))->create(); + } +} diff --git a/modules/inpostizi/src/Builder/Basket/BasketBuilderFactoryInterface.php b/modules/inpostizi/src/Builder/Basket/BasketBuilderFactoryInterface.php new file mode 100644 index 00000000..c646f576 --- /dev/null +++ b/modules/inpostizi/src/Builder/Basket/BasketBuilderFactoryInterface.php @@ -0,0 +1,16 @@ +configuration = $configuration; + $this->carrierRepository = $carrierRepository; + $this->clock = $clock; + $this->serviceNameTranslator = $serviceNameTranslator; + $this->priceCalculator = $priceCalculator; + } + + /** + * @return DeliveryOption[] + */ + public function getAvailableDeliveryOptions(\Cart $cart, ?int $idShop = null): array + { + $deliveryOptions = []; + $idShop = $idShop ?? (int) $cart->id_shop; + + $deliveryDate = $this->getDeliveryDate(); + $isFreeShipping = null; + + foreach (DeliveryType::cases() as $deliveryType) { + $options = $this->configuration->getShippingOptions($deliveryType, (int) $idShop); + $referenceId = $options->getCarrierMapping()->getReferenceId(); + + if (null === $referenceId || null === $carrier = $this->getCarrier($cart, $referenceId)) { + continue; + } + + if (!isset($isFreeShipping)) { + $isFreeShipping = $this->hasFreeShippingCartRule($cart); + } + + $price = $isFreeShipping + ? PriceFactory::create(0., 0.) + : $this->priceCalculator->getDeliveryPrice($cart, $carrier); + + $deliveryOptions[] = new DeliveryOption( + $deliveryType, + $deliveryDate, + $price, + $this->getOptionalServices($deliveryType, $cart, $options, $carrier, $isFreeShipping), + $this->priceCalculator->getFreeDeliveryMinAmount($cart, $carrier) + ); + } + + return $deliveryOptions; + } + + /** + * @return OptionalService[] + */ + private function getOptionalServices(DeliveryType $deliveryType, \Cart $cart, ShippingOptions $options, \Carrier $defaultCarrier, bool $isFreeShipping): array + { + $services = []; + + foreach ($deliveryType->getAvailableServiceCodes() as $serviceCode) { + if (null === $carrierId = $options->getCarrierMapping($serviceCode)->getReferenceId()) { + continue; + } + + $serviceOptions = $options->getServiceOptions($serviceCode); + + if (null === $serviceOptions || !$this->checkServiceAvailability($serviceCode, $serviceOptions)) { + continue; + } + + if ($carrierId === (int) $defaultCarrier->id_reference) { + $carrier = $defaultCarrier; + } elseif (null === $carrier = $this->getCarrier($cart, $carrierId)) { + continue; + } + + $servicePrice = $this->getServicePrice($serviceOptions, $cart, $carrier, $defaultCarrier, $isFreeShipping); + + if (0 > $servicePrice->getNet()) { + continue; + } + + $services[] = new OptionalService( + $this->serviceNameTranslator->getName($serviceCode), + $serviceCode, + $servicePrice + ); + } + + return $services; + } + + private function getServicePrice(ServiceOptions $options, \Cart $cart, \Carrier $carrier, \Carrier $defaultCarrier, bool $isFreeShipping): Price + { + if ($isFreeShipping) { + return PriceFactory::create(0., 0.); + } + + $servicePrice = $this->priceCalculator->getAdditionalServicePrice($cart, $carrier, $options); + + if ($carrier === $defaultCarrier) { + return $servicePrice; + } + + $carrierPrice = $this->priceCalculator->getDeliveryPrice($cart, $carrier); + $defaultCarrierPrice = $this->priceCalculator->getDeliveryPrice($cart, $defaultCarrier); + + return $servicePrice + ->add($carrierPrice) + ->sub($defaultCarrierPrice); + } + + private function checkServiceAvailability(ServiceCode $serviceCode, ServiceOptions $options): bool + { + if (!$serviceCode->isAvailabilityTimeDependent()) { + return true; + } + + $availability = $options->getAvailabilityRange(); + + return null === $availability + || $availability->contains($this->clock->now()); + } + + private function isDeliveryOptionAvailable(\Cart $cart, int $carrierId): bool + { + $deliveryOptionList = $cart->getDeliveryOptionList(); + $addressId = (int) $cart->id_address_delivery; + + if (!isset($deliveryOptionList[$addressId])) { + return false; + } + + foreach ($deliveryOptionList[$addressId] as $option) { + if (isset($option['carrier_list'][$carrierId]) && 1 === count($option['carrier_list'])) { + return true; + } + } + + return false; + } + + private function hasFreeShippingCartRule(\Cart $cart): bool + { + return [] !== $cart->getCartRules(\CartRule::FILTER_ACTION_SHIPPING, false); + } + + private function getCarrier(\Cart $cart, int $referenceId): ?\Carrier + { + $carrier = $this->carrierRepository->findOneByReferenceId($referenceId); + + if (null === $carrier || !$this->isDeliveryOptionAvailable($cart, (int) $carrier->id)) { + return null; + } + + return $carrier; + } + + // TODO make configurable? + private function getDeliveryDate(): \DateTimeImmutable + { + return $this->clock + ->now() + ->modify('+2 days') + ->setTime(12, 0); + } +} diff --git a/modules/inpostizi/src/Builder/Basket/MerchantApiResponseBuilder.php b/modules/inpostizi/src/Builder/Basket/MerchantApiResponseBuilder.php new file mode 100644 index 00000000..fd14d485 --- /dev/null +++ b/modules/inpostizi/src/Builder/Basket/MerchantApiResponseBuilder.php @@ -0,0 +1,42 @@ + + */ +interface MerchantApiResponseBuilderInterface extends BasketBuilderInterface +{ + public function build(): Basket; +} diff --git a/modules/inpostizi/src/Builder/Basket/ProductDeliveryFactory.php b/modules/inpostizi/src/Builder/Basket/ProductDeliveryFactory.php new file mode 100644 index 00000000..eba76eb1 --- /dev/null +++ b/modules/inpostizi/src/Builder/Basket/ProductDeliveryFactory.php @@ -0,0 +1,215 @@ + + */ + private $carrierRepository; + + /** + * @var CartTotalDeliveryStrategyInterface + */ + private $cartTotalDeliveryStrategy; + + /** + * @var CartWeightDeliveryStrategyInterface + */ + private $cartWeightDeliveryStrategy; + + /** + * @var ProductDimensionsDeliveryStrategyInterface + */ + private $productDimensionsDeliveryStrategy; + + /** + * @var ProductRestrictionDeliveryInterface + */ + private $productRestrictionDelivery; + + /** + * @param CarrierRepository $carrierRepository + */ + public function __construct( + ShippingConfigurationInterface $configuration, + ObjectRepositoryInterface $carrierRepository, + CartTotalDeliveryStrategyInterface $cartTotalDeliveryStrategy, + CartWeightDeliveryStrategyInterface $cartWeightDeliveryStrategy, + ProductDimensionsDeliveryStrategyInterface $productDimensionsDeliveryStrategy, + ProductRestrictionDeliveryInterface $productRestrictionDelivery + ) { + $this->configuration = $configuration; + $this->carrierRepository = $carrierRepository; + $this->cartTotalDeliveryStrategy = $cartTotalDeliveryStrategy; + $this->cartWeightDeliveryStrategy = $cartWeightDeliveryStrategy; + $this->productDimensionsDeliveryStrategy = $productDimensionsDeliveryStrategy; + $this->productRestrictionDelivery = $productRestrictionDelivery; + } + + public function createForCartProduct( + DeliveryType $deliveryType, + \Cart $cart, + \Product $product, + Price $unitPrice, + float $weight, + Quantity $quantity, + ?int $idShop = null + ): DeliveryProduct { + $totalPrice = $unitPrice->multiply($quantity->getQuantity()); + $totalWeight = (new Weight($weight))->multiply($quantity->getQuantity()); + $isDeliveryOptionAvailable = $this->isDeliveryOptionAvailable($product, $deliveryType, $totalPrice, $totalWeight, $cart, $idShop); + + return new DeliveryProduct($deliveryType, $isDeliveryOptionAvailable); + } + + public function createForRelatedProduct( + DeliveryType $deliveryType, + Summary $summary, + \Cart $cart, + \Product $product, + Price $productPrice, + Quantity $quantity, + ?float $freeDeliveryAmount = null, + ?int $idShop = null + ): DeliveryRelatedProducts { + $basketNewPrice = $this->getCartTotalWithNewProduct($productPrice, $quantity, $this->getCartBasePrice($summary)); + $basketNewWeight = $this->getCartWeightWithNewProduct($product, $quantity, $cart); + $isDeliveryOptionAvailable = $this->isDeliveryOptionAvailable($product, $deliveryType, $basketNewPrice, $basketNewWeight, $cart, $idShop); + $isFreeDelivery = $isDeliveryOptionAvailable && $this->isFreeDelivery($freeDeliveryAmount, $basketNewPrice); // if delivery option is not available, free delivery is not possible + + return new DeliveryRelatedProducts( + $deliveryType, + $isDeliveryOptionAvailable, + $isFreeDelivery + ); + } + + private function isDeliveryOptionAvailable( + \Product $product, + DeliveryType $deliveryType, + Price $basketNewPrice, + Weight $basketNewWeight, + \Cart $cart, + ?int $idShop = null + ): bool { + $idShop = $idShop ?? (int) $cart->id_shop; + $options = $this->configuration->getShippingOptions($deliveryType, (int) $idShop); + $referenceId = $options->getCarrierMapping()->getReferenceId(); + + if (null === $referenceId || null === $carrier = $this->carrierRepository->findOneByReferenceId($referenceId)) { + return false; + } + + if (!$carrier->active) { + return false; + } + + if (false === $this->productRestrictionDelivery->isShippingAvailableBasedOnProductCarrierRestriction($carrier, $product)) { + return false; + } + + if (false === $this->productDimensionsDeliveryStrategy->isShippingAvailableBasedOnProductDimensions($carrier, new Dimensions((float) $product->width, (float) $product->height, (float) $product->depth))) { + return false; + } + + if (false === $this->cartTotalDeliveryStrategy->isShippingAvailableBasedOnTotalPrice($carrier, $basketNewPrice)) { + return false; + } + + if (false === $this->cartWeightDeliveryStrategy->isShippingAvailableBasedOnTotalWeight($carrier, $basketNewWeight)) { + return false; + } + + // TODO: pass modified product list + return $this->checkModuleRestrictions($carrier, $cart); + } + + private function isFreeDelivery(?float $freeDeliveryAmount, Price $basketNewPrice): bool + { + if (null === $freeDeliveryAmount) { + return false; + } + + return $basketNewPrice->getGross() >= $freeDeliveryAmount; + } + + private function getCartTotalWithNewProduct(Price $productPrice, Quantity $quantity, Price $basketBasePrice): Price + { + $productTotalPrice = $productPrice->multiply($quantity->getQuantity()); + + return $basketBasePrice->add($productTotalPrice); + } + + private function getCartWeightWithNewProduct(\Product $product, Quantity $quantity, \Cart $cart): Weight + { + $productWeight = new Weight((float) $product->weight); + $defaultCombinationId = (int) \Product::getDefaultAttribute($product->id); + $cartWeight = $this->getCartBaseWeight($cart); + + if (0 < $defaultCombinationId) { + $combination = new \Combination($defaultCombinationId); + $productWeight = $productWeight->add(new Weight((float) $combination->weight)); + } + + return $cartWeight->add($productWeight->multiply($quantity->getQuantity())); + } + + private function getCartBasePrice(Summary $summary): Price + { + return $summary->getPromoPrice() ?? $summary->getBasePrice(); + } + + private function getCartBaseWeight(\Cart $cart): Weight + { + if (!isset($this->cartBaseWeight[$cart->id])) { + $this->cartBaseWeight[$cart->id] = new Weight((float) $cart->getTotalWeight()); + } + + return $this->cartBaseWeight[$cart->id]; + } + + /** + * Carrier modules can disable delivery options by returning false as the shipping cost. + */ + private function checkModuleRestrictions(\Carrier $carrier, \Cart $cart): bool + { + if (!$carrier->is_module || !$carrier->shipping_external || $carrier->is_free) { + return true; + } + + $shippingCost = (\Closure::bind(function () use ($carrier) { + $products = $this->getProducts(); + + return $this->getPackageShippingCostFromModule($carrier, 10., $products); + }, $cart, \Cart::class))(); + + return false !== $shippingCost; + } +} diff --git a/modules/inpostizi/src/Builder/Order/OrderEventBuilder.php b/modules/inpostizi/src/Builder/Order/OrderEventBuilder.php new file mode 100644 index 00000000..1de7dac4 --- /dev/null +++ b/modules/inpostizi/src/Builder/Order/OrderEventBuilder.php @@ -0,0 +1,142 @@ +order = $order; + $this->clock = $clock; + $this->statusDescriptionProvider = $orderStatusDescriptionProvider; + } + + /** + * @return static + */ + public function setEventId(string $eventId): OrderEventBuilderInterface + { + $this->eventId = $eventId; + + return $this; + } + + /** + * @return static + */ + public function setEventTime(\DateTimeImmutable $time): OrderEventBuilderInterface + { + $this->eventTime = $time; + + return $this; + } + + /** + * @return static + */ + public function setOrderStatus(?MerchantOrderStatus $status): OrderEventBuilderInterface + { + $this->status = $status; + + return $this; + } + + /** + * @return static + */ + public function setTrackingNumbers(?array $numbers): OrderEventBuilderInterface + { + $this->trackingNumbers = $numbers; + + return $this; + } + + /** + * @return static + */ + public function setDeliveryData(?Delivery $delivery): OrderEventBuilderInterface + { + $this->delivery = $delivery; + + return $this; + } + + public function build(): OrderEvent + { + $eventData = $this->createEventData(); + $eventTime = $this->getEventTime(); + $eventId = $this->getEventId($eventTime); + + return new OrderEvent($eventId, $eventTime, $eventData); + } + + private function getEventId(\DateTimeImmutable $eventTime): string + { + return $this->eventId ?? (string) $eventTime->getTimestamp(); + } + + private function getEventTime(): \DateTimeImmutable + { + return $this->eventTime ?? $this->clock->now(); + } + + private function createEventData(): OrderEventData + { + $statusDescription = $this->statusDescriptionProvider->getStatus($this->order); + + return new OrderEventData( + $this->status, + $statusDescription, + $this->trackingNumbers, + $this->delivery + ); + } +} diff --git a/modules/inpostizi/src/Builder/Order/OrderEventBuilderFactory.php b/modules/inpostizi/src/Builder/Order/OrderEventBuilderFactory.php new file mode 100644 index 00000000..cf6953ac --- /dev/null +++ b/modules/inpostizi/src/Builder/Order/OrderEventBuilderFactory.php @@ -0,0 +1,45 @@ + + */ + private $repository; + + /** + * @var OrderStatusDescriptionProvider + */ + private $statusDescriptionProvider; + + /** + * @var ClockInterface + */ + private $clock; + + /** + * @param ObjectRepositoryInterface<\Order> $repository + */ + public function __construct(ObjectRepositoryInterface $repository, OrderStatusDescriptionProvider $statusDescriptionProvider, ClockInterface $clock) + { + $this->repository = $repository; + $this->statusDescriptionProvider = $statusDescriptionProvider; + $this->clock = $clock; + } + + public function create(int $orderId): OrderEventBuilderInterface + { + if (null === $order = $this->repository->find($orderId)) { + throw new \DomainException(sprintf('Order "%s" does not exist.', $orderId)); + } + + return new OrderEventBuilder($order, $this->clock, $this->statusDescriptionProvider); + } +} diff --git a/modules/inpostizi/src/Builder/Order/OrderEventBuilderFactoryInterface.php b/modules/inpostizi/src/Builder/Order/OrderEventBuilderFactoryInterface.php new file mode 100644 index 00000000..fb5ac730 --- /dev/null +++ b/modules/inpostizi/src/Builder/Order/OrderEventBuilderFactoryInterface.php @@ -0,0 +1,10 @@ + + */ + private $repository; + + /** + * @var OrdersConfigurationInterface + */ + private $configuration; + + /** + * @param ObjectRepositoryInterface<\OrderState> $repository + */ + public function __construct(ObjectRepositoryInterface $repository, OrdersConfigurationInterface $configuration) + { + $this->repository = $repository; + $this->configuration = $configuration; + } + + public function getStatus(\Order $order): string + { + $orderStateId = (int) $order->current_state; + $languageId = (int) $order->id_lang; + + return $this->configuration->getStatusDescription($orderStateId, $languageId, (int) $order->id_shop) + ?? $this->getOrderStateName($orderStateId, $languageId); + } + + private function getOrderStateName(int $orderStateId, int $languageId): string + { + if (null === $orderState = $this->repository->find($orderStateId, $languageId)) { + throw new \RuntimeException(sprintf('Order state #%d does not exist.', $orderStateId)); + } + + return (string) $orderState->name; + } +} diff --git a/modules/inpostizi/src/Builder/PriceFactory.php b/modules/inpostizi/src/Builder/PriceFactory.php new file mode 100644 index 00000000..86ff236a --- /dev/null +++ b/modules/inpostizi/src/Builder/PriceFactory.php @@ -0,0 +1,19 @@ +configuration = $configuration; + $this->serializer = $serializer; + $this->clock = $clock; + } + + public static function getConfigKeyPrefix(): string + { + return sprintf(self::CONFIG_KEY_PATTERN, ''); + } + + /** + * @return mixed + */ + public function get($key, $default = null) + { + $configKey = $this->validateKey($key); + + try { + $configValue = $this->configuration->get($configKey); + } catch (\Exception $e) { + throw new RuntimeException($e->getMessage(), $e->getCode(), $e); + } + + try { + $item = $this->deserializeCacheItem($configValue); + } catch (ExceptionInterface $e) { + return $default; + } + + if (null === $item) { + return $default; + } + + if (null !== $item['expiry'] && $item['expiry'] < $this->clock->now()) { + return $default; + } + + return $item['value']; + } + + public function set($key, $value, $ttl = null): bool + { + $configKey = $this->validateKey($key); + + try { + $item = $this->createCacheItem($value, $ttl); + } catch (ExceptionInterface $e) { + throw new RuntimeException($e->getMessage(), $e->getCode(), $e); + } + + try { + $this->configuration->set($configKey, json_encode($item)); + + return true; + } catch (\Exception $e) { + return false; + } + } + + public function delete($key): bool + { + $configKey = $this->validateKey($key); + + try { + $this->configuration->remove($configKey); + + return true; + } catch (\Exception $e) { + return false; + } + } + + public function clear(): bool + { + try { + $this->configuration->removeMatching(sprintf(self::CONFIG_KEY_PATTERN, '*')); + + return true; + } catch (\Exception $e) { + return false; + } + } + + public function getMultiple($keys, $default = null): iterable + { + $this->validateKeys($keys); + + $result = []; + + foreach ($keys as $key) { + $value = $this->get($key, $default); + $result[$key] = $value; + } + + return $result; + } + + public function setMultiple($values, $ttl = null): bool + { + if (!is_iterable($values)) { + throw new InvalidArgumentException(sprintf('Cache values must be an array or a Traversable, "%s" given.', get_debug_type($values))); + } + + $success = true; + foreach ($values as $key => $value) { + $success = $this->set($key, $value, $ttl) && $success; + } + + return $success; + } + + public function deleteMultiple($keys): bool + { + $this->validateKeys($keys); + + $success = true; + foreach ($keys as $key) { + $success = $this->delete($key) && $success; + } + + return $success; + } + + public function has($key): bool + { + $configKey = $this->validateKey($key); + + return $this->configuration->has($configKey); + } + + private function validateKey($key): string + { + if (!is_string($key)) { + throw new InvalidArgumentException(sprintf('Cache key must be a string, "%s" given.', get_debug_type($key))); + } + + if ('' === $key) { + throw new InvalidArgumentException('Cache key length must be greater than zero.'); + } + + $configKey = sprintf(self::CONFIG_KEY_PATTERN, $key); + + if (!$this->configuration->isValidKey($configKey)) { + throw new InvalidArgumentException(sprintf('Cache key "%s" is invalid.', $key)); + } + + return $configKey; + } + + private function validateKeys($keys): void + { + if (!is_iterable($keys)) { + return; + } + + throw new InvalidArgumentException(sprintf('Cache keys must be an array or a Traversable, "%s" given.', get_debug_type($keys))); + } + + private function createCacheItem($value, $ttl): array + { + $expiry = $this->getExpiry($ttl); + + if (is_object($value)) { + $class = get_class($value); + $value = $this->serializeValue($value); + } else { + $class = null; + } + + return [ + 'value' => $value, + 'class' => $class, + 'expiry' => null !== $expiry ? $expiry->format('U.u') : null, + ]; + } + + private function serializeValue($value): string + { + try { + return $this->serializer->serialize($value, 'json'); + } catch (ExceptionInterface $e) { + throw new InvalidArgumentException('Unable to serialize cache value.', $e->getCode(), $e); + } + } + + private function getExpiry($ttl): ?\DateTimeImmutable + { + if (null === $ttl) { + return null; + } + + if (is_int($ttl)) { + $ttl = new \DateInterval(sprintf('PT%dS', $ttl)); + } elseif (!$ttl instanceof \DateInterval) { + throw new InvalidArgumentException(sprintf('TTL must be an integer, a DateInterval or null, "%s" given.', get_debug_type($ttl))); + } + + return $this->clock->now()->add($ttl); + } + + private function deserializeCacheItem($value): ?array + { + if (null === $value) { + return null; + } + + $item = json_decode((string) $value, true); + + if (!is_array($item)) { + return null; + } + + if (isset($item['class'])) { + $item['value'] = $this->serializer->deserialize($item['value'], $item['class'], 'json'); + } + + if (isset($item['expiry'])) { + $item['expiry'] = \DateTimeImmutable::createFromFormat('U.u', $item['expiry']); + } + + return $item; + } +} diff --git a/modules/inpostizi/src/Cache/Exception/InvalidArgumentException.php b/modules/inpostizi/src/Cache/Exception/InvalidArgumentException.php new file mode 100644 index 00000000..6156c9e8 --- /dev/null +++ b/modules/inpostizi/src/Cache/Exception/InvalidArgumentException.php @@ -0,0 +1,11 @@ +repository = $repository; + } + + public function clear(): void + { + $this->repository->resetBindingKeysCache(); + } +} diff --git a/modules/inpostizi/src/CacheClearer/CacheClearerInterface.php b/modules/inpostizi/src/CacheClearer/CacheClearerInterface.php new file mode 100644 index 00000000..fb34d7fb --- /dev/null +++ b/modules/inpostizi/src/CacheClearer/CacheClearerInterface.php @@ -0,0 +1,10 @@ + $clearers + */ + public function __construct(iterable $clearers) + { + $this->clearers = $clearers; + } + + public function clear(): void + { + foreach ($this->clearers as $clearer) { + $clearer->clear(); + } + } +} diff --git a/modules/inpostizi/src/CacheClearer/Psr16CacheClearer.php b/modules/inpostizi/src/CacheClearer/Psr16CacheClearer.php new file mode 100644 index 00000000..5f2800e4 --- /dev/null +++ b/modules/inpostizi/src/CacheClearer/Psr16CacheClearer.php @@ -0,0 +1,29 @@ +cache = $cache; + } + + public function clear(): void + { + if ($this->cache->clear()) { + return; + } + + throw new \RuntimeException('Failed to clear cache.'); + } +} diff --git a/modules/inpostizi/src/Cart/Exception/ProductAlreadyInCartException.php b/modules/inpostizi/src/Cart/Exception/ProductAlreadyInCartException.php new file mode 100644 index 00000000..bdc9e034 --- /dev/null +++ b/modules/inpostizi/src/Cart/Exception/ProductAlreadyInCartException.php @@ -0,0 +1,24 @@ +product = $product; + } + + public function getProduct(): array + { + return $this->product; + } +} diff --git a/modules/inpostizi/src/Cart/Util/ProductHelper.php b/modules/inpostizi/src/Cart/Util/ProductHelper.php new file mode 100644 index 00000000..c4ea1ea1 --- /dev/null +++ b/modules/inpostizi/src/Cart/Util/ProductHelper.php @@ -0,0 +1,40 @@ +getProducts() as $product) { + if (self::isSameProduct($product, $productId, $combinationId, $customizationId)) { + return $product; + } + } + + return null; + } + + public static function isInCart(\Cart $cart, int $productId, int $combinationId, int $customizationId = 0): bool + { + return null !== self::findProductInCart($cart, $productId, $combinationId, $customizationId); + } + + public static function getCartQuantity(\Cart $cart, int $productId, int $combinationId, int $customizationId = 0): int + { + if (null === $product = self::findProductInCart($cart, $productId, $combinationId, $customizationId)) { + return 0; + } + + return (int) $product['cart_quantity']; + } + + private static function isSameProduct(array $cartProduct, int $productId, int $combinationId, int $customizationId): bool + { + return $productId === (int) $cartProduct['id_product'] + && $combinationId === (int) $cartProduct['id_product_attribute'] + && $customizationId === (int) $cartProduct['id_customization']; + } +} diff --git a/modules/inpostizi/src/Clock/SystemClock.php b/modules/inpostizi/src/Clock/SystemClock.php new file mode 100644 index 00000000..98e6a7a4 --- /dev/null +++ b/modules/inpostizi/src/Clock/SystemClock.php @@ -0,0 +1,27 @@ +timezone = $timezone; + } + + public static function fromSystemTimezone(): self + { + return new self(new \DateTimeZone(date_default_timezone_get())); + } + + public function now(): \DateTimeImmutable + { + return new \DateTimeImmutable('now', $this->timezone); + } +} diff --git a/modules/inpostizi/src/Command/Config/CheckStatusCommand.php b/modules/inpostizi/src/Command/Config/CheckStatusCommand.php new file mode 100644 index 00000000..eb05733f --- /dev/null +++ b/modules/inpostizi/src/Command/Config/CheckStatusCommand.php @@ -0,0 +1,14 @@ +configuration = $configuration; + } + + public function getConfiguration(): AdvancedConfigurationInterface + { + return $this->configuration; + } +} diff --git a/modules/inpostizi/src/Command/Config/UpdateCartRuleOptionsCommand.php b/modules/inpostizi/src/Command/Config/UpdateCartRuleOptionsCommand.php new file mode 100644 index 00000000..37e20ec2 --- /dev/null +++ b/modules/inpostizi/src/Command/Config/UpdateCartRuleOptionsCommand.php @@ -0,0 +1,78 @@ +cartRuleId = $cartRuleId; + $this->omnibus = $isOmnibus; + } + + public static function for(CartRuleOptions $options): self + { + return (new self($options->getCartRuleId())) + ->setOmnibus($options->isOmnibus()) + ->setPromoDetailsPageId($options->getPromoDetailsPageId()); + } + + public function getCartRuleId(): int + { + return $this->cartRuleId; + } + + public function isOmnibus(): ?bool + { + return $this->omnibus; + } + + public function setOmnibus(?bool $isOmnibus): self + { + $this->omnibus = $isOmnibus; + + return $this; + } + + public function getPromoDetailsPageId(): ?int + { + return $this->promoDetailsPageId; + } + + public function setPromoDetailsPageId(?int $cmsId): self + { + $this->promoDetailsPageId = $cmsId; + + return $this; + } +} diff --git a/modules/inpostizi/src/Command/Config/UpdateConsentsConfigurationCommand.php b/modules/inpostizi/src/Command/Config/UpdateConsentsConfigurationCommand.php new file mode 100644 index 00000000..0ca3089b --- /dev/null +++ b/modules/inpostizi/src/Command/Config/UpdateConsentsConfigurationCommand.php @@ -0,0 +1,56 @@ + + * + * @Assert\Valid + * @Assert\Count(max = UpdateConsentsConfigurationCommand::CONSENTS_COUNT_MAX) + * @Unique(normalizer = {Consent::class, "normalize"}) + */ + private $consents; + + public function __construct(Consent ...$consents) + { + $this->consents = new ArrayCollection($consents); + } + + public function getConsents(?int $shopId = null): array + { + return $this->consents->toArray(); + } + + public function addConsent(Consent $consent): self + { + if (!$this->consents->contains($consent)) { + $this->consents->add($consent); + } + + return $this; + } + + public function removeConsent(Consent $consent): self + { + $this->consents->removeElement($consent); + + return $this; + } +} diff --git a/modules/inpostizi/src/Command/Config/UpdateGeneralConfigurationCommand.php b/modules/inpostizi/src/Command/Config/UpdateGeneralConfigurationCommand.php new file mode 100644 index 00000000..d95889cb --- /dev/null +++ b/modules/inpostizi/src/Command/Config/UpdateGeneralConfigurationCommand.php @@ -0,0 +1,109 @@ +apiConfiguration = $apiConfiguration; + $this->ordersConfiguration = $ordersConfiguration; + $this->generalConfiguration = $generalConfiguration; + $this->productConfiguration = $productConfiguration; + } + + public function getApiConfiguration(): ApiConfigurationInterface + { + return $this->apiConfiguration; + } + + public function setApiConfiguration(ApiConfigurationInterface $apiConfiguration): self + { + $this->apiConfiguration = $apiConfiguration; + + return $this; + } + + public function getOrdersConfiguration(): OrdersConfigurationInterface + { + return $this->ordersConfiguration; + } + + public function setOrdersConfiguration(OrdersConfigurationInterface $ordersConfiguration): self + { + $this->ordersConfiguration = $ordersConfiguration; + + return $this; + } + + public function getGeneralConfiguration(): GeneralConfigurationInterface + { + return $this->generalConfiguration; + } + + public function setGeneralConfiguration(GeneralConfigurationInterface $generalConfiguration): self + { + $this->generalConfiguration = $generalConfiguration; + + return $this; + } + + public function getProductConfiguration(): ProductConfigurationInterface + { + return $this->productConfiguration; + } + + public function setProductConfiguration(ProductConfigurationInterface $productConfiguration): self + { + $this->productConfiguration = $productConfiguration; + + return $this; + } +} diff --git a/modules/inpostizi/src/Command/Config/UpdateGeneralConfigurationCommandFactory.php b/modules/inpostizi/src/Command/Config/UpdateGeneralConfigurationCommandFactory.php new file mode 100644 index 00000000..5b92c90d --- /dev/null +++ b/modules/inpostizi/src/Command/Config/UpdateGeneralConfigurationCommandFactory.php @@ -0,0 +1,66 @@ + + */ + private $apiConfiguration; + + /** + * @var PersistentConfigurationInterface + */ + private $ordersConfiguration; + + /** + * @var PersistentConfigurationInterface + */ + private $generalConfiguration; + + /** + * @var PersistentConfigurationInterface + */ + private $productConfiguration; + + /** + * @param ApiConfiguration $apiConfiguration + * @param OrdersConfiguration $ordersConfiguration + * @param GeneralConfiguration $generalConfiguration + * @param ProductConfiguration $productConfiguration + */ + public function __construct( + ApiConfigurationInterface $apiConfiguration, + OrdersConfigurationInterface $ordersConfiguration, + GeneralConfigurationInterface $generalConfiguration, + ProductConfigurationInterface $productConfiguration + ) { + $this->apiConfiguration = $apiConfiguration; + $this->ordersConfiguration = $ordersConfiguration; + $this->generalConfiguration = $generalConfiguration; + $this->productConfiguration = $productConfiguration; + } + + public function create(): UpdateGeneralConfigurationCommand + { + return new UpdateGeneralConfigurationCommand( + $this->apiConfiguration->copy(), + $this->ordersConfiguration->copy(), + $this->generalConfiguration->copy(), + $this->productConfiguration->copy() + ); + } +} diff --git a/modules/inpostizi/src/Command/Config/UpdateGuiConfigurationCommand.php b/modules/inpostizi/src/Command/Config/UpdateGuiConfigurationCommand.php new file mode 100644 index 00000000..baf644d1 --- /dev/null +++ b/modules/inpostizi/src/Command/Config/UpdateGuiConfigurationCommand.php @@ -0,0 +1,29 @@ +configuration = $configuration; + } + + public function getConfiguration(): GuiConfigurationInterface + { + return $this->configuration; + } +} diff --git a/modules/inpostizi/src/Command/Config/UpdateShippingConfigurationCommand.php b/modules/inpostizi/src/Command/Config/UpdateShippingConfigurationCommand.php new file mode 100644 index 00000000..8b0f6621 --- /dev/null +++ b/modules/inpostizi/src/Command/Config/UpdateShippingConfigurationCommand.php @@ -0,0 +1,29 @@ +configuration = $configuration; + } + + public function getConfiguration(): ShippingConfigurationInterface + { + return $this->configuration; + } +} diff --git a/modules/inpostizi/src/Command/GetBasketBindingKeyCommand.php b/modules/inpostizi/src/Command/GetBasketBindingKeyCommand.php new file mode 100644 index 00000000..5eec09f7 --- /dev/null +++ b/modules/inpostizi/src/Command/GetBasketBindingKeyCommand.php @@ -0,0 +1,43 @@ +basket = $basket; + $this->refresh = $refresh; + } + + public function getBasket(): BasketInterface + { + return $this->basket; + } + + public function isRefresh(): bool + { + return $this->refresh; + } +} diff --git a/modules/inpostizi/src/Command/GetOrderConfirmationUrlCommand.php b/modules/inpostizi/src/Command/GetOrderConfirmationUrlCommand.php new file mode 100644 index 00000000..2b2ba23b --- /dev/null +++ b/modules/inpostizi/src/Command/GetOrderConfirmationUrlCommand.php @@ -0,0 +1,31 @@ +basketId = $basketId; + } + + public function getBasketId(): string + { + return $this->basketId; + } +} diff --git a/modules/inpostizi/src/Command/UnbindBasketCommand.php b/modules/inpostizi/src/Command/UnbindBasketCommand.php new file mode 100644 index 00000000..b1c99f4a --- /dev/null +++ b/modules/inpostizi/src/Command/UnbindBasketCommand.php @@ -0,0 +1,45 @@ +basketId = $basketId; + $this->orderCompleted = $orderCompleted; + } + + /** + * @return int|string + */ + public function getBasketId() + { + return $this->basketId; + } + + public function isOrderCompleted(): bool + { + return $this->orderCompleted; + } +} diff --git a/modules/inpostizi/src/Command/UpdateBasketCommand.php b/modules/inpostizi/src/Command/UpdateBasketCommand.php new file mode 100644 index 00000000..de7ac89e --- /dev/null +++ b/modules/inpostizi/src/Command/UpdateBasketCommand.php @@ -0,0 +1,34 @@ +basketId = $basketId; + } + + /** + * @return int|string + */ + public function getBasketId() + { + return $this->basketId; + } +} diff --git a/modules/inpostizi/src/Command/UpdateOrderAddressDeliveryCommand.php b/modules/inpostizi/src/Command/UpdateOrderAddressDeliveryCommand.php new file mode 100644 index 00000000..5cdab4f0 --- /dev/null +++ b/modules/inpostizi/src/Command/UpdateOrderAddressDeliveryCommand.php @@ -0,0 +1,39 @@ +orderId = $orderId; + $this->eventTime = $eventTime; + } + + public function getOrderId(): string + { + return $this->orderId; + } + + public function getEventTime(): \DateTimeImmutable + { + return $this->eventTime; + } +} diff --git a/modules/inpostizi/src/Command/UpdateOrderStatusCommand.php b/modules/inpostizi/src/Command/UpdateOrderStatusCommand.php new file mode 100644 index 00000000..3c8c8dc6 --- /dev/null +++ b/modules/inpostizi/src/Command/UpdateOrderStatusCommand.php @@ -0,0 +1,51 @@ +orderId = $orderId; + $this->eventTime = $eventTime; + $this->status = $status; + } + + public function getOrderId(): string + { + return $this->orderId; + } + + public function getEventTime(): \DateTimeImmutable + { + return $this->eventTime; + } + + public function getStatus(): ?MerchantOrderStatus + { + return $this->status; + } +} diff --git a/modules/inpostizi/src/Command/UpdateOrderTrackingNumbersCommand.php b/modules/inpostizi/src/Command/UpdateOrderTrackingNumbersCommand.php new file mode 100644 index 00000000..b8f80bb7 --- /dev/null +++ b/modules/inpostizi/src/Command/UpdateOrderTrackingNumbersCommand.php @@ -0,0 +1,39 @@ +orderId = $orderId; + $this->eventTime = $eventTime; + } + + public function getOrderId(): string + { + return $this->orderId; + } + + public function getEventTime(): \DateTimeImmutable + { + return $this->eventTime; + } +} diff --git a/modules/inpostizi/src/CommandBus.php b/modules/inpostizi/src/CommandBus.php new file mode 100644 index 00000000..e225634e --- /dev/null +++ b/modules/inpostizi/src/CommandBus.php @@ -0,0 +1,151 @@ +locator = $locator; + } + + public static function getSubscribedServices(): array + { + return [ + UpdateOrderStatusCommand::class => UpdateOrderStatusHandlerInterface::class, + UpdateOrderAddressDeliveryCommand::class => UpdateOrderAddressDeliveryHandlerInterface::class, + UpdateOrderTrackingNumbersCommand::class => UpdateOrderTrackingNumbersHandlerInterface::class, + UpdateBasketCommand::class => UpdateBasketHandlerInterface::class, + UnbindBasketCommand::class => UnbindBasketHandlerInterface::class, + + /* widget v2 */ + GetBasketBindingKeyCommand::class => '?' . GetBasketBindingKeyHandlerInterface::class, + GetOrderConfirmationUrlCommand::class => '?' . GetOrderConfirmationUrlHandlerInterface::class, + + /* merchant API */ + ConfirmBasketBindingCommand::class => '?' . ConfirmBasketBindingHandlerInterface::class, + DeleteBasketBindingCommand::class => '?' . DeleteBasketBindingHandlerInterface::class, + GetBasketCommand::class => '?' . GetBasketHandlerInterface::class, + MerchantApi\Command\UpdateBasketCommand::class => '?' . MerchantApi\Handler\UpdateBasketHandlerInterface::class, + CreateOrderCommand::class => '?' . CreateOrderHandlerInterface::class, + GetOrderCommand::class => '?' . GetOrderHandlerInterface::class, + UpdateOrderCommand::class => '?' . UpdateOrderHandlerInterface::class, + GetProductsCommand::class => '?' . GetProductsHandlerInterface::class, + AddProductToBasketCommand::class => '?' . AddProductToBasketHandlerInterface::class, + + UpdateCartMessageCommand::class => '?' . UpdateCartMessageHandlerInterface::class, + CreateCartCommand::class => '?' . CreateCartHandlerInterface::class, + AddProductToCartCommand::class => '?' . AddProductToCartHandlerInterface::class, + IncrementCartQuantityCommand::class => '?' . IncrementCartQuantityHandlerInterface::class, + + /* configuration */ + UpdateGeneralConfigurationCommand::class => '?' . UpdateGeneralConfigurationHandlerInterface::class, + UpdateConsentsConfigurationCommand::class => '?' . UpdateConsentsConfigurationHandlerInterface::class, + UpdateGuiConfigurationCommand::class => '?' . UpdateGuiConfigurationHandlerInterface::class, + UpdateShippingConfigurationCommand::class => '?' . UpdateShippingConfigurationHandlerInterface::class, + UpdateAdvancedConfigurationCommand::class => '?' . UpdateAdvancedConfigurationHandlerInterface::class, + CheckStatusCommand::class => '?' . CheckStatusHandlerInterface::class, + DownloadModuleDataCommand::class => '?' . DownloadModuleDataHandlerInterface::class, + UpdateCartRuleOptionsCommand::class => '?' . UpdateCartRuleOptionsHandlerInterface::class, + + CreateHotProductCommand::class => '?' . CreateHotProductHandlerInterface::class, + ImportHotProductCommand::class => '?' . ImportHotProductHandlerInterface::class, + UpdateHotProductCommand::class => '?' . UpdateHotProductHandlerInterface::class, + DeleteHotProductCommand::class => '?' . DeleteHotProductHandlerInterface::class, + DeleteRemoteProductCommand::class => '?' . DeleteRemoteProductHandlerInterface::class, + + UpdateCartAnalyticsCommand::class => '?' . UpdateCartAnalyticsHandlerInterface::class, + ]; + } + + /** + * {@inheritDoc} + */ + public function handle($command) + { + $class = get_class($command); + $handler = $this->locator->get($class); + + if (!is_callable($handler)) { + throw new \LogicException(sprintf('Handler for command "%s" is not callable', $class)); + } + + return $handler($command); + } +} diff --git a/modules/inpostizi/src/CommandBusInterface.php b/modules/inpostizi/src/CommandBusInterface.php new file mode 100644 index 00000000..8a1bdf19 --- /dev/null +++ b/modules/inpostizi/src/CommandBusInterface.php @@ -0,0 +1,15 @@ +type = $type; + $this->promo_code_value = $promo_code_value; + $this->description = $description; + $this->start_date = $start_date; + $this->end_date = $end_date; + $this->priority = $priority; + $this->details = $details; + } + + public function getType(): PromotionType + { + return $this->type; + } + + public function getPromoCode(): string + { + return $this->promo_code_value; + } + + public function getDescription(): string + { + return $this->description; + } + + public function getStartDate(): ?\DateTimeImmutable + { + return $this->start_date; + } + + public function getEndDate(): ?\DateTimeImmutable + { + return $this->end_date; + } + + public function getPriority(): ?int + { + return $this->priority; + } + + public function getDetails(): PromoDetails + { + return $this->details; + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/Common/Basket/Consent.php b/modules/inpostizi/src/Common/Basket/Consent.php new file mode 100644 index 00000000..43a2218f --- /dev/null +++ b/modules/inpostizi/src/Common/Basket/Consent.php @@ -0,0 +1,100 @@ +consent_id = $consent_id; + $this->consent_link = $consent_link; + $this->consent_description = $consent_description; + $this->consent_version = $consent_version; + $this->requirement_type = $requirement_type; + $this->label_link = $label_link; + $this->additional_consent_links = $additional_consent_links; + } + + public function getId(): string + { + return $this->consent_id; + } + + public function getLink(): string + { + return $this->consent_link; + } + + public function getDescription(): string + { + return $this->consent_description; + } + + public function getVersion(): string + { + return $this->consent_version; + } + + public function getLinkLabel(): ?string + { + return $this->label_link; + } + + /** + * @return ConsentLink[] + */ + public function getAdditionalLinks(): array + { + return $this->additional_consent_links; + } + + public function getRequirementType(): ConsentRequirementType + { + return $this->requirement_type; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/Common/Basket/ConsentLink.php b/modules/inpostizi/src/Common/Basket/ConsentLink.php new file mode 100644 index 00000000..6a1a9df5 --- /dev/null +++ b/modules/inpostizi/src/Common/Basket/ConsentLink.php @@ -0,0 +1,50 @@ +id = $id; + $this->consent_link = $consent_link; + $this->label_link = $label_link; + } + + public function getId(): string + { + return $this->id; + } + + public function getLink(): string + { + return $this->consent_link; + } + + public function getLinkLabel(): ?string + { + return $this->label_link; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/Common/Basket/ConsentRequirementType.php b/modules/inpostizi/src/Common/Basket/ConsentRequirementType.php new file mode 100644 index 00000000..06957d4c --- /dev/null +++ b/modules/inpostizi/src/Common/Basket/ConsentRequirementType.php @@ -0,0 +1,19 @@ +delivery_type = $delivery_type; + $this->delivery_date = $delivery_date; + $this->delivery_price = $delivery_price; + $this->delivery_options = $delivery_options; + $this->free_delivery_minimum_gross_price = $free_delivery_minimum_gross_price; + } + + public function getType(): DeliveryType + { + return $this->delivery_type; + } + + public function getDeliveryDate(): \DateTimeImmutable + { + return $this->delivery_date; + } + + public function getPrice(): Price + { + return $this->delivery_price; + } + + /** + * @return OptionalService[] + */ + public function getAvailableServices(): array + { + return $this->delivery_options; + } + + public function getFreeDeliveryMinimumGrossPrice(): ?PriceAmount + { + return $this->free_delivery_minimum_gross_price; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/Common/Basket/Notice.php b/modules/inpostizi/src/Common/Basket/Notice.php new file mode 100644 index 00000000..c5799e36 --- /dev/null +++ b/modules/inpostizi/src/Common/Basket/Notice.php @@ -0,0 +1,49 @@ +type = $type; + $this->description = $description; + } + + public static function error(string $description): self + { + return new self(NoticeType::Error(), $description); + } + + public static function attention(string $description): self + { + return new self(NoticeType::Attention(), $description); + } + + public function getType(): NoticeType + { + return $this->type; + } + + public function getDescription(): string + { + return $this->description; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/Common/Basket/NoticeType.php b/modules/inpostizi/src/Common/Basket/NoticeType.php new file mode 100644 index 00000000..4e046a24 --- /dev/null +++ b/modules/inpostizi/src/Common/Basket/NoticeType.php @@ -0,0 +1,17 @@ +delivery_name = $delivery_name; + $this->delivery_code_value = $delivery_code_value; + $this->delivery_option_price = $delivery_option_price; + } + + public function getName(): string + { + return $this->delivery_name; + } + + public function getCode(): ServiceCode + { + return $this->delivery_code_value; + } + + public function getPrice(): Price + { + return $this->delivery_option_price; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/Common/Basket/Product.php b/modules/inpostizi/src/Common/Basket/Product.php new file mode 100644 index 00000000..7e85bfaf --- /dev/null +++ b/modules/inpostizi/src/Common/Basket/Product.php @@ -0,0 +1,218 @@ +product_id = $product_id; + $this->product_category = $product_category; + $this->ean = $ean; + $this->product_name = $product_name; + $this->product_description = $product_description; + $this->product_link = $product_link; + $this->product_image = $product_image; + $this->base_price = $base_price; + $this->promo_price = $promo_price; + $this->lowest_price = $lowest_price; + $this->quantity = $quantity; + $this->product_attributes = $product_attributes; + $this->variants = $variants; + $this->additional_product_images = $additional_product_images; + $this->delivery_product = $delivery_product; + $this->delivery_related_products = $delivery_related_products; + } + + public function getId(): string + { + return $this->product_id; + } + + public function getCategory(): ?string + { + return $this->product_category; + } + + public function getEan(): ?string + { + return $this->ean; + } + + public function getName(): string + { + return $this->product_name; + } + + public function getDescription(): ?string + { + return $this->product_description; + } + + public function getLink(): ?string + { + return $this->product_link; + } + + public function getImageUrl(): ?string + { + return $this->product_image; + } + + public function getBasePrice(): Price + { + return $this->base_price; + } + + public function getPromoPrice(): ?Price + { + return $this->promo_price; + } + + public function getLowestPrice(): ?Price + { + return $this->lowest_price; + } + + public function getQuantity(): Quantity + { + return $this->quantity; + } + + public function getAttributes(): array + { + return $this->product_attributes; + } + + /** + * @return ProductVariant[] + */ + public function getVariants(): array + { + return $this->variants; + } + + /** + * @return ProductImage[] + */ + public function getAdditionalProductImages(): array + { + return $this->additional_product_images; + } + + /** + * @return DeliveryProduct[]|null + */ + public function getDeliveryProduct(): ?array + { + return $this->delivery_product; + } + + /** + * @return DeliveryRelatedProducts[]|null + */ + public function getDeliveryRelatedProducts(): ?array + { + return $this->delivery_related_products; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/Common/Basket/PromoDetails.php b/modules/inpostizi/src/Common/Basket/PromoDetails.php new file mode 100644 index 00000000..b64a96aa --- /dev/null +++ b/modules/inpostizi/src/Common/Basket/PromoDetails.php @@ -0,0 +1,31 @@ +link = $link; + } + + public function getLink(): string + { + return $this->link; + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/Common/Basket/PromotionType.php b/modules/inpostizi/src/Common/Basket/PromotionType.php new file mode 100644 index 00000000..3ab7ab2e --- /dev/null +++ b/modules/inpostizi/src/Common/Basket/PromotionType.php @@ -0,0 +1,17 @@ +quantity = $quantity; + $this->quantity_type = $quantity_type; + $this->quantity_unit = $quantity_unit; + $this->available_quantity = $available_quantity; + $this->max_quantity = $max_quantity; + } + + /** + * @return self + */ + public static function integer(int $quantity, ?int $available_quantity = null, ?int $max_quantity = null, ?string $quantity_unit = null): self + { + return new self($quantity, QuantityType::Integer(), $quantity_unit, $available_quantity, $max_quantity); + } + + /** + * @return self + */ + public static function decimal(float $quantity, ?float $available_quantity = null, ?float $max_quantity = null, ?string $quantity_unit = null): self + { + return new self($quantity, QuantityType::Decimal(), $quantity_unit, $available_quantity, $max_quantity); + } + + /** + * @return T + */ + public function getQuantity() + { + return $this->quantity; + } + + public function getType(): QuantityType + { + return $this->quantity_type; + } + + public function getUnit(): ?string + { + return $this->quantity_unit; + } + + /** + * @return T|null + */ + public function getAvailableQuantity() + { + return $this->available_quantity; + } + + /** + * @return T|null + */ + public function getMaxQuantity() + { + return $this->max_quantity; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/Common/Basket/Summary.php b/modules/inpostizi/src/Common/Basket/Summary.php new file mode 100644 index 00000000..15af8a9d --- /dev/null +++ b/modules/inpostizi/src/Common/Basket/Summary.php @@ -0,0 +1,115 @@ +basket_base_price = $basket_base_price; + $this->basket_final_price = $basket_final_price; + $this->basket_promo_price = $basket_promo_price; + $this->currency = $currency; + $this->basket_expiration_date = $basket_expiration_date; + $this->basket_additional_information = $basket_additional_information; + $this->payment_type = $payment_type; + $this->basket_notice = $basket_notice; + } + + public function getBasePrice(): Price + { + return $this->basket_base_price; + } + + public function getFinalPrice(): ?Price + { + return $this->basket_final_price; + } + + public function getPromoPrice(): ?Price + { + return $this->basket_promo_price; + } + + public function getCurrency(): Currency + { + return $this->currency; + } + + public function getExpirationDate(): ?\DateTimeImmutable + { + return $this->basket_expiration_date; + } + + public function getAdditionalInformation(): ?string + { + return $this->basket_additional_information; + } + + /** + * @return PaymentType[] + */ + public function getPaymentType(): array + { + return $this->payment_type; + } + + public function getNotice(): ?Notice + { + return $this->basket_notice; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/Common/BindingPlace.php b/modules/inpostizi/src/Common/BindingPlace.php new file mode 100644 index 00000000..7abd99a4 --- /dev/null +++ b/modules/inpostizi/src/Common/BindingPlace.php @@ -0,0 +1,79 @@ +l('Product card', GuiConfigurationType::TRANSLATION_SOURCE); + case self::BasketSummary(): + return $translator->l('Cart page', GuiConfigurationType::TRANSLATION_SOURCE); + case self::BasketPopup(): + return $translator->l('Add to cart confirmation', 'bindingplace'); + case self::OrderCreate(): + return $translator->l('Payment method option selection', 'bindingplace'); + case self::LoginPage(): + return $translator->l('Login page', GuiConfigurationType::TRANSLATION_SOURCE); + case self::RegisterFormPage(): + return $translator->l('Register page', GuiConfigurationType::TRANSLATION_SOURCE); + case self::CheckoutPage(): + return $translator->l('Checkout page', GuiConfigurationType::TRANSLATION_SOURCE); + case self::MiniCartPage(): + return $translator->l('Cart preview', GuiConfigurationType::TRANSLATION_SOURCE); + case self::ThankYouPage(): + return $translator->l('"Thank you" page', 'bindingplace'); + default: + throw new \LogicException('Unreachable statement.'); + } + } +} diff --git a/modules/inpostizi/src/Common/Currency.php b/modules/inpostizi/src/Common/Currency.php new file mode 100644 index 00000000..85c902d6 --- /dev/null +++ b/modules/inpostizi/src/Common/Currency.php @@ -0,0 +1,30 @@ +name = $name; + $this->surname = $surname; + $this->phone_number = $phone_number; + $this->mail = $mail; + $this->client_address = $client_address; + } + + public static function fromOrderRequestData(OrderRequestAccountInfo $accountInfo): self + { + $address = ClientAddress::fromOrderRequestData($accountInfo->getAddress()); + + return new self($accountInfo->getName(), $accountInfo->getSurname(), $accountInfo->getPhoneNumber(), $accountInfo->getEmail(), $address); + } + + public function getName(): string + { + return $this->name; + } + + public function getSurname(): string + { + return $this->surname; + } + + public function getPhoneNumber(): PhoneNumber + { + return $this->phone_number; + } + + public function getEmail(): string + { + return $this->mail; + } + + public function getAddress(): ClientAddress + { + return $this->client_address; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/Common/Customer/ClientAddress.php b/modules/inpostizi/src/Common/Customer/ClientAddress.php new file mode 100644 index 00000000..e882b123 --- /dev/null +++ b/modules/inpostizi/src/Common/Customer/ClientAddress.php @@ -0,0 +1,68 @@ +country_code = $country_code; + $this->address = $address; + $this->city = $city; + $this->postal_code = $postal_code; + } + + public static function fromOrderRequestData(OrderRequestClientAddress $address): self + { + return new self($address->getCountryCode(), $address->getAddress(), $address->getCity(), $address->getPostalCode()); + } + + public function getCountryCode(): string + { + return $this->country_code; + } + + public function getAddress(): string + { + return $this->address; + } + + public function getCity(): string + { + return $this->city; + } + + public function getPostalCode(): string + { + return $this->postal_code; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/Common/Customer/InvoiceDetails.php b/modules/inpostizi/src/Common/Customer/InvoiceDetails.php new file mode 100644 index 00000000..5fb4ebc7 --- /dev/null +++ b/modules/inpostizi/src/Common/Customer/InvoiceDetails.php @@ -0,0 +1,182 @@ +legal_form = $legal_form; + $this->country_code = $country_code; + $this->tax_id_prefix = $tax_id_prefix; + $this->tax_id = $tax_id; + $this->company_name = $company_name; + $this->name = $name; + $this->surname = $surname; + $this->city = $city; + $this->street = $street; + $this->building = $building; + $this->flat = $flat; + $this->postal_code = $postal_code; + $this->mail = $mail; + $this->registration_data_edited = $registration_data_edited; + $this->additional_information = $additional_information; + } + + public function getLegalForm(): LegalForm + { + return $this->legal_form; + } + + public function getCountryCode(): string + { + return $this->country_code; + } + + public function getTaxIdPrefix(): ?string + { + return $this->tax_id_prefix; + } + + public function getTaxId(): ?string + { + return $this->tax_id; + } + + public function getCompanyName(): ?string + { + return $this->company_name; + } + + public function getName(): ?string + { + return $this->name; + } + + public function getSurname(): ?string + { + return $this->surname; + } + + public function getCity(): string + { + return $this->city; + } + + public function getStreet(): string + { + return $this->street; + } + + public function getBuilding(): string + { + return $this->building; + } + + public function getFlat(): ?string + { + return $this->flat; + } + + public function getPostalCode(): string + { + return $this->postal_code; + } + + public function getMail(): ?string + { + return $this->mail; + } + + public function getRegistrationDataEdited(): ?string + { + return $this->registration_data_edited; + } + + public function getAdditionalInformation(): ?string + { + return $this->additional_information; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/Common/Customer/LegalForm.php b/modules/inpostizi/src/Common/Customer/LegalForm.php new file mode 100644 index 00000000..79f69df9 --- /dev/null +++ b/modules/inpostizi/src/Common/Customer/LegalForm.php @@ -0,0 +1,17 @@ +l('APM', 'deliverytype'); + case self::Courier(): + return $translator->l('Courier', 'deliverytype'); + default: + return $this->name; + } + } + + /** + * @return ServiceCode[] + */ + public function getAvailableServiceCodes(): array + { + if (self::Apm() === $this) { + return ServiceCode::cases(); + } + + return [ServiceCode::Cod()]; + } +} diff --git a/modules/inpostizi/src/Common/Delivery/OptionalService.php b/modules/inpostizi/src/Common/Delivery/OptionalService.php new file mode 100644 index 00000000..90ffd44e --- /dev/null +++ b/modules/inpostizi/src/Common/Delivery/OptionalService.php @@ -0,0 +1,52 @@ +delivery_name = $delivery_name; + $this->delivery_code_value = $delivery_code_value; + $this->delivery_option_price = $delivery_option_price; + } + + public function getName(): string + { + return $this->delivery_name; + } + + public function getCode(): ServiceCode + { + return $this->delivery_code_value; + } + + public function getPrice(): Price + { + return $this->delivery_option_price; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/Common/Delivery/ServiceCode.php b/modules/inpostizi/src/Common/Delivery/ServiceCode.php new file mode 100644 index 00000000..f0cb1c28 --- /dev/null +++ b/modules/inpostizi/src/Common/Delivery/ServiceCode.php @@ -0,0 +1,43 @@ + + */ + public static function getAvailableCombinations(DeliveryType $deliveryType): array + { + $serviceCodes = $deliveryType->getAvailableServiceCodes(); + $combinations = [[]]; + + foreach ($serviceCodes as $serviceCode) { + foreach ($combinations as $combination) { + $combinations[] = array_merge($combination, [$serviceCode]); + } + } + + usort($combinations, static function (array $c1, $c2): int { + return count($c1) - count($c2); + }); + + return $combinations; + } +} diff --git a/modules/inpostizi/src/Common/Dimensions.php b/modules/inpostizi/src/Common/Dimensions.php new file mode 100644 index 00000000..f2996eeb --- /dev/null +++ b/modules/inpostizi/src/Common/Dimensions.php @@ -0,0 +1,45 @@ +width = $width; + $this->height = $height; + $this->depth = $depth; + } + + public function getWidth(): float + { + return $this->width; + } + + public function getHeight(): float + { + return $this->height; + } + + public function getDepth(): float + { + return $this->depth; + } +} diff --git a/modules/inpostizi/src/Common/Error/Error.php b/modules/inpostizi/src/Common/Error/Error.php new file mode 100644 index 00000000..bd4048db --- /dev/null +++ b/modules/inpostizi/src/Common/Error/Error.php @@ -0,0 +1,39 @@ +error_code = $error_code; + $this->error_message = $error_message; + } + + public function getCode(): string + { + return $this->error_code; + } + + public function getMessage(): string + { + return $this->error_message; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/Common/HotProduct/IdentifiableProduct.php b/modules/inpostizi/src/Common/HotProduct/IdentifiableProduct.php new file mode 100644 index 00000000..b3828d63 --- /dev/null +++ b/modules/inpostizi/src/Common/HotProduct/IdentifiableProduct.php @@ -0,0 +1,45 @@ +product_id = $product_id; + $this->product_name = $product_name; + $this->product_description = $product_description; + $this->product_image = $product_image; + $this->price = $price; + $this->currency = $currency; + $this->quantity = $quantity; + $this->product_availability = $product_availability; + $this->additional_product_images = $additional_product_images; + $this->product_attributes = $product_attributes; + $this->setEan($ean); + $this->setLink($product_link); + } + + public function getId(): string + { + return $this->product_id; + } +} diff --git a/modules/inpostizi/src/Common/HotProduct/Product.php b/modules/inpostizi/src/Common/HotProduct/Product.php new file mode 100644 index 00000000..6efe1bfb --- /dev/null +++ b/modules/inpostizi/src/Common/HotProduct/Product.php @@ -0,0 +1,52 @@ +product_name = $product_name; + $this->product_description = $product_description; + $this->product_image = $product_image; + $this->price = $price; + $this->currency = $currency; + $this->quantity = $quantity; + $this->product_availability = $product_availability; + $this->additional_product_images = $additional_product_images; + $this->product_attributes = $product_attributes; + $this->setEan($ean); + $this->setLink($product_link); + } + + public function asIdentifiable(string $id): IdentifiableProduct + { + return new IdentifiableProduct( + $id, + $this->product_name, + $this->product_description, + $this->product_image, + $this->price, + $this->currency, + $this->quantity, + $this->ean, + $this->product_availability, + $this->additional_product_images, + $this->product_attributes, + $this->product_link + ); + } +} diff --git a/modules/inpostizi/src/Common/HotProduct/ProductAvailability.php b/modules/inpostizi/src/Common/HotProduct/ProductAvailability.php new file mode 100644 index 00000000..0263d49b --- /dev/null +++ b/modules/inpostizi/src/Common/HotProduct/ProductAvailability.php @@ -0,0 +1,39 @@ +start_date = $start_date; + $this->end_date = $end_date; + } + + public function getStartDate(): ?\DateTimeImmutable + { + return $this->start_date; + } + + public function getEndDate(): ?\DateTimeImmutable + { + return $this->end_date; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/Common/HotProduct/ProductTrait.php b/modules/inpostizi/src/Common/HotProduct/ProductTrait.php new file mode 100644 index 00000000..335c4602 --- /dev/null +++ b/modules/inpostizi/src/Common/HotProduct/ProductTrait.php @@ -0,0 +1,158 @@ +ean; + } + + public function getAvailability(): ?ProductAvailability + { + return $this->product_availability; + } + + public function getName(): string + { + return $this->product_name; + } + + public function getDescription(): string + { + return $this->product_description; + } + + public function getImageUrl(): string + { + return $this->product_image; + } + + /** + * @return ProductImage[] + */ + public function getAdditionalImages(): array + { + return $this->additional_product_images; + } + + public function getPrice(): Price + { + return $this->price; + } + + public function getCurrency(): Currency + { + return $this->currency; + } + + /** + * @return Quantity + */ + public function getQuantity(): Quantity + { + return $this->quantity; + } + + /** + * @return ProductAttribute[] + */ + public function getAttributes(): array + { + return $this->product_attributes; + } + + public function getLink(): string + { + return $this->product_link; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } + + private function setLink(string $link): void + { + if ('' === $link) { + @trigger_error('Passing a product link will be required in the future version of InPost Pay.', E_USER_DEPRECATED); + } + + $this->product_link = $link; + } + + private function setEan(?string $ean): void + { + if ('' === $ean = (string) $ean) { + @trigger_error('Passing an EAN code will be required in the future version of InPost Pay.', E_USER_DEPRECATED); + } + + $this->ean = $ean; + } +} diff --git a/modules/inpostizi/src/Common/HotProduct/Quantity.php b/modules/inpostizi/src/Common/HotProduct/Quantity.php new file mode 100644 index 00000000..acc2df29 --- /dev/null +++ b/modules/inpostizi/src/Common/HotProduct/Quantity.php @@ -0,0 +1,77 @@ +available_quantity = $available_quantity; + $this->quantity_type = $quantity_type; + $this->quantity_unit = $quantity_unit; + } + + /** + * @return static + */ + public static function integer(int $available_quantity, ?string $quantity_unit = null): self + { + return new static($available_quantity, QuantityType::Integer(), $quantity_unit); + } + + /** + * @return static + */ + public static function decimal(float $available_quantity, ?string $quantity_unit = null): self + { + return new static($available_quantity, QuantityType::Decimal(), $quantity_unit); + } + + /** + * @return T + */ + public function getAvailableQuantity() + { + return $this->available_quantity; + } + + public function getType(): QuantityType + { + return $this->quantity_type; + } + + public function getUnit(): ?string + { + return $this->quantity_unit; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/Common/Order/Consent.php b/modules/inpostizi/src/Common/Order/Consent.php new file mode 100644 index 00000000..b0abbacb --- /dev/null +++ b/modules/inpostizi/src/Common/Order/Consent.php @@ -0,0 +1,50 @@ +consent_id = $consent_id; + $this->consent_version = $consent_version; + $this->is_accepted = $is_accepted; + } + + public function getId(): string + { + return $this->consent_id; + } + + public function getVersion(): string + { + return $this->consent_version; + } + + public function isAccepted(): bool + { + return $this->is_accepted; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/Common/Order/Delivery.php b/modules/inpostizi/src/Common/Order/Delivery.php new file mode 100644 index 00000000..5828a181 --- /dev/null +++ b/modules/inpostizi/src/Common/Order/Delivery.php @@ -0,0 +1,104 @@ +delivery_type = $delivery_type; + $this->delivery_codes = $delivery_codes; + $this->mail = $mail; + $this->phone_number = $phone_number; + $this->delivery_point = $delivery_point; + $this->delivery_address = $delivery_address; + $this->courier_note = $courier_note; + } + + public function getType(): DeliveryType + { + return $this->delivery_type; + } + + /** + * @return ServiceCode[] + */ + public function getOptionalServiceCodes(): array + { + return $this->delivery_codes; + } + + public function getEmail(): ?string + { + return $this->mail; + } + + public function getPhoneNumber(): ?PhoneNumber + { + return $this->phone_number; + } + + public function getPoint(): ?string + { + return $this->delivery_point; + } + + public function getAddress(): ?DeliveryAddress + { + return $this->delivery_address; + } + + public function getCourierNote(): ?string + { + return $this->courier_note; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/Common/Order/DeliveryAddress.php b/modules/inpostizi/src/Common/Order/DeliveryAddress.php new file mode 100644 index 00000000..e82f0f98 --- /dev/null +++ b/modules/inpostizi/src/Common/Order/DeliveryAddress.php @@ -0,0 +1,72 @@ +name = $name; + $this->country_code = $country_code; + $this->address = $address; + $this->city = $city; + $this->postal_code = $postal_code; + } + + public function getName(): ?string + { + return $this->name; + } + + public function getCountryCode(): ?string + { + return $this->country_code; + } + + public function getAddress(): string + { + return $this->address; + } + + public function getCity(): string + { + return $this->city; + } + + public function getPostalCode(): string + { + return $this->postal_code; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/Common/Order/MerchantOrderStatus.php b/modules/inpostizi/src/Common/Order/MerchantOrderStatus.php new file mode 100644 index 00000000..2167c0fa --- /dev/null +++ b/modules/inpostizi/src/Common/Order/MerchantOrderStatus.php @@ -0,0 +1,17 @@ +order_status = $order_status; + $this->order_merchant_status_description = $order_merchant_status_description; + $this->delivery_references_list = $delivery_references_list; + } + + public function getStatus(): ?MerchantOrderStatus + { + return $this->order_status; + } + + public function getStatusDescription(): ?string + { + return $this->order_merchant_status_description; + } + + /** + * @return string[]|null + */ + public function getDeliveryReferencesList(): ?array + { + return $this->delivery_references_list; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/Common/Order/OrderAdditionalParameter.php b/modules/inpostizi/src/Common/Order/OrderAdditionalParameter.php new file mode 100644 index 00000000..9416c05b --- /dev/null +++ b/modules/inpostizi/src/Common/Order/OrderAdditionalParameter.php @@ -0,0 +1,29 @@ +key = $key; + $this->value = $value; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/Common/Order/OrderAdditionalParameters.php b/modules/inpostizi/src/Common/Order/OrderAdditionalParameters.php new file mode 100644 index 00000000..eeafa0cf --- /dev/null +++ b/modules/inpostizi/src/Common/Order/OrderAdditionalParameters.php @@ -0,0 +1,41 @@ +addParameter($parameter); + } + } + + public function addParameter(OrderAdditionalParameter $parameter): void + { + $this->order_additional_parameters[] = $parameter; + } + + /** + * @return OrderAdditionalParameter[] + */ + public function getParameters(): array + { + return $this->order_additional_parameters; + } + + public function jsonSerialize(): array + { + return $this->order_additional_parameters; + } +} diff --git a/modules/inpostizi/src/Common/Order/Product.php b/modules/inpostizi/src/Common/Order/Product.php new file mode 100644 index 00000000..f915fa8b --- /dev/null +++ b/modules/inpostizi/src/Common/Order/Product.php @@ -0,0 +1,165 @@ +product_id = $product_id; + $this->product_category = $product_category; + $this->ean = $ean; + $this->product_name = $product_name; + $this->product_description = $product_description; + $this->product_link = $product_link; + $this->product_image = $product_image; + $this->base_price = $base_price; + $this->quantity = $quantity; + $this->product_attributes = $product_attributes; + $this->variants = $variants; + $this->additional_product_images = $additional_product_images; + } + + public function getId(): string + { + return $this->product_id; + } + + public function getCategory(): ?string + { + return $this->product_category; + } + + public function getEan(): ?string + { + return $this->ean; + } + + public function getName(): string + { + return $this->product_name; + } + + public function getDescription(): ?string + { + return $this->product_description; + } + + public function getLink(): ?string + { + return $this->product_link; + } + + public function getImageUrl(): ?string + { + return $this->product_image; + } + + public function getBasePrice(): Price + { + return $this->base_price; + } + + public function getQuantity(): Quantity + { + return $this->quantity; + } + + public function getAttributes(): array + { + return $this->product_attributes; + } + + /** + * @return ProductVariant[] + */ + public function getVariants(): array + { + return $this->variants; + } + + /** + * @return ProductImage[] + */ + public function getAdditionalProductImages(): array + { + return $this->additional_product_images; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/Common/Order/Quantity.php b/modules/inpostizi/src/Common/Order/Quantity.php new file mode 100644 index 00000000..6a179a20 --- /dev/null +++ b/modules/inpostizi/src/Common/Order/Quantity.php @@ -0,0 +1,77 @@ +quantity = $quantity; + $this->quantity_type = $quantity_type; + $this->quantity_unit = $quantity_unit; + } + + /** + * @return self + */ + public static function integer(int $quantity, ?string $quantity_unit = null): self + { + return new self($quantity, QuantityType::Integer(), $quantity_unit); + } + + /** + * @return self + */ + public static function decimal(float $quantity, ?string $quantity_unit = null): self + { + return new self($quantity, QuantityType::Decimal(), $quantity_unit); + } + + /** + * @return T + */ + public function getQuantity() + { + return $this->quantity; + } + + public function getType(): QuantityType + { + return $this->quantity_type; + } + + public function getUnit(): ?string + { + return $this->quantity_unit; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/Common/PaymentType.php b/modules/inpostizi/src/Common/PaymentType.php new file mode 100644 index 00000000..db6d3cfb --- /dev/null +++ b/modules/inpostizi/src/Common/PaymentType.php @@ -0,0 +1,77 @@ +country_prefix = $country_prefix; + $this->phone = $phone; + } + + public function getCountryPrefix(): string + { + return $this->country_prefix; + } + + public function getPhone(): string + { + return $this->phone; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/Common/Price.php b/modules/inpostizi/src/Common/Price.php new file mode 100644 index 00000000..c4db1a77 --- /dev/null +++ b/modules/inpostizi/src/Common/Price.php @@ -0,0 +1,80 @@ +net = $net; + $this->gross = $gross; + $this->vat = $vat; + } + + public function getNet(): float + { + return $this->net; + } + + public function getGross(): float + { + return $this->gross; + } + + public function getVat(): float + { + return $this->vat; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } + + public function add(self $price): self + { + return new self( + $this->net + $price->net, + $this->gross + $price->gross, + $this->vat + $price->vat + ); + } + + public function sub(self $price): self + { + return new self( + $this->net - $price->net, + $this->gross - $price->gross, + $this->vat - $price->vat + ); + } + + /** + * @param float|int $multiplier + */ + public function multiply($multiplier): self + { + return new self( + $this->net * $multiplier, + $this->gross * $multiplier, + $this->vat * $multiplier + ); + } +} diff --git a/modules/inpostizi/src/Common/PriceAmount.php b/modules/inpostizi/src/Common/PriceAmount.php new file mode 100644 index 00000000..f2f2ef5a --- /dev/null +++ b/modules/inpostizi/src/Common/PriceAmount.php @@ -0,0 +1,28 @@ +priceAmount = $priceAmount; + } + + public function getPriceAmount(): float + { + return $this->priceAmount; + } + + public function jsonSerialize(): float + { + return $this->priceAmount; + } +} diff --git a/modules/inpostizi/src/Common/Product/DeliveryProduct.php b/modules/inpostizi/src/Common/Product/DeliveryProduct.php new file mode 100644 index 00000000..d3943db1 --- /dev/null +++ b/modules/inpostizi/src/Common/Product/DeliveryProduct.php @@ -0,0 +1,41 @@ +delivery_type = $delivery_type; + $this->if_delivery_available = $if_delivery_available; + } + + public function getDeliveryType(): DeliveryType + { + return $this->delivery_type; + } + + public function isDeliveryAvailable(): bool + { + return $this->if_delivery_available; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/Common/Product/DeliveryRelatedProducts.php b/modules/inpostizi/src/Common/Product/DeliveryRelatedProducts.php new file mode 100644 index 00000000..48f857b9 --- /dev/null +++ b/modules/inpostizi/src/Common/Product/DeliveryRelatedProducts.php @@ -0,0 +1,52 @@ +delivery_type = $delivery_type; + $this->if_delivery_available = $if_delivery_available; + $this->if_delivery_free = $if_delivery_free; + } + + public function getDeliveryType(): DeliveryType + { + return $this->delivery_type; + } + + public function isDeliveryAvailable(): bool + { + return $this->if_delivery_available; + } + + public function isDeliveryFree(): bool + { + return $this->if_delivery_free; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/Common/Product/ProductAttribute.php b/modules/inpostizi/src/Common/Product/ProductAttribute.php new file mode 100644 index 00000000..8fce85c2 --- /dev/null +++ b/modules/inpostizi/src/Common/Product/ProductAttribute.php @@ -0,0 +1,39 @@ +attribute_name = $attribute_name; + $this->attribute_value = $attribute_value; + } + + public function getName(): string + { + return $this->attribute_name; + } + + public function getValue(): string + { + return $this->attribute_value; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/Common/Product/ProductImage.php b/modules/inpostizi/src/Common/Product/ProductImage.php new file mode 100644 index 00000000..156723a1 --- /dev/null +++ b/modules/inpostizi/src/Common/Product/ProductImage.php @@ -0,0 +1,39 @@ +small_size = $small_size; + $this->normal_size = $normal_size; + } + + public function getSmallSize(): string + { + return $this->small_size; + } + + public function getNormalSize(): string + { + return $this->normal_size; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/Common/Product/ProductVariant.php b/modules/inpostizi/src/Common/Product/ProductVariant.php new file mode 100644 index 00000000..a334954e --- /dev/null +++ b/modules/inpostizi/src/Common/Product/ProductVariant.php @@ -0,0 +1,72 @@ +variant_id = $variant_id; + $this->variant_name = $variant_name; + $this->variant_description = $variant_description; + $this->variant_type = $variant_type; + $this->variant_values = $variant_values; + } + + public function getId(): string + { + return $this->variant_id; + } + + public function getName(): string + { + return $this->variant_name; + } + + public function getDescription(): ?string + { + return $this->variant_description; + } + + public function getType(): ?string + { + return $this->variant_type; + } + + public function getValues(): ?string + { + return $this->variant_values; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/Common/PromoCode.php b/modules/inpostizi/src/Common/PromoCode.php new file mode 100644 index 00000000..b8b79d38 --- /dev/null +++ b/modules/inpostizi/src/Common/PromoCode.php @@ -0,0 +1,52 @@ +name = $name; + $this->promo_code_value = $promo_code_value; + $this->regulation_type = $regulation_type; + } + + public function getName(): string + { + return $this->name; + } + + public function getCode(): string + { + return $this->promo_code_value; + } + + public function getRegulationType(): ?string + { + return $this->regulation_type; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/Common/QuantityType.php b/modules/inpostizi/src/Common/QuantityType.php new file mode 100644 index 00000000..ec7c1b0d --- /dev/null +++ b/modules/inpostizi/src/Common/QuantityType.php @@ -0,0 +1,17 @@ +weight = $weight; + } + + public function getWeight(): float + { + return $this->weight; + } + + public function add(self $weight): self + { + return new self( + $this->getWeight() + $weight->getWeight() + ); + } + + public function sub(self $weight): self + { + return new self( + $this->getWeight() - $weight->getWeight() + ); + } + + /** + * @param $multiplier float|int + * + * @return self + */ + public function multiply($multiplier): self + { + return new self( + $this->getWeight() * $multiplier + ); + } + + public function equals(self $weight): bool + { + return $this->getWeight() === $weight->getWeight(); + } + + public function greaterThan(self $weight): bool + { + return $this->getWeight() > $weight->getWeight(); + } + + public function greaterThanOrEqual(self $weight): bool + { + return $this->getWeight() >= $weight->getWeight(); + } + + public function lessThan(self $weight): bool + { + return $this->getWeight() < $weight->getWeight(); + } + + public function lessThanOrEqual(self $weight): bool + { + return $this->getWeight() <= $weight->getWeight(); + } +} diff --git a/modules/inpostizi/src/Configuration/Adapter/Configuration.php b/modules/inpostizi/src/Configuration/Adapter/Configuration.php new file mode 100644 index 00000000..ee172c36 --- /dev/null +++ b/modules/inpostizi/src/Configuration/Adapter/Configuration.php @@ -0,0 +1,124 @@ +db = $db ?? \Db::getInstance(); + } + + public function get(string $key, ?int $shopId = null, ?int $languageId = null) + { + return $this->doGet($key, $languageId, $shopId); + } + + public function getGlobal(string $key) + { + return $this->doGet($key, null, 0, 0); + } + + public function getLocalized(string $key): array + { + $configuration = []; + + foreach (\Language::getIDs(false) as $languageId) { + $configuration[$languageId] = $this->doGet($key, (int) $languageId); + } + + return $configuration; + } + + public function set(string $key, $value, ?int $shopId = null): void + { + $this->doSet($key, $value, $shopId); + } + + public function setGlobal(string $key, $value): void + { + $this->doSet($key, $value, 0, 0); + } + + public function remove(string $key): void + { + if (\Configuration::deleteByName($key)) { + return; + } + + throw new \RuntimeException(sprintf('Could not remove configuration values for key "%s".', $key)); + } + + public function has(string $key): bool + { + return \Configuration::hasKey($key); + } + + public function isValidKey(string $key): bool + { + return strlen($key) <= \Configuration::$definition['fields']['name']['size'] + && \Validate::isConfigName($key); + } + + public function removeMatching(string $keyPattern): void + { + $keyPattern = str_replace('_', '#_', pSQL($keyPattern)); + $keyPatterns = $this->getSqlKeyPatterns($keyPattern); + + $result = $this->db->delete('configuration', implode(' AND ', array_map(static function (string $pattern): string { + return 'name LIKE "' . $pattern . '" ESCAPE "#"'; + }, $keyPatterns))); + + if (false === $result) { + throw new \RuntimeException(sprintf('Could not remove configuration values matching pattern "%s".', $keyPattern)); + } + } + + private function getSqlKeyPatterns(string $pattern): array + { + $parts = preg_split('/\*+/', $pattern, 0); + + foreach ($parts as $key => $part) { + if ('' !== $part) { + continue; + } + + if (isset($parts[$key - 1])) { + $parts[$key - 1] .= '%'; + } + + if (isset($parts[$key + 1])) { + $parts[$key + 1] = '%' . $parts[$key + 1]; + } + + unset($parts[$key]); + } + + return $parts; + } + + private function doGet(string $key, ?int $languageId = null, ?int $shopId = null, ?int $shopGroupId = null) + { + $value = \Configuration::get($key, $languageId, $shopGroupId, $shopId, null); + + return '' === $value ? null : $value; + } + + private function doSet(string $key, $value, ?int $shopId = null, ?int $shopGroupId = null): void + { + if (\Configuration::updateValue($key, $value, false, $shopGroupId, $shopId)) { + return; + } + + throw new \RuntimeException(sprintf('Could not update the configuration value for key "%s".', $key)); + } +} diff --git a/modules/inpostizi/src/Configuration/AdvancedConfiguration.php b/modules/inpostizi/src/Configuration/AdvancedConfiguration.php new file mode 100644 index 00000000..24b91813 --- /dev/null +++ b/modules/inpostizi/src/Configuration/AdvancedConfiguration.php @@ -0,0 +1,40 @@ + + */ +final class AdvancedConfiguration implements AdvancedConfigurationInterface, PersistentConfigurationInterface +{ + private const DEBUG_ENABLED = 'INPOST_PAY_DEBUG_ENABLED'; + + /** + * @var ConfigurationInterface + */ + private $configuration; + + public function __construct(ConfigurationInterface $configuration) + { + $this->configuration = $configuration; + } + + public function copy(): AdvancedConfigurationInterface + { + return new DTO\AdvancedConfiguration( + $this->isDebugEnabled() + ); + } + + public function persist(AdvancedConfigurationInterface $configuration): void + { + $this->configuration->set(self::DEBUG_ENABLED, $configuration->isDebugEnabled()); + } + + public function isDebugEnabled(): bool + { + return (bool) $this->configuration->get(self::DEBUG_ENABLED); + } +} diff --git a/modules/inpostizi/src/Configuration/AdvancedConfigurationInterface.php b/modules/inpostizi/src/Configuration/AdvancedConfigurationInterface.php new file mode 100644 index 00000000..6b78b06a --- /dev/null +++ b/modules/inpostizi/src/Configuration/AdvancedConfigurationInterface.php @@ -0,0 +1,10 @@ + + */ +final class ApiConfiguration implements ApiConfigurationInterface, AccessTokenRepositoryInterface, PersistentConfigurationInterface +{ + public const OAUTH2_CLIENT_SECRET = 'INPOST_PAY_client_secret'; + public const ACCESS_TOKEN = 'INPOST_PAY_ACCESS_TOKEN'; + + private const ENVIRONMENT_TYPE = 'INPOST_PAY_environment'; + private const OAUTH2_CLIENT_ID = 'INPOST_PAY_client_id'; + private const MERCHANT_CLIENT_ID = 'INPOST_PAY_MERCHANT_CLIENT_ID'; + + /** + * @var ConfigurationInterface + */ + private $configuration; + + /** + * @var SerializerInterface + */ + private $serializer; + + /** + * @var EnvironmentFactoryInterface + */ + private $environmentFactory; + + /** + * @var EnvironmentInterface|null + */ + private $environment; + + /** + * @var ClientCredentialsInterface|null + */ + private $clientCredentials; + + /** + * @var AccessTokenInterface|null + */ + private $accessToken; + + public function __construct(ConfigurationInterface $configuration, SerializerInterface $serializer, EnvironmentFactoryInterface $environmentFactory) + { + $this->configuration = $configuration; + $this->serializer = $serializer; + $this->environmentFactory = $environmentFactory; + } + + public function getEnvironment(): EnvironmentInterface + { + return $this->environment ?? $this->environment = $this->createEnvironment(); + } + + public function getMerchantClientId(): ?string + { + $value = $this->configuration->get(self::MERCHANT_CLIENT_ID); + + return $value ? (string) $value : null; + } + + public function getClientCredentials(): ?ClientCredentialsInterface + { + if (isset($this->clientCredentials)) { + return $this->clientCredentials; + } + + $clientId = $this->configuration->get(self::OAUTH2_CLIENT_ID); + $clientSecret = $this->configuration->get(self::OAUTH2_CLIENT_SECRET); + + if (null === $clientId || null === $clientSecret) { + return null; + } + + return $this->clientCredentials = new ClientCredentials($clientId, $clientSecret); + } + + public function getToken(): ?AccessTokenInterface + { + if (isset($this->accessToken)) { + return $this->accessToken; + } + + $value = $this->configuration->get(self::ACCESS_TOKEN); + + if (null === $value) { + return null; + } + + try { + return $this->accessToken = $this->serializer->deserialize($value, BearerToken::class, 'json'); + } catch (ExceptionInterface $e) { + return null; + } + } + + public function saveToken(AccessTokenInterface $accessToken): void + { + $value = $this->serializer->serialize($accessToken, 'json'); + $this->configuration->set(self::ACCESS_TOKEN, $value); + $this->accessToken = $accessToken; + } + + public function deleteToken(): void + { + $this->configuration->set(self::ACCESS_TOKEN, null); + $this->accessToken = null; + } + + public function copy(): ApiConfigurationInterface + { + $configuration = new DTO\ApiConfiguration( + $this->getEnvironmentType(), + $this->getClientCredentials() + ); + + return $configuration->setMerchantClientId($this->getMerchantClientId()); + } + + public function persist(ApiConfigurationInterface $configuration): void + { + $this->configuration->set(self::MERCHANT_CLIENT_ID, $configuration->getMerchantClientId()); + + $this->setEnvironment($configuration); + $this->setClientCredentials($configuration->getClientCredentials()); + $this->deleteToken(); + } + + private function getEnvironmentType(): EnvironmentType + { + $value = (int) $this->configuration->get(self::ENVIRONMENT_TYPE); + + return EnvironmentType::tryFrom($value) ?? EnvironmentType::Production(); + } + + private function setEnvironment(ApiConfigurationInterface $configuration): void + { + $environment = $configuration->getEnvironment(); + $this->configuration->set(self::ENVIRONMENT_TYPE, $environment->getType()->value); + $this->environment = $environment; + } + + private function setClientCredentials(?ClientCredentialsInterface $credentials = null): void + { + $this->configuration->set(self::OAUTH2_CLIENT_ID, $credentials ? $credentials->getClientId() : null); + $this->configuration->set(self::OAUTH2_CLIENT_SECRET, $credentials ? $credentials->getClientSecret() : null); + $this->clientCredentials = $credentials; + } + + private function createEnvironment(): EnvironmentInterface + { + $type = $this->getEnvironmentType(); + + return $this->environmentFactory->createEnvironment($type); + } +} diff --git a/modules/inpostizi/src/Configuration/ApiConfigurationInterface.php b/modules/inpostizi/src/Configuration/ApiConfigurationInterface.php new file mode 100644 index 00000000..e6eb1a54 --- /dev/null +++ b/modules/inpostizi/src/Configuration/ApiConfigurationInterface.php @@ -0,0 +1,15 @@ + + */ +final class ConsentsConfiguration implements ConsentsConfigurationInterface, PersistentConfigurationInterface +{ + private const CONSENTS = 'INPOST_PAY_CONSENTS'; + + /** + * @var ShopAwareConfigurationInterface + */ + private $configuration; + + /** + * @var SerializerInterface + */ + private $serializer; + + private $consents; + + public function __construct(ShopAwareConfigurationInterface $configuration, SerializerInterface $serializer) + { + $this->configuration = $configuration; + $this->serializer = $serializer; + } + + public function getConsents(?int $shopId = null): array + { + if (!isset($this->consents[(int) $shopId])) { + $this->consents[(int) $shopId] = $this->loadConsents($shopId); + } + + return array_map(static function (Consent $consent): Consent { + return clone $consent; + }, $this->consents[(int) $shopId]); + } + + /** + * @return Consent[] + */ + private function loadConsents(?int $shopId): array + { + $config = $this->configuration->get(self::CONSENTS, $shopId); + + if (null === $config) { + return []; + } + + try { + return $this->serializer->deserialize($config, Consent::class . '[]', 'json'); + } catch (ExceptionInterface $e) { + return []; + } + } + + public function copy(): ConsentsConfigurationInterface + { + return new UpdateConsentsConfigurationCommand(...$this->getConsents()); + } + + public function persist(ConsentsConfigurationInterface $configuration): void + { + $consents = $configuration->getConsents(); + + $value = $this->serializer->serialize($consents, 'json'); + $this->configuration->set(self::CONSENTS, $value); + + $this->consents[0] = array_map(static function (Consent $consent): Consent { + return clone $consent; + }, $consents); + } +} diff --git a/modules/inpostizi/src/Configuration/ConsentsConfigurationInterface.php b/modules/inpostizi/src/Configuration/ConsentsConfigurationInterface.php new file mode 100644 index 00000000..2b52741a --- /dev/null +++ b/modules/inpostizi/src/Configuration/ConsentsConfigurationInterface.php @@ -0,0 +1,15 @@ +debugEnabled = $debugEnabled; + } + + public function isDebugEnabled(): bool + { + return $this->debugEnabled; + } + + public function setDebugEnabled(bool $debugEnabled): self + { + $this->debugEnabled = $debugEnabled; + + return $this; + } +} diff --git a/modules/inpostizi/src/Configuration/DTO/ApiConfiguration.php b/modules/inpostizi/src/Configuration/DTO/ApiConfiguration.php new file mode 100644 index 00000000..697a3927 --- /dev/null +++ b/modules/inpostizi/src/Configuration/DTO/ApiConfiguration.php @@ -0,0 +1,85 @@ +environmentType = $environmentType; + $this->clientCredentials = $clientCredentials; + } + + public function getEnvironment(): EnvironmentInterface + { + $type = $this->getEnvironmentType(); + + return (new EnvironmentFactory())->createEnvironment($type); + } + + public function getEnvironmentType(): EnvironmentType + { + return $this->environmentType ?? EnvironmentType::Production(); + } + + public function setEnvironmentType(?EnvironmentType $environmentType): self + { + $this->environmentType = $environmentType; + + return $this; + } + + public function getClientCredentials(): ?ClientCredentialsInterface + { + return $this->clientCredentials; + } + + public function setClientCredentials(?ClientCredentialsInterface $clientCredentials): self + { + $this->clientCredentials = $clientCredentials; + + return $this; + } + + public function getMerchantClientId(): ?string + { + return $this->merchantClientId; + } + + public function setMerchantClientId(?string $merchantClientId): self + { + $this->merchantClientId = $merchantClientId; + + return $this; + } +} diff --git a/modules/inpostizi/src/Configuration/DTO/Consent.php b/modules/inpostizi/src/Configuration/DTO/Consent.php new file mode 100644 index 00000000..8b8254d0 --- /dev/null +++ b/modules/inpostizi/src/Configuration/DTO/Consent.php @@ -0,0 +1,267 @@ +link = $link ?? new ConsentLink(); + $this->descriptions = $descriptions; + $this->requirementType = $requirementType; + $this->additionalLinks = $additionalLinks; + $this->dateUpdated = $dateUpdated; + } + + public static function normalize(self $consent): ?string + { + return $consent->getId(); + } + + public function getLink(): ConsentLink + { + return $this->link; + } + + public function setLink(ConsentLink $link): self + { + $this->link = $link; + + return $this; + } + + public function getId(): ?string + { + return $this->link->getId(); + } + + public function getLinkLabel(int $languageId): ?string + { + return $this->link->getLabel($languageId); + } + + /** + * @return array consent text by language ID + */ + public function getDescriptions(): array + { + return $this->descriptions; + } + + public function getDescription(int $languageId): string + { + return $this->descriptions[$languageId] ?? ''; + } + + /** + * @param array $descriptions consent text by language ID + */ + public function setDescriptions(array $descriptions): self + { + $this->dirty = $this->dirty || $this->descriptions !== $descriptions; + $this->descriptions = $descriptions; + + return $this; + } + + public function getRequirementType(): ?ConsentRequirementType + { + return $this->requirementType; + } + + public function setRequirementType(?ConsentRequirementType $requirementType): self + { + $this->dirty = $this->dirty || $this->requirementType !== $requirementType; + $this->requirementType = $requirementType; + + return $this; + } + + /** + * @return ConsentLink[] + */ + public function getAdditionalLinks(): array + { + return $this->additionalLinks; + } + + public function addAdditionalLink(ConsentLink $link): self + { + if (!in_array($link, $this->additionalLinks, true)) { + $this->dirty = true; + $this->additionalLinks[] = $link; + } + + return $this; + } + + public function removeAdditionalLink(ConsentLink $link): self + { + $key = array_search($link, $this->additionalLinks, true); + + if (false !== $key) { + $this->dirty = true; + unset($this->additionalLinks[$key]); + } + + return $this; + } + + public function getDateUpdated(): ?\DateTimeImmutable + { + return $this->dateUpdated; + } + + public function setDateUpdated(?\DateTimeImmutable $dateUpdated): self + { + if (null === $this->dateUpdated || $this->isDirty()) { + $this->dateUpdated = $dateUpdated; + $this->onUpdated(); + } + + return $this; + } + + public function getVersion(): string + { + if (null === $this->dateUpdated) { + return '0'; + } + + return (string) $this->dateUpdated->getTimestamp(); + } + + public function jsonSerialize(): array + { + return [ + 'link' => $this->link, + 'descriptions' => $this->descriptions, + 'requirementType' => $this->requirementType, + 'additionalLinks' => $this->additionalLinks, + 'dateUpdated' => null !== $this->dateUpdated ? $this->dateUpdated->format(\DateTime::RFC3339) : null, + ]; + } + + public function denormalize(DenormalizerInterface $denormalizer, $data, $format = null, array $context = []): void + { + if (!is_array($data)) { + throw new UnexpectedValueException('Expected data to be an array.'); + } + + if (!isset($data['descriptions'])) { + throw new UnexpectedValueException('Normalized data does not contain all of the required parameters.'); + } + + if (isset($data['link'])) { + $this->link = $denormalizer->denormalize($data['link'], ConsentLink::class, $format, $context); + } else { + $this->link = new ConsentLink($data['id'] ?? null, $data['cmsPageId'] ?? null); + } + + $this->descriptions = $data['descriptions']; + $this->requirementType = isset($data['requirementType']) ? ConsentRequirementType::tryFrom($data['requirementType']) : null; + $this->additionalLinks = isset($data['additionalLinks']) ? $denormalizer->denormalize($data['additionalLinks'], ConsentLink::class . '[]', $format, $context) : []; + $this->dateUpdated = isset($data['dateUpdated']) ? $denormalizer->denormalize($data['dateUpdated'], \DateTimeImmutable::class, $format, $context) : null; + } + + public function __clone() + { + $this->link = clone $this->link; + $this->additionalLinks = array_map(static function (ConsentLink $link): ConsentLink { + return clone $link; + }, $this->additionalLinks); + } + + private function isDirty(): bool + { + if ($this->dirty) { + return true; + } + + if ($this->link->isDirty()) { + return true; + } + + foreach ($this->additionalLinks as $link) { + if ($link->isDirty()) { + return true; + } + } + + return false; + } + + private function onUpdated(): void + { + $this->link->onConsentUpdated(); + + foreach ($this->additionalLinks as $link) { + $link->onConsentUpdated(); + } + + $this->dirty = false; + } +} diff --git a/modules/inpostizi/src/Configuration/DTO/ConsentLink.php b/modules/inpostizi/src/Configuration/DTO/ConsentLink.php new file mode 100644 index 00000000..fab061b4 --- /dev/null +++ b/modules/inpostizi/src/Configuration/DTO/ConsentLink.php @@ -0,0 +1,125 @@ +id = $id; + $this->cmsPageId = $cmsPageId; + $this->labels = $labels; + } + + public static function normalize(self $link): ?string + { + return $link->getId(); + } + + public function getId(): ?string + { + return $this->id; + } + + public function setId(?string $id): self + { + $this->id = $id; + + return $this; + } + + public function getCmsPageId(): ?int + { + return $this->cmsPageId; + } + + public function setCmsPageId(?\CMS $cmsPage): self + { + $cmsPageId = null === $cmsPage ? null : (int) $cmsPage->id; + + $this->dirty = $this->dirty || $this->cmsPageId !== $cmsPageId; + $this->cmsPageId = $cmsPageId; + + return $this; + } + + /** + * @return array label by language ID + */ + public function getLabels(): array + { + return $this->labels; + } + + public function getLabel(int $languageId): ?string + { + return $this->labels[$languageId] ?? null; + } + + /** + * @param array $labels label by language ID + */ + public function setLabels(array $labels): self + { + $this->dirty = $this->dirty || $this->labels !== $labels; + $this->labels = $labels; + + return $this; + } + + public function isDirty(): bool + { + return $this->dirty; + } + + public function onConsentUpdated(): void + { + $this->dirty = false; + } + + public function jsonSerialize(): array + { + return [ + 'id' => $this->id, + 'cmsPageId' => $this->cmsPageId, + 'labels' => $this->labels, + ]; + } +} diff --git a/modules/inpostizi/src/Configuration/DTO/GeneralConfiguration.php b/modules/inpostizi/src/Configuration/DTO/GeneralConfiguration.php new file mode 100644 index 00000000..14440d0e --- /dev/null +++ b/modules/inpostizi/src/Configuration/DTO/GeneralConfiguration.php @@ -0,0 +1,181 @@ +enabledForEveryone = $enabledForEveryone; + $this->maxSuggestedProducts = $maxSuggestedProducts; + $this->thankYouDisplayHook = $thankYouDisplayHook; + $this->productCardDisplayHook = $productCardDisplayHook; + $this->checkoutButtonDisplayHook = $checkoutButtonDisplayHook; + $this->fullPageCacheModuleInUse = $fullPageCacheModuleInUse; + $this->sendAnalyticsData = $sendAnalyticsData; + } + + public function isEnabledForEveryone(): bool + { + return $this->enabledForEveryone; + } + + public function setEnabledForEveryone(bool $enabledForEveryone): GeneralConfiguration + { + $this->enabledForEveryone = $enabledForEveryone; + + return $this; + } + + public function getMaxSuggestedProducts(?int $shopId = null): ?int + { + return $this->maxSuggestedProducts; + } + + public function setMaxSuggestedProducts(?int $maxSuggestedProducts): GeneralConfiguration + { + $this->maxSuggestedProducts = $maxSuggestedProducts; + + return $this; + } + + public function getThankYouDisplayHook(?int $shopId = null): ?string + { + return $this->thankYouDisplayHook; + } + + public function setThankYouDisplayHook(?string $thankYouDisplayHook): GeneralConfiguration + { + $this->thankYouDisplayHook = $thankYouDisplayHook; + + return $this; + } + + public function getProductCardDisplayHook(?int $shopId = null): ?string + { + return $this->productCardDisplayHook; + } + + public function setProductCardDisplayHook(?string $productCardDisplayHook): GeneralConfiguration + { + $this->productCardDisplayHook = $productCardDisplayHook; + + return $this; + } + + public function getCheckoutButtonDisplayHook(?int $shopId = null): ?string + { + return $this->checkoutButtonDisplayHook; + } + + public function setCheckoutButtonDisplayHook(?string $checkoutButtonDisplayHook): GeneralConfiguration + { + $this->checkoutButtonDisplayHook = $checkoutButtonDisplayHook; + + return $this; + } + + public function isFullPageCacheModuleInUse(?int $shopId = null): bool + { + return $this->fullPageCacheModuleInUse; + } + + public function setFullPageCacheModuleInUse(bool $fullPageCacheModuleInUse): GeneralConfiguration + { + $this->fullPageCacheModuleInUse = $fullPageCacheModuleInUse; + + return $this; + } + + public function isSendAnalyticsData(?int $shopId = null): bool + { + return $this->sendAnalyticsData; + } + + public function setSendAnalyticsData(bool $sendAnalyticsData): GeneralConfiguration + { + $this->sendAnalyticsData = $sendAnalyticsData; + + return $this; + } + + public function getDefaultPromoDetailsPageId(?int $shopId = null): ?int + { + return $this->defaultPromoDetailsPageId; + } + + /** + * @param int|null $cmsId ID of {@see \CMS} + * + * @return $this + */ + public function setDefaultPromoDetailsPageId(?int $cmsId): self + { + $this->defaultPromoDetailsPageId = $cmsId; + + return $this; + } +} diff --git a/modules/inpostizi/src/Configuration/DTO/GuiConfiguration.php b/modules/inpostizi/src/Configuration/DTO/GuiConfiguration.php new file mode 100644 index 00000000..ce03f450 --- /dev/null +++ b/modules/inpostizi/src/Configuration/DTO/GuiConfiguration.php @@ -0,0 +1,110 @@ + configurations by BindingPlace value + * + * @Assert\Valid + * @Assert\All({ + * @Assert\NotNull, + * }) + */ + private $displayConfigurations = []; + + private static $supportedBindingPlaces; + + /** + * @param WidgetDisplayConfigurationInterface[] $displayConfigurations + */ + public function __construct(array $displayConfigurations = []) + { + foreach ($displayConfigurations as $displayConfiguration) { + $this->addDisplayConfiguration($displayConfiguration); + } + } + + public static function getSupportedBindingPlaces(): array + { + if (isset(self::$supportedBindingPlaces)) { + return self::$supportedBindingPlaces; + } + + self::$supportedBindingPlaces = []; + + foreach (BindingPlace::getBindingWidgetDisplayPlaces() as $bindingPlace) { + self::$supportedBindingPlaces[$bindingPlace->value] = $bindingPlace; + } + + return self::$supportedBindingPlaces; + } + + public function getDisplayConfiguration(BindingPlace $bindingPlace): WidgetDisplayConfigurationInterface + { + $offset = $bindingPlace->value; + + return $this[$offset] ?? WidgetDisplayConfiguration::for($bindingPlace); + } + + public function addDisplayConfiguration(WidgetDisplayConfigurationInterface $displayConfiguration): void + { + $offset = $displayConfiguration->getWidgetConfiguration()->getBindingPlace()->value; + + $this[$offset] = $displayConfiguration; + } + + /** + * @return WidgetDisplayConfigurationInterface|null[] + */ + public function getDisplayConfigurations(): array + { + return $this->displayConfigurations; + } + + public function offsetExists($offset): bool + { + $supportedBindingPlaces = self::getSupportedBindingPlaces(); + + return isset($supportedBindingPlaces[$offset]); + } + + public function offsetGet($offset): ?WidgetDisplayConfigurationInterface + { + if (!isset($this[$offset])) { + throw new \DomainException(sprintf('Undefined offset: "%s".', $offset)); + } + + return $this->displayConfigurations[$offset] ?? null; + } + + public function offsetSet($offset, $value): void + { + if (!isset($this[$offset])) { + throw new \DomainException(sprintf('Undefined offset: "%s".', $offset)); + } + + if (null !== $value && !$value instanceof WidgetDisplayConfigurationInterface) { + throw new \InvalidArgumentException(sprintf('Expected null or an instance of "%s", "%s" given.', WidgetDisplayConfigurationInterface::class, get_debug_type($value))); + } + + $this->displayConfigurations[$offset] = $value; + } + + public function offsetUnset($offset): void + { + if (!isset($this[$offset])) { + return; + } + + $this->displayConfigurations[$offset] = null; + } +} diff --git a/modules/inpostizi/src/Configuration/DTO/HtmlStyles.php b/modules/inpostizi/src/Configuration/DTO/HtmlStyles.php new file mode 100644 index 00000000..14e72bbc --- /dev/null +++ b/modules/inpostizi/src/Configuration/DTO/HtmlStyles.php @@ -0,0 +1,158 @@ +marginLeft; + } + + public function setMarginLeft(?int $marginLeft): self + { + $this->marginLeft = $marginLeft; + + return $this; + } + + public function getMarginRight(): ?int + { + return $this->marginRight; + } + + public function setMarginRight(?int $marginRight): self + { + $this->marginRight = $marginRight; + + return $this; + } + + public function getMarginTop(): ?int + { + return $this->marginTop; + } + + public function setMarginTop(?int $marginTop): self + { + $this->marginTop = $marginTop; + + return $this; + } + + public function getMarginBottom(): ?int + { + return $this->marginBottom; + } + + public function setMarginBottom(?int $marginBottom): self + { + $this->marginBottom = $marginBottom; + + return $this; + } + + public function getJustifyContent(): ?string + { + return $this->justifyContent; + } + + /** + * @return $this + */ + public function setJustifyContent(?string $justifyContent): self + { + $this->justifyContent = $justifyContent; + + return $this; + } + + /** + * @return \Generator + */ + public function getIterator(): \Generator + { + if (null !== $this->marginLeft) { + yield 'margin-left' => sprintf('%dpx', $this->marginLeft); + } + + if (null !== $this->marginRight) { + yield 'margin-right' => sprintf('%dpx', $this->marginRight); + } + + if (null !== $this->marginTop) { + yield 'margin-top' => sprintf('%dpx', $this->marginTop); + } + + if (null !== $this->marginBottom) { + yield 'margin-bottom' => sprintf('%dpx', $this->marginBottom); + } + + if (null !== $this->justifyContent) { + yield 'display' => 'flex'; + yield 'flex-wrap' => 'wrap'; + yield 'justify-content' => $this->justifyContent; + } + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/Configuration/DTO/Order/MessageOptions.php b/modules/inpostizi/src/Configuration/DTO/Order/MessageOptions.php new file mode 100644 index 00000000..372a45a1 --- /dev/null +++ b/modules/inpostizi/src/Configuration/DTO/Order/MessageOptions.php @@ -0,0 +1,89 @@ +message = $message; + $this->appendIfApmDelivery = $appendIfApmDelivery; + $this->customFormat = $customFormat; + } + + public function getMessage(): ?string + { + return $this->message; + } + + public function setMessage(?string $message): self + { + $this->message = $message; + + return $this; + } + + public function getAppendIfApmDelivery(): ?bool + { + return $this->appendIfApmDelivery; + } + + public function setAppendIfApmDelivery(?bool $appendIfApmDelivery): self + { + $this->appendIfApmDelivery = $appendIfApmDelivery; + + return $this; + } + + public function isCustomFormat(): bool + { + return $this->customFormat; + } + + /** + * @ProcessableMessageFormat + */ + public function getFormat(): string + { + if ($this->customFormat) { + return (string) $this->message; + } + + if (!$this->appendIfApmDelivery) { + return MessageFormatterInterface::DEFAULT_FORMAT; + } + + return sprintf("%s\n\n{%% if \"APM\" == delivery_type %%}\n%s\n{%% endif %%}", MessageFormatterInterface::DEFAULT_FORMAT, $this->message); + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/Configuration/DTO/OrdersConfiguration.php b/modules/inpostizi/src/Configuration/DTO/OrdersConfiguration.php new file mode 100644 index 00000000..2bb11ce7 --- /dev/null +++ b/modules/inpostizi/src/Configuration/DTO/OrdersConfiguration.php @@ -0,0 +1,259 @@ +> + * + * @Assert\All( + * @Assert\All( + * @Assert\Type("string"), + * ) + * ) + */ + private $statusDescriptionMap; + + /** + * @var string|null + * + * @Assert\NotBlank() + */ + private $posId; + + /** + * @var PaymentType[] + * + * @Assert\All( + * @Assert\Type(PaymentType::class), + * ) + */ + private $availablePaymentOptions; + + /** + * @var bool|null + * + * @Assert\NotNull() + */ + private $allPaymentOptionsEnabled = true; + + /** + * @var MessageOptions|null + * + * @Assert\Valid() + */ + private $messageOptions; + + /** + * @param array> $statusDescriptionMap + * @param PaymentType[] $availablePaymentOptions + */ + public function __construct(?int $initialStatusId = null, ?int $paidStatusId = null, ?string $posId = null, array $statusDescriptionMap = [], array $availablePaymentOptions = []) + { + $this->defaultInitialStatusId = $initialStatusId; + $this->paidStatusId = $paidStatusId; + $this->posId = $posId; + $this->statusDescriptionMap = $statusDescriptionMap; + $this->availablePaymentOptions = $availablePaymentOptions; + } + + public function getInitialStatusId(?PaymentType $paymentType = null, ?int $shopId = null): ?int + { + if (PaymentType::CashOnDelivery() === $paymentType) { + return $this->cashOnDeliveryStatusId; + } + + return $this->defaultInitialStatusId; + } + + public function getDefaultInitialStatusId(): ?int + { + return $this->defaultInitialStatusId; + } + + public function setDefaultInitialStatusId(?\OrderState $initialStatus): self + { + $this->defaultInitialStatusId = null === $initialStatus ? null : (int) $initialStatus->id; + + return $this; + } + + public function getCashOnDeliveryStatusId(): ?int + { + return $this->cashOnDeliveryStatusId; + } + + public function setCashOnDeliveryStatusId(?\OrderState $codStatus): self + { + $this->cashOnDeliveryStatusId = null === $codStatus ? null : (int) $codStatus->id; + + return $this; + } + + public function getPaidStatusId(?int $shopId = null): ?int + { + return $this->paidStatusId; + } + + public function setPaidStatusId(?\OrderState $paidStatus): self + { + $this->paidStatusId = null === $paidStatus ? null : (int) $paidStatus->id; + + return $this; + } + + public function getStatusDescription(int $statusId, int $languageId, ?int $shopId = null): ?string + { + return $this->statusDescriptionMap[$languageId][$statusId] ?? null; + } + + public function getStatusDescriptionMap(): array + { + return $this->statusDescriptionMap; + } + + public function setStatusDescriptionMap(array $statusDescriptionMap): self + { + $this->statusDescriptionMap = $statusDescriptionMap; + + return $this; + } + + public function getAvailablePaymentOptions(?int $shopId = null): array + { + return $this->availablePaymentOptions; + } + + /** + * @param PaymentType[] $availablePaymentOptions + */ + public function setAvailablePaymentOptions(array $availablePaymentOptions): self + { + $this->availablePaymentOptions = $availablePaymentOptions; + + return $this; + } + + public function isAllPaymentOptionsEnabled(): ?bool + { + return $this->allPaymentOptionsEnabled; + } + + public function setAllPaymentOptionsEnabled(?bool $enabled): self + { + $this->allPaymentOptionsEnabled = $enabled; + + return $this; + } + + /** + * @deprecated use {@see getAvailablePaymentOptions()} instead + */ + public function isCarrierPaymentEnabled(): bool + { + @trigger_error(sprintf('Method "%s()" is deprecated. Use "getAvailablePaymentOptions()" instead.', __METHOD__), \E_USER_DEPRECATED); + + return [] !== array_uintersect($this->availablePaymentOptions, PaymentType::getCarrierProvidedPaymentOptions(), [Enum::class, 'compareValues']); + } + + /** + * @deprecated use {@see setAvailablePaymentOptions()} instead + */ + public function setCarrierPaymentEnabled(?bool $carrierPaymentEnabled): self + { + @trigger_error(sprintf('Method "%s()" is deprecated. Use "setAvailablePaymentOptions()" instead.', __METHOD__), \E_USER_DEPRECATED); + + $paymentOptions = PaymentType::getCarrierProvidedPaymentOptions(); + + if ($carrierPaymentEnabled) { + $this->availablePaymentOptions = array_unique(array_merge($this->availablePaymentOptions, $paymentOptions), SORT_REGULAR); + } else { + $this->availablePaymentOptions = array_filter($this->availablePaymentOptions, static function (PaymentType $type) use ($paymentOptions): bool { + return !in_array($type, $paymentOptions, true); + }); + } + + return $this; + } + + public function getPointOfSaleId(?int $shopId = null): ?string + { + return $this->posId; + } + + public function setPointOfSaleId(?string $posId): self + { + $this->posId = $posId; + + return $this; + } + + /** + * @internal + */ + public function getMessageOptions(): MessageOptions + { + return $this->messageOptions ?? ($this->messageOptions = new MessageOptions()); + } + + /** + * @internal + */ + public function setMessageOptions(MessageOptions $options): self + { + $this->messageOptions = $options; + + return $this; + } + + public function getMessageFormat(?int $shopId = null): string + { + return $this->getMessageOptions()->getFormat(); + } + + /** + * @Assert\IsTrue(message="At least one payment option must be enabled.") + * + * @internal + */ + public function hasEnabledPaymentOptions(): bool + { + return $this->allPaymentOptionsEnabled || [] !== $this->availablePaymentOptions; + } +} diff --git a/modules/inpostizi/src/Configuration/DTO/Product/ProductRestrictions.php b/modules/inpostizi/src/Configuration/DTO/Product/ProductRestrictions.php new file mode 100644 index 00000000..3d7998c3 --- /dev/null +++ b/modules/inpostizi/src/Configuration/DTO/Product/ProductRestrictions.php @@ -0,0 +1,160 @@ +productTypes; + } + + /** + * @param ProductType[] $productTypes + * + * @return $this + */ + public function setProductTypes(array $productTypes): self + { + $this->productTypes = $productTypes; + + return $this; + } + + /** + * @return int[] + */ + public function getCategoryIds(): array + { + return $this->categoryIds; + } + + /** + * @param int[] $categoryIds + * + * @return $this + */ + public function setCategoryIds(array $categoryIds): self + { + $this->categoryIds = $categoryIds; + + return $this; + } + + /** + * @return int[] + */ + public function getManufacturerIds(): array + { + return $this->manufacturerIds; + } + + /** + * @param int[] $manufacturerIds + * + * @return $this + */ + public function setManufacturerIds(array $manufacturerIds): self + { + $this->manufacturerIds = $manufacturerIds; + + return $this; + } + + /** + * @return int[] + */ + public function getAttributeGroupIds(): array + { + return $this->attributeGroupIds; + } + + /** + * @param int[] $attributeGroupIds + * + * @return $this + */ + public function setAttributeGroupIds(array $attributeGroupIds): self + { + $this->attributeGroupIds = $attributeGroupIds; + + return $this; + } + + /** + * @return int[] + */ + public function getFeatureIds(): array + { + return $this->featureIds; + } + + /** + * @param int[] $featureIds + * + * @return $this + */ + public function setFeatureIds(array $featureIds): self + { + $this->featureIds = $featureIds; + + return $this; + } + + public function isBlockOrder(): bool + { + return $this->blockOrder; + } + + /** + * @return $this + */ + public function setBlockOrder(bool $blockOrder): self + { + $this->blockOrder = $blockOrder; + + return $this; + } +} diff --git a/modules/inpostizi/src/Configuration/DTO/Product/ProductRestrictionsCache.php b/modules/inpostizi/src/Configuration/DTO/Product/ProductRestrictionsCache.php new file mode 100644 index 00000000..6faee039 --- /dev/null +++ b/modules/inpostizi/src/Configuration/DTO/Product/ProductRestrictionsCache.php @@ -0,0 +1,113 @@ +setProductTypes($restrictions->getProductTypes()) + ->setHasCategoryRestrictions([] !== $restrictions->getCategoryIds()) + ->setHasManufacturerRestrictions([] !== $restrictions->getManufacturerIds()) + ->setHasAttributeGroupRestrictions([] !== $restrictions->getAttributeGroupIds()) + ->setHasFeatureRestrictions([] !== $restrictions->getFeatureIds()); + } + + public function getProductTypes(): array + { + return $this->productTypes; + } + + /** + * @param ProductType[] $productTypes + */ + public function setProductTypes(array $productTypes): self + { + $this->productTypes = $productTypes; + + return $this; + } + + public function hasCategoryRestrictions(): bool + { + return $this->hasCategoryRestrictions; + } + + public function setHasCategoryRestrictions(bool $hasCategoryRestrictions): self + { + $this->hasCategoryRestrictions = $hasCategoryRestrictions; + + return $this; + } + + public function hasManufacturerRestrictions(): bool + { + return $this->hasManufacturerRestrictions; + } + + public function setHasManufacturerRestrictions(bool $hasManufacturerRestrictions): self + { + $this->hasManufacturerRestrictions = $hasManufacturerRestrictions; + + return $this; + } + + public function hasAttributeGroupRestrictions(): bool + { + return $this->hasAttributeGroupRestrictions; + } + + public function setHasAttributeGroupRestrictions(bool $hasAttributeGroupRestrictions): self + { + $this->hasAttributeGroupRestrictions = $hasAttributeGroupRestrictions; + + return $this; + } + + public function hasFeatureRestrictions(): bool + { + return $this->hasFeatureRestrictions; + } + + public function setHasFeatureRestrictions(bool $hasFeatureRestrictions): ProductRestrictionsCache + { + $this->hasFeatureRestrictions = $hasFeatureRestrictions; + + return $this; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/Configuration/DTO/ProductConfiguration.php b/modules/inpostizi/src/Configuration/DTO/ProductConfiguration.php new file mode 100644 index 00000000..8336a522 --- /dev/null +++ b/modules/inpostizi/src/Configuration/DTO/ProductConfiguration.php @@ -0,0 +1,72 @@ +normalImageTypeId = $normalImageTypeId; + $this->smallImageTypeId = $smallImageTypeId; + $this->largeImageTypeId = $largeImageTypeId; + } + + public function getNormalImageTypeId(?int $shopId = null): ?int + { + return $this->normalImageTypeId; + } + + public function setNormalImageTypeId(?\ImageType $imageType): void + { + $this->normalImageTypeId = null === $imageType ? null : (int) $imageType->id; + } + + public function getSmallImageTypeId(?int $shopId = null): ?int + { + return $this->smallImageTypeId; + } + + public function setSmallImageTypeId(?\ImageType $imageType): void + { + $this->smallImageTypeId = null === $imageType ? null : (int) $imageType->id; + } + + public function getLargeImageTypeId(?int $shopId = null): ?int + { + return $this->largeImageTypeId; + } + + public function setLargeImageTypeId(?\ImageType $imageType): void + { + $this->largeImageTypeId = null === $imageType ? null : (int) $imageType->id; + } +} diff --git a/modules/inpostizi/src/Configuration/DTO/ProductPageDisplayConfiguration.php b/modules/inpostizi/src/Configuration/DTO/ProductPageDisplayConfiguration.php new file mode 100644 index 00000000..21744352 --- /dev/null +++ b/modules/inpostizi/src/Configuration/DTO/ProductPageDisplayConfiguration.php @@ -0,0 +1,70 @@ +displayConfiguration = $configuration; + $this->productRestrictions = $restrictions; + } + + public function isDisplayed(): bool + { + return $this->displayConfiguration->isDisplayed(); + } + + public function getWidgetConfiguration(): WidgetConfigurationInterface + { + return $this->displayConfiguration->getWidgetConfiguration(); + } + + public function getHtmlStyles(): iterable + { + return $this->displayConfiguration->getHtmlStyles(); + } + + public function getProductRestrictions(): ?ProductRestrictions + { + return $this->productRestrictions; + } + + /** + * @return $this + */ + public function setProductRestrictions(?ProductRestrictions $restrictions): self + { + $this->productRestrictions = $restrictions; + + return $this; + } + + public function getDisplayConfiguration(): WidgetDisplayConfiguration + { + return $this->displayConfiguration; + } +} diff --git a/modules/inpostizi/src/Configuration/DTO/Shipping/CarrierMapping.php b/modules/inpostizi/src/Configuration/DTO/Shipping/CarrierMapping.php new file mode 100644 index 00000000..7881cd87 --- /dev/null +++ b/modules/inpostizi/src/Configuration/DTO/Shipping/CarrierMapping.php @@ -0,0 +1,59 @@ +referenceId = $referenceId; + $this->serviceCodes = $serviceCodes; + } + + public function getReferenceId(): ?int + { + return $this->referenceId; + } + + public function setReferenceId(?\Carrier $carrier): self + { + $this->referenceId = null === $carrier ? null : (int) $carrier->id_reference; + + return $this; + } + + /** + * @return ServiceCode[] + */ + public function getServiceCodes(): array + { + return $this->serviceCodes; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/Configuration/DTO/Shipping/ServiceOptions.php b/modules/inpostizi/src/Configuration/DTO/Shipping/ServiceOptions.php new file mode 100644 index 00000000..97ad78e1 --- /dev/null +++ b/modules/inpostizi/src/Configuration/DTO/Shipping/ServiceOptions.php @@ -0,0 +1,74 @@ +serviceCode = $serviceCode; + $this->additionalCost = $additionalCost; + $this->availabilityRange = $availabilityRange; + } + + public function getServiceCode(): ServiceCode + { + return $this->serviceCode; + } + + public function getAdditionalCost(): ?float + { + return $this->additionalCost; + } + + public function setAdditionalCost(?float $additionalCost): ServiceOptions + { + $this->additionalCost = $additionalCost; + + return $this; + } + + public function getAvailabilityRange(): ?TimeOfWeekRange + { + return $this->availabilityRange; + } + + public function setAvailabilityRange(?TimeOfWeekRange $availabilityRange): ServiceOptions + { + $this->availabilityRange = $availabilityRange; + + return $this; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } + + public function __clone() + { + $this->availabilityRange = null === $this->availabilityRange ? null : clone $this->availabilityRange; + } +} diff --git a/modules/inpostizi/src/Configuration/DTO/Shipping/ShippingOptions.php b/modules/inpostizi/src/Configuration/DTO/Shipping/ShippingOptions.php new file mode 100644 index 00000000..419009c5 --- /dev/null +++ b/modules/inpostizi/src/Configuration/DTO/Shipping/ShippingOptions.php @@ -0,0 +1,122 @@ +carrierMappings = $carrierMappings; + $this->optionalServices = $optionalServices; + } + + /** + * @return CarrierMapping[] + */ + public function getCarrierMappings(): array + { + return $this->carrierMappings; + } + + public function getCarrierMapping(ServiceCode ...$serviceCodes): CarrierMapping + { + foreach ($this->carrierMappings as $carrierMapping) { + $mappingServiceCodes = $carrierMapping->getServiceCodes(); + + $diff = array_udiff($serviceCodes, $mappingServiceCodes, [Enum::class, 'compareValues']); + + if ([] === $diff && count($mappingServiceCodes) === count($serviceCodes)) { + return $carrierMapping; + } + } + + return new CarrierMapping(null, $serviceCodes); + } + + /** + * @param CarrierMapping[] $carrierMappings + */ + public function setCarrierMappings(array $carrierMappings): self + { + $this->carrierMappings = $carrierMappings; + + return $this; + } + + /** + * @return ServiceOptions[] + */ + public function getOptionalServices(): array + { + return $this->optionalServices; + } + + public function getServiceOptions(ServiceCode $serviceCode): ?ServiceOptions + { + foreach ($this->optionalServices as $serviceOptions) { + if ($serviceCode === $serviceOptions->getServiceCode()) { + return $serviceOptions; + } + } + + return null; + } + + /** + * @param ServiceOptions[] $optionalServices + */ + public function setOptionalServices(array $optionalServices): self + { + $this->optionalServices = $optionalServices; + + return $this; + } + + public function __clone() + { + $this->carrierMappings = array_map(static function (CarrierMapping $mapping) { + return clone $mapping; + }, $this->carrierMappings); + + $this->optionalServices = array_map(static function (ServiceOptions $options) { + return clone $options; + }, $this->optionalServices); + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/Configuration/DTO/Shipping/TimeOfWeek.php b/modules/inpostizi/src/Configuration/DTO/Shipping/TimeOfWeek.php new file mode 100644 index 00000000..88bf3c3b --- /dev/null +++ b/modules/inpostizi/src/Configuration/DTO/Shipping/TimeOfWeek.php @@ -0,0 +1,75 @@ +weekDay = $weekDay ?? WeekDay::Monday(); + $this->time = $time ?? new \DateTimeImmutable('00:00'); + } + + public static function fromDateTime(\DateTimeInterface $dateTime): self + { + return new self( + WeekDay::fromDateTime($dateTime), + \DateTimeImmutable::createFromFormat(self::TIME_FORMAT, $dateTime->format(self::TIME_FORMAT)) + ); + } + + public function getWeekDay(): WeekDay + { + return $this->weekDay; + } + + public function setWeekDay(WeekDay $weekDay): self + { + $this->weekDay = $weekDay; + + return $this; + } + + public function getTime(): \DateTimeImmutable + { + return $this->time; + } + + public function setTime(\DateTimeImmutable $time): self + { + $this->time = $time; + + return $this; + } + + public function jsonSerialize(): array + { + return [ + 'weekDay' => $this->weekDay, + 'time' => $this->time->format(self::TIME_FORMAT), + ]; + } + + public function compare(self $timeOfWeek): int + { + if ($this->weekDay !== $timeOfWeek->weekDay) { + return $this->weekDay->value <=> $timeOfWeek->weekDay->value; + } + + return $this->time->format(self::TIME_FORMAT) <=> $timeOfWeek->time->format(self::TIME_FORMAT); + } +} diff --git a/modules/inpostizi/src/Configuration/DTO/Shipping/TimeOfWeekRange.php b/modules/inpostizi/src/Configuration/DTO/Shipping/TimeOfWeekRange.php new file mode 100644 index 00000000..048e9039 --- /dev/null +++ b/modules/inpostizi/src/Configuration/DTO/Shipping/TimeOfWeekRange.php @@ -0,0 +1,68 @@ +start = $start ?? new TimeOfWeek(); + $this->end = $end ?? new TimeOfWeek(); + } + + public function getStart(): TimeOfWeek + { + return $this->start; + } + + public function setStart(TimeOfWeek $start): TimeOfWeekRange + { + $this->start = $start; + + return $this; + } + + public function getEnd(): TimeOfWeek + { + return $this->end; + } + + public function setEnd(TimeOfWeek $end): TimeOfWeekRange + { + $this->end = $end; + + return $this; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } + + public function contains(\DateTimeInterface $dateTime): bool + { + $timeOfWeek = TimeOfWeek::fromDateTime($dateTime); + + return 0 > $this->start->compare($this->end) + ? 0 >= $this->start->compare($timeOfWeek) && 0 < $this->end->compare($timeOfWeek) + : 0 <= $this->start->compare($timeOfWeek) || 0 > $this->end->compare($timeOfWeek); + } + + public function __clone() + { + $this->start = clone $this->start; + $this->end = clone $this->end; + } +} diff --git a/modules/inpostizi/src/Configuration/DTO/Shipping/WeekDay.php b/modules/inpostizi/src/Configuration/DTO/Shipping/WeekDay.php new file mode 100644 index 00000000..666ac7b2 --- /dev/null +++ b/modules/inpostizi/src/Configuration/DTO/Shipping/WeekDay.php @@ -0,0 +1,32 @@ +format('N')); + } +} diff --git a/modules/inpostizi/src/Configuration/DTO/ShippingConfiguration.php b/modules/inpostizi/src/Configuration/DTO/ShippingConfiguration.php new file mode 100644 index 00000000..4fb93bf4 --- /dev/null +++ b/modules/inpostizi/src/Configuration/DTO/ShippingConfiguration.php @@ -0,0 +1,69 @@ +apmShippingOptions = $apmOptions; + $this->courierShippingOptions = $courierOptions; + } + + public function getShippingOptions(DeliveryType $deliveryType, ?int $shopId = null): ShippingOptions + { + switch ($deliveryType) { + case DeliveryType::Courier(): + return $this->courierShippingOptions; + case DeliveryType::Apm(): + return $this->apmShippingOptions; + default: + throw new \LogicException('Not implemented.'); + } + } + + public function getApmShippingOptions(?int $shopId = null): ShippingOptions + { + return $this->apmShippingOptions; + } + + public function setApmShippingOptions(ShippingOptions $shipping): self + { + $this->apmShippingOptions = $shipping; + + return $this; + } + + public function getCourierShippingOptions(?int $shopId = null): ShippingOptions + { + return $this->courierShippingOptions; + } + + public function setCourierShippingOptions(ShippingOptions $courierShippingOptions): self + { + $this->courierShippingOptions = $courierShippingOptions; + + return $this; + } +} diff --git a/modules/inpostizi/src/Configuration/DTO/WidgetDisplayConfiguration.php b/modules/inpostizi/src/Configuration/DTO/WidgetDisplayConfiguration.php new file mode 100644 index 00000000..1a9d6756 --- /dev/null +++ b/modules/inpostizi/src/Configuration/DTO/WidgetDisplayConfiguration.php @@ -0,0 +1,107 @@ +bindingPlace = $widgetConfiguration->getBindingPlace(); + $this->displayed = $displayed; + $this->widgetConfiguration = $widgetConfiguration; + $this->htmlStyles = $htmlStyles; + } + + public static function for(BindingPlace $bindingPlace): self + { + $widgetConfiguration = new WidgetConfiguration($bindingPlace); + + return new self($widgetConfiguration); + } + + public function getWidgetConfiguration(): WidgetConfigurationInterface + { + return $this->widgetConfiguration ?? new WidgetConfiguration($this->bindingPlace); + } + + public function setWidgetConfiguration(?WidgetConfigurationInterface $widgetConfiguration): self + { + $this->widgetConfiguration = $widgetConfiguration; + + return $this; + } + + public function isDisplayed(): bool + { + return true === $this->displayed; + } + + public function getDisplayed(): ?bool + { + return $this->displayed; + } + + public function setDisplayed(?bool $displayed): self + { + $this->displayed = $displayed; + + return $this; + } + + public function getHtmlStyles(): HtmlStyles + { + return $this->htmlStyles ?? new HtmlStyles(); + } + + public function setHtmlStyles(?HtmlStyles $htmlStyles): self + { + $this->htmlStyles = $htmlStyles; + + return $this; + } + + public function __clone() + { + if (null !== $this->widgetConfiguration) { + $this->widgetConfiguration = clone $this->widgetConfiguration; + } + + if (null !== $this->htmlStyles) { + $this->htmlStyles = clone $this->htmlStyles; + } + } +} diff --git a/modules/inpostizi/src/Configuration/GeneralConfiguration.php b/modules/inpostizi/src/Configuration/GeneralConfiguration.php new file mode 100644 index 00000000..54c8c82e --- /dev/null +++ b/modules/inpostizi/src/Configuration/GeneralConfiguration.php @@ -0,0 +1,127 @@ + + */ +final class GeneralConfiguration implements GeneralConfigurationInterface, PromoCodesConfigurationInterface, PersistentConfigurationInterface +{ + private const ENABLE_FOR_EVERYONE = 'INPOST_PAY_show_izi'; + private const MAX_SUGGESTED_PRODUCTS = 'INPOST_PAY_related_count'; + private const THANK_YOU_DISPLAY_HOOK = 'INPOST_PAY_THANK_YOU_DISPLAY'; + private const PRODUCT_CARD_DISPLAY_HOOK = 'INPOST_PAY_PRODUCT_CARD_DISPLAY_HOOK'; + private const CHECKOUT_BUTTON_DISPLAY_HOOK = 'INPOST_PAY_CHECKOUT_DISPLAY_HOOK'; + private const FULL_PAGE_CACHE_MODULE_IN_USE = 'INPOST_PAY_FULL_PAGE_CACHE_MODULE_IN_USE'; + private const SEND_ANALYTICS_DATA = 'INPOST_PAY_SEND_ANALYTICS_DATA'; + private const DEFAULT_PROMOTION_DETAILS_PAGE_ID = 'INPOST_PAY_DEFAULT_PROMO_DETAILS_CMS_ID'; + + /** + * @var ShopAwareConfigurationInterface + */ + private $configuration; + + public function __construct(ShopAwareConfigurationInterface $configuration) + { + $this->configuration = $configuration; + } + + public function isEnabledForEveryone(): bool + { + return (bool) $this->configuration->get(self::ENABLE_FOR_EVERYONE); + } + + public function getMaxSuggestedProducts(?int $shopId = null): ?int + { + $value = $this->configuration->get(self::MAX_SUGGESTED_PRODUCTS, $shopId); + + return null === $value ? $value : (int) $value; + } + + public function getThankYouDisplayHook(?int $shopId = null): ?string + { + return $this->configuration->get(self::THANK_YOU_DISPLAY_HOOK, $shopId); + } + + public function getProductCardDisplayHook(?int $shopId = null): ?string + { + $hook = $this->configuration->get(self::PRODUCT_CARD_DISPLAY_HOOK, $shopId); + + if (null === $hook) { + if (!DisplayProductActions::getVersionRange()->contains(_PS_VERSION_)) { + $hook = DisplayProductAdditionalInfo::HOOK_NAME; + } else { + $hook = DisplayProductActions::HOOK_NAME; + } + } + + return $hook; + } + + public function getCheckoutButtonDisplayHook(?int $shopId = null): ?string + { + $hook = $this->configuration->get(self::CHECKOUT_BUTTON_DISPLAY_HOOK, $shopId); + + if (null === $hook) { + $hook = DisplayCheckoutSummaryTop::HOOK_NAME; + } + + return $hook; + } + + public function isFullPageCacheModuleInUse(?int $shopId = null): bool + { + return (bool) $this->configuration->get(self::FULL_PAGE_CACHE_MODULE_IN_USE, $shopId); + } + + public function isSendAnalyticsData(?int $shopId = null): bool + { + return (bool) $this->configuration->get(self::SEND_ANALYTICS_DATA, $shopId); + } + + public function getDefaultPromoDetailsPageId(?int $shopId = null): ?int + { + $value = $this->configuration->get(self::DEFAULT_PROMOTION_DETAILS_PAGE_ID, $shopId); + + return null === $value ? null : (int) $value; + } + + /** + * @return GeneralConfigurationInterface&PromoCodesConfigurationInterface + */ + public function copy(): GeneralConfigurationInterface + { + $configuration = new DTO\GeneralConfiguration( + $this->isEnabledForEveryone(), + $this->getMaxSuggestedProducts(), + $this->getThankYouDisplayHook(), + $this->getProductCardDisplayHook(), + $this->getCheckoutButtonDisplayHook(), + $this->isFullPageCacheModuleInUse(), + $this->isSendAnalyticsData() + ); + + return $configuration->setDefaultPromoDetailsPageId($this->getDefaultPromoDetailsPageId()); + } + + public function persist(GeneralConfigurationInterface $configuration): void + { + $this->configuration->set(self::ENABLE_FOR_EVERYONE, $configuration->isEnabledForEveryone()); + $this->configuration->set(self::MAX_SUGGESTED_PRODUCTS, $configuration->getMaxSuggestedProducts()); + $this->configuration->set(self::THANK_YOU_DISPLAY_HOOK, $configuration->getThankYouDisplayHook()); + $this->configuration->set(self::PRODUCT_CARD_DISPLAY_HOOK, $configuration->getProductCardDisplayHook()); + $this->configuration->set(self::CHECKOUT_BUTTON_DISPLAY_HOOK, $configuration->getCheckoutButtonDisplayHook()); + $this->configuration->set(self::FULL_PAGE_CACHE_MODULE_IN_USE, $configuration->isFullPageCacheModuleInUse()); + $this->configuration->set(self::SEND_ANALYTICS_DATA, $configuration->isSendAnalyticsData()); + + if ($configuration instanceof PromoCodesConfigurationInterface) { + $this->configuration->set(self::DEFAULT_PROMOTION_DETAILS_PAGE_ID, $configuration->getDefaultPromoDetailsPageId()); + } + } +} diff --git a/modules/inpostizi/src/Configuration/GeneralConfigurationInterface.php b/modules/inpostizi/src/Configuration/GeneralConfigurationInterface.php new file mode 100644 index 00000000..93e86a2a --- /dev/null +++ b/modules/inpostizi/src/Configuration/GeneralConfigurationInterface.php @@ -0,0 +1,22 @@ + + */ +final class GuiConfiguration implements GuiConfigurationInterface, PersistentConfigurationInterface, ServiceSubscriberInterface, ProductRestrictionsConfigurationInterface +{ + use SafeDeserializerTrait; + + private const BASKET_SUMMARY_WIDGET_DISPLAY = 'INPOST_PAY_show_button_cart'; + private const BASKET_SUMMARY_WIDGET_CONFIG = 'INPOST_PAY_CART_WIDGET_CONFIG'; + private const BASKET_SUMMARY_HTML_STYLES = 'INPOST_PAY_CART_HTML_STYLES'; + + private const PRODUCT_CARD_WIDGET_DISPLAY = 'INPOST_PAY_show_button_details'; + private const PRODUCT_CARD_WIDGET_CONFIG = 'INPOST_PAY_PRODUCT_CARD_WIDGET_CONFIG'; + private const PRODUCT_CARD_HTML_STYLES = 'INPOST_PAY_PRODUCT_HTML_STYLES'; + private const PRODUCT_PAGE_RESTRICTIONS = 'INPOST_PAY_PRODUCT_PAGE_RESTRICTIONS'; + private const DISALLOW_ORDERING_RESTRICTED_PRODUCTS = 'INPOST_PAY_DISALLOW_ORDERING_RESTRICTED_PRODUCTS'; + + private const LOGIN_PAGE_WIDGET_DISPLAY = 'INPOST_PAY_SHOW_LOGIN_PAGE_WIDGET'; + private const LOGIN_PAGE_WIDGET_CONFIG = 'INPOST_PAY_LOGIN_PAGE_WIDGET_CONFIG'; + private const LOGIN_PAGE_HTML_STYLES = 'INPOST_PAY_LOGIN_PAGE_HTML_STYLES'; + + private const REGISTERFORM_PAGE_WIDGET_DISPLAY = 'INPOST_PAY_SHOW_REGISTERFORM_PAGE_WIDGET'; + private const REGISTERFORM_PAGE_WIDGET_CONFIG = 'INPOST_PAY_REGISTERFORM_PAGE_WIDGET_CONFIG'; + private const REGISTERFORM_PAGE_HTML_STYLES = 'INPOST_PAY_REGISTERFORM_PAGE_HTML_STYLES'; + + private const CHECKOUT_PAGE_WIDGET_DISPLAY = 'INPOST_PAY_SHOW_CHECKOUT_PAGE_WIDGET'; + private const CHECKOUT_PAGE_WIDGET_CONFIG = 'INPOST_PAY_CHECKOUT_PAGE_WIDGET_CONFIG'; + private const CHECKOUT_PAGE_HTML_STYLES = 'INPOST_PAY_CHECKOUT_PAGE_HTML_STYLES'; + + private const MINICART_PAGE_WIDGET_DISPLAY = 'INPOST_PAY_SHOW_MINICART_PAGE_WIDGET'; + private const MINICART_PAGE_WIDGET_CONFIG = 'INPOST_PAY_MINICART_PAGE_WIDGET_CONFIG'; + private const MINICART_PAGE_HTML_STYLES = 'INPOST_PAY_MINICART_PAGE_HTML_STYLES'; + + /** + * @var ConfigurationInterface + */ + private $configuration; + + /** + * @var ContainerInterface + */ + private $container; + + /** + * @var array + */ + private $loadedConfiguration = []; + + /** + * @var array + */ + private $productRestrictions = []; + + /** + * @var BindingPlace[] + */ + private static $supportedBindingPlaces; + + public function __construct(ConfigurationInterface $configuration, SerializerInterface $serializer, ContainerInterface $container) + { + $this->configuration = $configuration; + $this->serializer = $serializer; + $this->container = $container; + } + + public static function getSubscribedServices(): array + { + return [ + ProductRestrictionsRepositoryInterface::class, + 'context' => Context::class, + 'validator' => '?' . ValidatorInterface::class, + ]; + } + + public static function getSupportedBindingPlaces(): array + { + return self::$supportedBindingPlaces ?? self::$supportedBindingPlaces = [ + BindingPlace::BasketSummary(), + BindingPlace::ProductCard(), + BindingPlace::LoginPage(), + BindingPlace::RegisterFormPage(), + BindingPlace::CheckoutPage(), + BindingPlace::MiniCartPage(), + BindingPlace::OrderCreate(), + ]; + } + + public static function getConfigurableBindingPlaces(): array + { + return array_slice(self::getSupportedBindingPlaces(), 0, -1); // all supported except "ORDER_CREATE" + } + + /** + * @return WidgetDisplayConfigurationInterface + */ + public function getDisplayConfiguration(BindingPlace $bindingPlace): WidgetDisplayConfigurationInterface + { + if (!$bindingPlace->canDisplayBindingWidget()) { + throw new \LogicException(sprintf('Binding widget cannot be displayed in "%s".', $bindingPlace->value)); + } + + if (!in_array($bindingPlace, self::getSupportedBindingPlaces(), true)) { + throw new \DomainException(sprintf('Unsupported binding place: "%s".', $bindingPlace->value)); + } + + if (BindingPlace::OrderCreate() === $bindingPlace) { + $configuration = clone $this->getDisplayConfigurationByBindingPlace(BindingPlace::BasketSummary()); + $configuration->setWidgetConfiguration( + $configuration + ->getWidgetConfiguration() + ->withBindingPlace(BindingPlace::OrderCreate()) + ); + + return $configuration; + } + + if (BindingPlace::ProductCard() !== $bindingPlace || !$this->container->has('validator')) { + return clone $this->getDisplayConfigurationByBindingPlace($bindingPlace); + } + + return new ProductPageDisplayConfiguration( + clone $this->getDisplayConfigurationByBindingPlace($bindingPlace), + $this->container->get('validator'), + $this->getProductValidationConstraints() + ); + } + + /** + * {@inheritDoc} + */ + public function getProductRestrictionConstraints(?int $shopId = null): array + { + if (!$this->getDisallowOrderingRestrictedProducts($shopId)) { + return []; + } + + return $this->getProductValidationConstraints($shopId); + } + + public function copy(): GuiConfigurationInterface + { + $displayConfigurations = []; + + foreach (self::getConfigurableBindingPlaces() as $bindingPlace) { + $displayConfiguration = clone $this->getDisplayConfigurationByBindingPlace($bindingPlace); + + if (BindingPlace::ProductCard() === $bindingPlace) { + $displayConfiguration = new DTO\ProductPageDisplayConfiguration( + $displayConfiguration, + $this->getProductRestrictions() + ); + } + + $displayConfigurations[] = $displayConfiguration; + } + + return new DTO\GuiConfiguration($displayConfigurations); + } + + public function persist(GuiConfigurationInterface $configuration): void + { + $configurablePlaces = self::getConfigurableBindingPlaces(); + $productRestrictions = null; + + foreach ($configuration->getSupportedBindingPlaces() as $bindingPlace) { + if (!in_array($bindingPlace, $configurablePlaces, true)) { + continue; + } + + $displayConfiguration = $configuration->getDisplayConfiguration($bindingPlace); + + if ($displayConfiguration instanceof ProductRestrictionsProviderInterface && BindingPlace::ProductCard() === $bindingPlace) { + $productRestrictions = $displayConfiguration->getProductRestrictions() ?? new ProductRestrictions(); + } + + $this->setWidgetDisplayConfiguration($displayConfiguration); + } + + if (null === $productRestrictions) { + return; + } + + $this->updateProductRestrictions($productRestrictions); + } + + private function setWidgetDisplayConfiguration(WidgetDisplayConfigurationInterface $widgetDisplayConfig): void + { + $bindingPlace = $widgetDisplayConfig->getWidgetConfiguration()->getBindingPlace(); + + $this->configuration->set($this->getDisplayWidgetConfigKey($bindingPlace), $widgetDisplayConfig->isDisplayed()); + $this->setHtmlStyles($widgetDisplayConfig->getHtmlStyles(), $bindingPlace); + $this->setWidgetConfiguration($widgetDisplayConfig->getWidgetConfiguration(), $bindingPlace); + } + + private function setHtmlStyles(iterable $styles, BindingPlace $bindingPlace): void + { + $value = $this->serializer->serialize($styles, 'json'); + $this->configuration->set($this->getHtmlStyleConfigKey($bindingPlace), $value); + } + + private function setWidgetConfiguration(WidgetConfigurationInterface $config, BindingPlace $bindingPlace): void + { + $value = $this->serializer->serialize($config, 'json'); + $this->configuration->set($this->getConfigurationWidgetConfigKey($bindingPlace), $value); + } + + private function getDisplayConfigurationByBindingPlace(BindingPlace $bindingPlace): WidgetDisplayConfiguration + { + $key = $bindingPlace->value; + + if (!isset($this->loadedConfiguration[$key])) { + $this->loadedConfiguration[$key] = $this->loadDisplayConfiguration($bindingPlace); + } + + return $this->loadedConfiguration[$key]; + } + + private function loadDisplayConfiguration(BindingPlace $bindingPlace): WidgetDisplayConfiguration + { + if (null === $configuration = $this->loadWidgetConfiguration($bindingPlace)) { + return WidgetDisplayConfiguration::for($bindingPlace); + } + + $displayed = (bool) $this->configuration->get($this->getDisplayWidgetConfigKey($bindingPlace)); + $htmlStyles = $this->loadHtmlStyles($bindingPlace); + + return new WidgetDisplayConfiguration($configuration, $displayed, $htmlStyles); + } + + private function loadWidgetConfiguration(BindingPlace $bindingPlace): ?WidgetConfiguration + { + $value = $this->configuration->get($this->getConfigurationWidgetConfigKey($bindingPlace)); + + if (null !== $value && $config = $this->deserialize($value, WidgetConfiguration::class)) { + return $config; + } + + return null; + } + + private function loadHtmlStyles(BindingPlace $bindingPlace): ?HtmlStyles + { + $value = $this->configuration->get($this->getHtmlStyleConfigKey($bindingPlace)); + + if (null !== $value && $styles = $this->deserialize($value, HtmlStyles::class)) { + return $styles; + } + + return null; + } + + private function getHtmlStyleConfigKey(BindingPlace $bindingPlace): string + { + $constantName = $bindingPlace->value . '_HTML_STYLES'; + $classNamespace = self::class; + + return constant($classNamespace . '::' . $constantName); + } + + private function getDisplayWidgetConfigKey(BindingPlace $bindingPlace): string + { + $constantName = $bindingPlace->value . '_WIDGET_DISPLAY'; + $classNamespace = self::class; + + return constant($classNamespace . '::' . $constantName); + } + + private function getConfigurationWidgetConfigKey(BindingPlace $bindingPlace): string + { + $constantName = $bindingPlace->value . '_WIDGET_CONFIG'; + $classNamespace = self::class; + + return constant($classNamespace . '::' . $constantName); + } + + private function getDisallowOrderingRestrictedProducts(?int $shopId = null): bool + { + return (bool) $this->configuration->get(self::DISALLOW_ORDERING_RESTRICTED_PRODUCTS, $shopId); + } + + private function setDisallowOrderingRestrictedProducts(bool $disallow, ?int $shopId = null): void + { + $this->configuration->set(self::DISALLOW_ORDERING_RESTRICTED_PRODUCTS, $disallow, $shopId); + } + + private function getProductRestrictions(): ProductRestrictions + { + $shopId = $this->getContextShopId(); + $productTypes = $this + ->getCachedProductRestrictions($shopId) + ->getProductTypes(); + + return $this + ->getProductRestrictionsRepository() + ->getProductRestrictions($shopId) + ->setProductTypes($productTypes) + ->setBlockOrder($this->getDisallowOrderingRestrictedProducts($shopId)); + } + + private function updateProductRestrictions(ProductRestrictions $restrictions): void + { + $this->setDisallowOrderingRestrictedProducts($restrictions->isBlockOrder()); + + $repository = $this->getProductRestrictionsRepository(); + + $repository->updateProductRestrictions($restrictions, $shopId = $this->getContextShopId()); + $this->cacheProductRestrictions( + ProductRestrictionsCache::fromRestrictions($restrictions), + $shopId + ); + + if (null !== $shopId) { + return; + } + + foreach ($this->getContext()->getContextListShopID() as $shopId) { + $shopId = (int) $shopId; + $cache = $this + ->getCachedProductRestrictions($shopId) + ->setHasCategoryRestrictions($repository->hasCategoryRestrictions($shopId)) + ->setHasManufacturerRestrictions($repository->hasManufacturerRestrictions($shopId)) + ->setHasAttributeGroupRestrictions($repository->hasAttributeGroupRestrictions($shopId)); + + if (!$repository instanceof FeatureRestrictionsRepositoryInterface) { + @trigger_error(sprintf('Not implementing "%s" in "%s" is deprecated since version 2.2.0.', FeatureRestrictionsRepositoryInterface::class, get_class($repository)), E_USER_DEPRECATED); + + $cache->setHasFeatureRestrictions(false); + } else { + $cache->setHasFeatureRestrictions($repository->hasFeatureRestrictions($shopId)); + } + + $this->cacheProductRestrictions($cache, $shopId); + } + } + + private function getCachedProductRestrictions(?int $shopId): ProductRestrictionsCache + { + if (!isset($this->productRestrictions[(int) $shopId])) { + $this->productRestrictions[(int) $shopId] = $this->loadCachedProductRestrictions($shopId); + } + + return $this->productRestrictions[(int) $shopId]; + } + + private function loadCachedProductRestrictions(?int $shopId): ProductRestrictionsCache + { + $value = $this->configuration->get(self::PRODUCT_PAGE_RESTRICTIONS, $shopId); + + if (null !== $value && $restrictions = $this->deserialize($value, ProductRestrictionsCache::class)) { + return $restrictions; + } + + return new ProductRestrictionsCache(); + } + + private function cacheProductRestrictions(ProductRestrictionsCache $restrictions, ?int $shopId): void + { + $value = $this->serializer->serialize($restrictions, 'json'); + $this->configuration->set(self::PRODUCT_PAGE_RESTRICTIONS, $value, $shopId); + + $this->productRestrictions[(int) $shopId] = $restrictions; + } + + private function getContextShopId(): ?int + { + if (null === $shopId = $this->getContext()->getContextShopID()) { + return null; + } + + return (int) $shopId; + } + + private function getProductRestrictionsRepository(): ProductRestrictionsRepositoryInterface + { + return $this->container->get(ProductRestrictionsRepositoryInterface::class); + } + + private function getContext(): Context + { + return $this->container->get('context'); + } + + private function getProductValidationConstraints(?int $shopId = null): array + { + $shopId = $shopId ?? $this->getContextShopId(); + + return iterator_to_array($this->generateProductValidationConstraints($shopId)); + } + + private function generateProductValidationConstraints(?int $shopId): \Generator + { + $restrictions = $this->getCachedProductRestrictions($shopId); + + if ([] !== $types = $restrictions->getProductTypes()) { + yield new NotOfType(['types' => $types]); + } + + if ($restrictions->hasCategoryRestrictions()) { + yield new NotInRestrictedCategory(['shopId' => $shopId]); + } + + if ($restrictions->hasManufacturerRestrictions()) { + yield new NotFromRestrictedManufacturer(['shopId' => $shopId]); + } + + if ($restrictions->hasAttributeGroupRestrictions()) { + yield new NotWithRestrictedAttributes(['shopId' => $shopId]); + } + + if ($restrictions->hasFeatureRestrictions()) { + yield new NotWithRestrictedFeatures(['shopId' => $shopId]); + } + } +} diff --git a/modules/inpostizi/src/Configuration/GuiConfigurationInterface.php b/modules/inpostizi/src/Configuration/GuiConfigurationInterface.php new file mode 100644 index 00000000..148bfd9f --- /dev/null +++ b/modules/inpostizi/src/Configuration/GuiConfigurationInterface.php @@ -0,0 +1,17 @@ +ignoredNames = array_merge($ignoredNames, self::IGNORED_NAMES); + } + + public function init(): void + { + foreach ($this->ignoredNames as $name) { + AnnotationReader::addGlobalIgnoredName($name); + } + } +} diff --git a/modules/inpostizi/src/Configuration/Initializer/AssetPackageInitializer.php b/modules/inpostizi/src/Configuration/Initializer/AssetPackageInitializer.php new file mode 100644 index 00000000..806b7285 --- /dev/null +++ b/modules/inpostizi/src/Configuration/Initializer/AssetPackageInitializer.php @@ -0,0 +1,38 @@ +packages = $packages; + $this->assetManager = $assetManager; + $this->packageName = $packageName; + } + + public function init(): void + { + $this->packages->addPackage($this->packageName, $this->assetManager->getPackage()); + } +} diff --git a/modules/inpostizi/src/Configuration/Initializer/ConfigurationInitializerInterface.php b/modules/inpostizi/src/Configuration/Initializer/ConfigurationInitializerInterface.php new file mode 100644 index 00000000..fd2416b1 --- /dev/null +++ b/modules/inpostizi/src/Configuration/Initializer/ConfigurationInitializerInterface.php @@ -0,0 +1,10 @@ + + */ + private $extensions; + + /** + * @param iterable $extensions + */ + public function __construct(Environment $twig, iterable $extensions) + { + $this->extensions = $extensions; + $this->twig = $twig; + } + + public function init(): void + { + foreach ($this->extensions as $extension) { + $this->twig->addExtension($extension); + } + } +} diff --git a/modules/inpostizi/src/Configuration/LanguageAwareConfigurationInterface.php b/modules/inpostizi/src/Configuration/LanguageAwareConfigurationInterface.php new file mode 100644 index 00000000..2368b4e8 --- /dev/null +++ b/modules/inpostizi/src/Configuration/LanguageAwareConfigurationInterface.php @@ -0,0 +1,15 @@ + values by language ID + */ + public function getLocalized(string $key): array; +} diff --git a/modules/inpostizi/src/Configuration/OrdersConfiguration.php b/modules/inpostizi/src/Configuration/OrdersConfiguration.php new file mode 100644 index 00000000..5e39eebd --- /dev/null +++ b/modules/inpostizi/src/Configuration/OrdersConfiguration.php @@ -0,0 +1,284 @@ + + */ +final class OrdersConfiguration implements OrdersConfigurationInterface, PersistentConfigurationInterface +{ + use SafeDeserializerTrait; + + private const INITIAL_OS_ID = 'INPOST_PAY_INITIAL_OS_ID'; + private const COD_OS_ID = 'INPOST_PAY_COD_OS_ID'; + private const PAID_OS_ID = 'INPOST_PAY_authorized_payment'; + private const STATUS_DESCRIPTION_MAP = 'INPOST_PAY_OS_DESCRIPTION_MAP'; + private const ENABLE_ALL_PAYMENT_OPTIONS = 'INPOST_PAY_ENABLE_ALL_PAYMENT_OPTIONS'; + private const AVAILABLE_PAYMENT_OPTIONS = 'INPOST_PAY_AVAILABLE_PAYMENT_OPTIONS'; + private const POS_ID = 'INPOST_PAY_pos_id'; + private const MESSAGE_FORMAT = 'INPOST_PAY_ORDER_MESSAGE_FORMAT'; + + /** + * @var LanguageAwareConfigurationInterface + */ + private $configuration; + + private $descriptionMappings = []; + + private $availablePaymentOptions = []; + + /** + * @var array + */ + private $messageOptions = []; + + public function __construct(LanguageAwareConfigurationInterface $configuration, SerializerInterface $serializer) + { + $this->configuration = $configuration; + $this->serializer = $serializer; + } + + public function getInitialStatusId(?PaymentType $paymentType = null, ?int $shopId = null): int + { + if (PaymentType::CashOnDelivery() === $paymentType && null !== $codStatusId = $this->getCashOnDeliveryStatusId($shopId)) { + return $codStatusId; + } + + return (int) $this->getDefaultInitialStatusId($shopId); + } + + public function getPaidStatusId(?int $shopId = null): ?int + { + return (int) $this->configuration->get(self::PAID_OS_ID, $shopId); + } + + public function getStatusDescriptionMap(): array + { + return array_map([$this, 'decodeStatusDescriptionMap'], $this->configuration->getLocalized(self::STATUS_DESCRIPTION_MAP)); + } + + public function getStatusDescription(int $statusId, int $languageId, int $shopId): ?string + { + $map = $this->getStatusDescriptionMapping($languageId, $shopId); + + return $map[$statusId] ?? null; + } + + /** + * @return PaymentType[] + */ + public function getAvailablePaymentOptions(?int $shopId = null): array + { + if (isset($this->availablePaymentOptions[(int) $shopId])) { + return $this->availablePaymentOptions[(int) $shopId]; + } + + if ($this->isAllPaymentOptionsEnabled($shopId)) { + return $this->availablePaymentOptions[(int) $shopId] = []; + } + + return $this->availablePaymentOptions[(int) $shopId] = $this->loadAvailablePaymentOptions($shopId); + } + + public function getPointOfSaleId(?int $shopId = null): ?string + { + return $this->configuration->get(self::POS_ID, $shopId); + } + + public function getMessageFormat(?int $shopId = null): string + { + return $this->getMessageOptions($shopId)->getFormat(); + } + + public function copy(): OrdersConfigurationInterface + { + $configuration = new DTO\OrdersConfiguration( + $this->getDefaultInitialStatusId(), + $this->getPaidStatusId(), + $this->getPointOfSaleId(), + $this->getStatusDescriptionMap(), + $this->loadAvailablePaymentOptions() + ); + + return $configuration + ->setCashOnDeliveryStatusId($this->getCashOnDeliveryStatus()) + ->setMessageOptions($this->getMessageOptions()) + ->setAllPaymentOptionsEnabled($this->isAllPaymentOptionsEnabled()); + } + + public function persist(OrdersConfigurationInterface $configuration): void + { + $defaultInitialStatusId = $configuration->getInitialStatusId(); + $codStatusId = $configuration->getInitialStatusId(PaymentType::CashOnDelivery()); + + $this->configuration->set(self::INITIAL_OS_ID, $defaultInitialStatusId); + $this->configuration->set(self::COD_OS_ID, $codStatusId); + $this->configuration->set(self::PAID_OS_ID, $configuration->getPaidStatusId()); + $this->configuration->set(self::POS_ID, $configuration->getPointOfSaleId()); + $this->setOrderStatusDescriptionMapping($configuration->getStatusDescriptionMap()); + $this->setAvailablePaymentOptions($configuration); + $this->setMessageOptions($configuration); + } + + private function loadStatusDescriptionMap(int $languageId, ?int $shopId): array + { + $config = $this->configuration->get(self::STATUS_DESCRIPTION_MAP, $shopId, $languageId); + + return $this->decodeStatusDescriptionMap($config); + } + + private function decodeStatusDescriptionMap($value): array + { + if (null === $value) { + return []; + } + + $map = json_decode($value, true); + + return is_array($map) ? $map : []; + } + + private function getStatusDescriptionMapping(int $languageId, ?int $shopId = null): array + { + if (!isset($this->descriptionMappings[$languageId][(int) $shopId])) { + $this->descriptionMappings[$languageId][(int) $shopId] = $this->loadStatusDescriptionMap($languageId, $shopId); + } + + return $this->descriptionMappings[$languageId][(int) $shopId]; + } + + private function setOrderStatusDescriptionMapping(array $data): void + { + $data = array_map('array_filter', $data); + + $this->configuration->set(self::STATUS_DESCRIPTION_MAP, array_map('json_encode', $data)); + + $this->descriptionMappings = []; + foreach ($data as $languageId => $map) { + $this->descriptionMappings[$languageId][0] = $map; + } + } + + private function isAllPaymentOptionsEnabled(?int $shopId = null): bool + { + $value = $this->configuration->get(self::ENABLE_ALL_PAYMENT_OPTIONS, $shopId); + + if (null === $value && !$this->configuration->has(self::AVAILABLE_PAYMENT_OPTIONS)) { + return true; + } + + return (bool) $value; + } + + private function setAllPaymentOptionsEnabled(bool $enabled): void + { + $this->configuration->set(self::ENABLE_ALL_PAYMENT_OPTIONS, (int) $enabled); + } + + private function loadAvailablePaymentOptions(?int $shopId = null): array + { + $config = $this->configuration->get(self::AVAILABLE_PAYMENT_OPTIONS, $shopId); + + return $this->decodeAvailablePaymentOptions($config); + } + + private function decodeAvailablePaymentOptions($value): array + { + if (null === $value) { + return []; + } + + $data = json_decode($value, true); + + if (!is_array($data)) { + return []; + } + + return array_filter(array_map([PaymentType::class, 'tryFrom'], $data)); + } + + private function setAvailablePaymentOptions(OrdersConfigurationInterface $configuration): void + { + $availablePaymentOptions = array_values($configuration->getAvailablePaymentOptions()); + $this->configuration->set(self::AVAILABLE_PAYMENT_OPTIONS, json_encode($availablePaymentOptions)); + + if (is_callable([$configuration, 'isAllPaymentOptionsEnabled'])) { + $enabled = (bool) $configuration->isAllPaymentOptionsEnabled(); + $this->setAllPaymentOptionsEnabled($enabled); + + if ($enabled) { + $availablePaymentOptions = []; + } + } else { + $this->setAllPaymentOptionsEnabled(false); + } + + $this->availablePaymentOptions = [0 => $availablePaymentOptions]; + } + + private function getMessageOptions(?int $shopId = null): MessageOptions + { + if (!isset($this->messageOptions[(int) $shopId])) { + $this->messageOptions[(int) $shopId] = $this->loadMessageOptions($shopId); + } + + return $this->messageOptions[(int) $shopId]; + } + + private function loadMessageOptions(?int $shopId): MessageOptions + { + $config = $this->configuration->get(self::MESSAGE_FORMAT, $shopId); + + if (null !== $config && $options = $this->deserialize($config, MessageOptions::class)) { + return $options; + } + + return new MessageOptions(); + } + + private function setMessageOptions(OrdersConfigurationInterface $configuration): void + { + if (is_callable([$configuration, 'getMessageOptions'])) { + $options = $configuration->getMessageOptions(); + } else { + $options = new MessageOptions($configuration->getMessageFormat(), false, true); + } + + $value = $this->serializer->serialize($options, 'json'); + $this->configuration->set(self::MESSAGE_FORMAT, $value); + $this->messageOptions[0] = $options; + } + + private function getDefaultInitialStatusId(?int $shopId = null): ?int + { + $value = $this->configuration->get(self::INITIAL_OS_ID, $shopId); + + return null === $value ? null : (int) $value; + } + + private function getCashOnDeliveryStatusId(?int $shopId = null): ?int + { + $value = $this->configuration->get(self::COD_OS_ID, $shopId); + + return null === $value ? null : (int) $value; + } + + private function getCashOnDeliveryStatus(): ?\OrderState + { + if (null === $statusId = $this->getCashOnDeliveryStatusId()) { + return null; + } + + $status = new \OrderState(); + $status->id = $statusId; + + return $status; + } +} diff --git a/modules/inpostizi/src/Configuration/OrdersConfigurationInterface.php b/modules/inpostizi/src/Configuration/OrdersConfigurationInterface.php new file mode 100644 index 00000000..c49f54ee --- /dev/null +++ b/modules/inpostizi/src/Configuration/OrdersConfigurationInterface.php @@ -0,0 +1,33 @@ +> descriptions by language and status ID + */ + public function getStatusDescriptionMap(): array; + + public function getStatusDescription(int $statusId, int $languageId, int $shopId): ?string; + + public function getPointOfSaleId(?int $shopId = null): ?string; + + /** + * @return PaymentType[] if all payment options are enabled, an empty array should be returned + */ + public function getAvailablePaymentOptions(?int $shopId = null): array; + + public function getMessageFormat(?int $shopId = null): string; +} diff --git a/modules/inpostizi/src/Configuration/PersistentConfigurationInterface.php b/modules/inpostizi/src/Configuration/PersistentConfigurationInterface.php new file mode 100644 index 00000000..f4548098 --- /dev/null +++ b/modules/inpostizi/src/Configuration/PersistentConfigurationInterface.php @@ -0,0 +1,20 @@ +configuration = $configuration; + } + + public function getDefaultShopId(): int + { + return (int) $this->configuration->get(self::DEFAULT_SHOP_ID); + } + + public function getDefaultCurrencyId(?int $shopId = null): int + { + return (int) $this->configuration->get(self::DEFAULT_CURRENCY_ID, $shopId); + } + + public function getDefaultCountryId(?int $shopId = null): int + { + return (int) $this->configuration->get(self::DEFAULT_COUNTRY_ID, $shopId); + } + + public function getDefaultLanguageId(?int $shopId = null): int + { + return (int) $this->configuration->get(self::DEFAULT_LANGUAGE_ID, $shopId); + } + + public function getTaxAddressType(?int $shopId = null): string + { + $value = $this->configuration->get(self::TAX_ADDRESS_TYPE, $shopId); + + if (in_array($value, ['id_address_delivery', 'id_address_invoice'], true)) { + return $value; + } + + return 'id_address_delivery'; + } + + public function getFreeDeliveryMinAmount(?int $shopId = null): float + { + return (float) $this->configuration->get(self::FREE_DELIVERY_MIN_AMOUNT, $shopId); + } + + public function getShippingHandlingCost(?int $shopId = null): float + { + return (float) $this->configuration->get(self::SHIPPING_HANDLING_COST, $shopId); + } + + public function getAttributesSeparator(?int $shopId = null): string + { + return (string) $this->configuration->get(self::ATTRIBUTES_SEPARATOR, $shopId); + } + + public function getAnonymousCustomerGroupId(?int $shopId = null): int + { + return (int) $this->configuration->get(self::ANONYMOUS_CUSTOMER_GROUP_ID, $shopId); + } +} diff --git a/modules/inpostizi/src/Configuration/ProductAwareWidgetDisplayConfigurationInterface.php b/modules/inpostizi/src/Configuration/ProductAwareWidgetDisplayConfigurationInterface.php new file mode 100644 index 00000000..39c2b914 --- /dev/null +++ b/modules/inpostizi/src/Configuration/ProductAwareWidgetDisplayConfigurationInterface.php @@ -0,0 +1,15 @@ + + */ +final class ProductConfiguration implements ProductConfigurationInterface, PersistentConfigurationInterface +{ + private const INPOST_PAY_PRODUCT_IMAGE_NORMAL_TYPE = 'INPOST_PAY_PRODUCT_IMAGE_NORMAL_TYPE'; + private const INPOST_PAY_PRODUCT_IMAGE_SMALL_TYPE = 'INPOST_PAY_PRODUCT_IMAGE_SMALL_TYPE'; + private const INPOST_PAY_PRODUCT_IMAGE_LARGE_TYPE = 'INPOST_PAY_PRODUCT_IMAGE_LARGE_TYPE'; + + /** + * @var ShopAwareConfigurationInterface + */ + private $configuration; + + public function __construct(ShopAwareConfigurationInterface $configuration) + { + $this->configuration = $configuration; + } + + public function getNormalImageTypeId(?int $shopId = null): ?int + { + return (int) $this->configuration->get(self::INPOST_PAY_PRODUCT_IMAGE_NORMAL_TYPE, $shopId); + } + + public function getSmallImageTypeId(?int $shopId = null): ?int + { + return (int) $this->configuration->get(self::INPOST_PAY_PRODUCT_IMAGE_SMALL_TYPE, $shopId); + } + + public function getLargeImageTypeId(?int $shopId = null): ?int + { + return (int) $this->configuration->get(self::INPOST_PAY_PRODUCT_IMAGE_LARGE_TYPE, $shopId); + } + + public function copy(): ProductConfigurationInterface + { + return new DTO\ProductConfiguration( + $this->getNormalImageTypeId(), + $this->getSmallImageTypeId(), + $this->getLargeImageTypeId() + ); + } + + public function persist(ProductConfigurationInterface $configuration): void + { + $this->configuration->set(self::INPOST_PAY_PRODUCT_IMAGE_NORMAL_TYPE, $configuration->getNormalImageTypeId()); + $this->configuration->set(self::INPOST_PAY_PRODUCT_IMAGE_SMALL_TYPE, $configuration->getSmallImageTypeId()); + $this->configuration->set(self::INPOST_PAY_PRODUCT_IMAGE_LARGE_TYPE, $configuration->getLargeImageTypeId()); + } +} diff --git a/modules/inpostizi/src/Configuration/ProductConfigurationInterface.php b/modules/inpostizi/src/Configuration/ProductConfigurationInterface.php new file mode 100644 index 00000000..8bde0df2 --- /dev/null +++ b/modules/inpostizi/src/Configuration/ProductConfigurationInterface.php @@ -0,0 +1,14 @@ +getWidgetConfiguration()->getBindingPlace()) { + throw new \DomainException('Expected binding place to be "%s", "%s" given', BindingPlace::ProductCard()->value, $bindingPlace->value); + } + + $this->configuration = $configuration; + $this->validator = $validator; + $this->constraints = $constraints; + } + + public function isDisplayed($product = null): bool + { + if (null === $product) { + return $this->configuration->isDisplayed(); + } + + if (!$this->configuration->isDisplayed($product)) { + return false; + } + + $violations = $this->validator->validate($product, new Sequentially([ + 'constraints' => $this->constraints, + ])); + + return 0 === count($violations); + } + + public function getWidgetConfiguration(): WidgetConfigurationInterface + { + return $this->configuration->getWidgetConfiguration(); + } + + public function getHtmlStyles(): iterable + { + return $this->configuration->getHtmlStyles(); + } +} diff --git a/modules/inpostizi/src/Configuration/ProductRestrictionsConfigurationInterface.php b/modules/inpostizi/src/Configuration/ProductRestrictionsConfigurationInterface.php new file mode 100644 index 00000000..5dbab50d --- /dev/null +++ b/modules/inpostizi/src/Configuration/ProductRestrictionsConfigurationInterface.php @@ -0,0 +1,13 @@ + + */ +final class ShippingConfiguration implements ShippingConfigurationInterface, PersistentConfigurationInterface +{ + use SafeDeserializerTrait; + + private const COURIER_SHIPPING_OPTIONS = 'INPOST_PAY_COURIER_SHIPPING_OPTIONS'; + private const APM_SHIPPING_OPTIONS = 'INPOST_PAY_APM_SHIPPING_OPTIONS'; + + /** + * @var ShopAwareConfigurationInterface + */ + private $configuration; + + private $apmShippingOptions = []; + private $courierShippingOptions = []; + + public function __construct(ShopAwareConfigurationInterface $configuration, SerializerInterface $serializer) + { + $this->configuration = $configuration; + $this->serializer = $serializer; + } + + public function getShippingOptions(DeliveryType $deliveryType, ?int $shopId = null): ShippingOptions + { + switch ($deliveryType) { + case DeliveryType::Courier(): + return $this->getCourierShippingOptions($shopId); + case DeliveryType::Apm(): + return $this->getApmShippingOptions($shopId); + default: + throw new \LogicException('Not implemented.'); + } + } + + public function getApmShippingOptions(?int $shopId = null): ShippingOptions + { + if (!isset($this->apmShippingOptions[(int) $shopId])) { + $this->apmShippingOptions[(int) $shopId] = $this->loadApmShippingOptions($shopId); + } + + return clone $this->apmShippingOptions[(int) $shopId]; + } + + public function getCourierShippingOptions(?int $shopId = null): ShippingOptions + { + if (!isset($this->courierShippingOptions[(int) $shopId])) { + $this->courierShippingOptions[(int) $shopId] = $this->loadCourierShippingOptions($shopId); + } + + return clone $this->courierShippingOptions[(int) $shopId]; + } + + public function copy(): DTO\ShippingConfiguration + { + return new DTO\ShippingConfiguration( + $this->getApmShippingOptions(), + $this->getCourierShippingOptions() + ); + } + + public function persist(ShippingConfigurationInterface $configuration): void + { + $this->setApmShippingOptions($configuration->getApmShippingOptions()); + $this->setCourierShippingOptions($configuration->getCourierShippingOptions()); + } + + private function loadApmShippingOptions(?int $shopId): ShippingOptions + { + $value = $this->configuration->get(self::APM_SHIPPING_OPTIONS, $shopId); + + if (null !== $value && $shippingOptions = $this->deserialize($value, ShippingOptions::class)) { + return $shippingOptions; + } + + return new ShippingOptions(); + } + + private function loadCourierShippingOptions(?int $shopId): ShippingOptions + { + $value = $this->configuration->get(self::COURIER_SHIPPING_OPTIONS, $shopId); + + if (null !== $value && $shippingOptions = $this->deserialize($value, ShippingOptions::class)) { + return $shippingOptions; + } + + return new ShippingOptions(); + } + + private function setApmShippingOptions(ShippingOptions $options): void + { + $value = $this->serializer->serialize($options, 'json'); + $this->configuration->set(self::APM_SHIPPING_OPTIONS, $value); + $this->apmShippingOptions[0] = clone $options; + } + + private function setCourierShippingOptions(ShippingOptions $options): void + { + $value = $this->serializer->serialize($options, 'json'); + $this->configuration->set(self::COURIER_SHIPPING_OPTIONS, $value); + $this->courierShippingOptions[0] = clone $options; + } +} diff --git a/modules/inpostizi/src/Configuration/ShippingConfigurationInterface.php b/modules/inpostizi/src/Configuration/ShippingConfigurationInterface.php new file mode 100644 index 00000000..14d12d1e --- /dev/null +++ b/modules/inpostizi/src/Configuration/ShippingConfigurationInterface.php @@ -0,0 +1,17 @@ + CSS values by property + */ + public function getHtmlStyles(); +} diff --git a/modules/inpostizi/src/ContextManager.php b/modules/inpostizi/src/ContextManager.php new file mode 100644 index 00000000..3f7aee65 --- /dev/null +++ b/modules/inpostizi/src/ContextManager.php @@ -0,0 +1,301 @@ +context = $context; + $this->manager = $manager; + $this->configuration = $configuration; + } + + public function getContext(): \Context + { + return $this->context; + } + + /** + * @param array{ + * currency?: Currency, + * country?: array{ + * delivery: string, + * invoice: string, + * }, + * } $options + */ + public function changeContext(\Cart $cart, array $options = []): void + { + $restorationPoint = []; + + try { + $this->isContextCart($cart) + ? $this->buildRestorationPointForContextCart($cart, $options, $restorationPoint) + : $this->buildRestorationPoint($cart, $options, $restorationPoint); + } finally { + $this->stack[] = $restorationPoint; + } + } + + public function restoreContext(): void + { + if ([] === $this->stack) { + return; + } + + if ([] === $previousContext = array_pop($this->stack)) { + return; + } + + if (isset($previousContext['currency'])) { + \Cache::clean('getPackageShippingCost_' . (int) $this->context->cart->id . '_*'); + } + + foreach ($previousContext as $name => $value) { + $this->restoreContextProperty($name, $value); + } + } + + private function isContextCart(\Cart $cart): bool + { + if (!isset($this->context->cart)) { + return false; + } + + return (int) $this->context->cart->id === (int) $cart->id; + } + + private function buildRestorationPointForContextCart(\Cart $cart, array $options, array &$restorationPoint): void + { + $shopId = $options['shop_id'] ?? (int) $this->context->shop->id; + + if (null !== $shop = $this->changeShop($shopId)) { + $restorationPoint['shop'] = $shop; + } + + if (null !== $country = $this->changeCountry($cart, $options['country'] ?? [], $shopId)) { + $restorationPoint['country'] = $country; + } + + if (null === $currency = $this->changeCurrency($cart, $options['currency'] ?? null)) { + return; + } + + /* @see \Cart::$id_currency might have changed */ + $restorationPoint['cart'] = $this->context->cart; + $restorationPoint['currency'] = $currency; + + $this->context->cart = $cart; + } + + private function buildRestorationPoint(\Cart $cart, array $options, array &$restorationPoint): void + { + $restorationPoint['cart'] = $this->context->cart; + $this->context->cart = $cart; + + $shopId = $options['shop_id'] ?? (int) $cart->id_shop; + + if (null !== $shop = $this->changeShop($shopId)) { + $restorationPoint['shop'] = $shop; + } + + if (null !== $currency = $this->changeCurrency($cart, $options['currency'] ?? null)) { + $restorationPoint['currency'] = $currency; + } + + if (null !== $language = $this->changeLanguage($cart)) { + $restorationPoint['language'] = $language; + } + + if (null !== $country = $this->changeCountry($cart, $options['country'] ?? [], $shopId)) { + $restorationPoint['country'] = $country; + } + + $restorationPoint['customer'] = $this->changeCustomer($cart); + } + + private function restoreContextProperty(string $name, $value): void + { + if ('shop' === $name) { + [$value, $type, $id] = $value; + \Shop::setContext($type, $id); + } + + $this->context->{$name} = $value; + + if ('language' === $name) { + $this->context->getTranslator()->setLocale($this->context->language->locale); + } + } + + private function changeCurrency(\Cart $cart, ?Currency $targetCurrency): ?\Currency + { + /** @var CurrencyRepository $repository */ + $repository = $this->manager->getRepository(\Currency::class); + + $currency = $repository->find((int) $cart->id_currency); + $currentCurrency = null === $currency ? null : Currency::tryFrom($currency->iso_code); + + if ( + null === $currentCurrency + || null !== $targetCurrency && $currentCurrency !== $targetCurrency + ) { + $targetCurrency = $targetCurrency ?? Currency::getDefault(); + $currency = $repository->findOneByIsoCode($targetCurrency->value); + + if (null === $currency) { + throw new \RuntimeException('Could not find suitable currency to switch to.'); + } + + $cart->id_currency = $currency->id; + } + + $contextCurrency = $this->context->currency; + + if ($contextCurrency && (int) $contextCurrency->id === (int) $currency->id) { + return null; + } + + $this->context->currency = $currency; + \Cache::clean('getPackageShippingCost_' . (int) $cart->id . '_*'); + + return $contextCurrency; + } + + private function changeShop(int $shopId): ?array + { + $type = \Shop::getContext(); + $contextShop = $this->context->shop; + + if (\Shop::CONTEXT_SHOP === $type && (int) $contextShop->id === $shopId) { + return null; + } + + $restorationData = [ + $contextShop, + $type, + \Shop::CONTEXT_GROUP === $type ? \Shop::getContextShopGroupID() : \Shop::getContextShopID(), + ]; + + \Shop::setContext(\Shop::CONTEXT_SHOP, $shopId); + $this->context->shop = $this->manager->getRepository(\Shop::class)->find($shopId); + + return $restorationData; + } + + private function changeCustomer(\Cart $cart): ?\Customer + { + $contextCustomer = $this->context->customer; + $customerId = (int) $cart->id_customer; + + if ($contextCustomer && (int) $contextCustomer->id === $customerId) { + return $contextCustomer; + } + + $customer = $this->manager->getRepository(\Customer::class)->find($customerId) ?? new \Customer(); + $this->context->customer = $customer; + $cart->setTaxCalculationMethod(); + + return $contextCustomer; + } + + private function changeLanguage(\Cart $cart): ?\Language + { + $contextLanguage = $this->context->language; + + if ((int) $contextLanguage->id === $languageId = (int) $cart->id_lang) { + return null; + } + + $language = $this->manager->getRepository(\Language::class)->find($languageId); + $this->context->language = $language; + $this->context->getTranslator()->setLocale($this->context->language->locale); + + return $contextLanguage; + } + + private function changeCountry(\Cart $cart, array $countryCodes, int $shopId): ?\Country + { + if (null === $country = $this->findTaxCalculationCountry($cart, $countryCodes, $shopId)) { + throw new \RuntimeException('Could not find suitable country to switch to.'); + } + + $contextCountry = $this->context->country; + + if ((int) $contextCountry->id === (int) $country->id) { + return null; + } + + $this->context->country = $country; + + return $contextCountry; + } + + private function findTaxCalculationCountry(\Cart $cart, array $countryCodes, int $shopId): ?\Country + { + $taxAddressType = $this->configuration->getTaxAddressType($shopId); + + if ('id_address_delivery' === $taxAddressType) { + // as of writing this comment, only domestic delivery is available + $isoCode = $countryCodes['delivery'] ?? 'PL'; + + return $this->getCountryByIsoCode($isoCode, (int) $cart->id_lang); + } + + if (isset($countryCodes['invoice'])) { + return $this->getCountryByIsoCode($countryCodes['invoice'], (int) $cart->id_lang); + } + + $taxAddressId = (int) $cart->id_address_invoice; + $taxAddress = $this->manager->getRepository(\Address::class)->find($taxAddressId); + + if (null === $taxAddress || 0 >= $countryId = (int) $taxAddress->id_country) { + $countryId = $this->configuration->getDefaultCountryId($shopId); + } + + return $this->manager + ->getRepository(\Country::class) + ->find($countryId, (int) $cart->id_lang); + } + + private function getCountryByIsoCode(string $isoCode, int $languageId): \Country + { + $country = $this->manager + ->getRepository(\Country::class) + ->findOneBy([ + 'iso_code' => $isoCode, + 'id_lang' => $languageId, + ], ['active' => 'DESC']); + + if (null !== $country) { + return $country; + } + + throw new \RuntimeException(sprintf('Country "%s" not found.', $isoCode)); // TODO specific exception + } +} diff --git a/modules/inpostizi/src/Controller/Admin/AbstractConfigurationController.php b/modules/inpostizi/src/Controller/Admin/AbstractConfigurationController.php new file mode 100644 index 00000000..c1bd6e92 --- /dev/null +++ b/modules/inpostizi/src/Controller/Admin/AbstractConfigurationController.php @@ -0,0 +1,200 @@ + $configInitializers + */ + public function __construct(LegacyTranslator $translator, \Context $context, iterable $configInitializers, ApiConfigurationInterface $apiConfiguration, bool $debug = false) + { + $this->translator = $translator; + $this->context = $context; + $this->apiConfiguration = $apiConfiguration; + $this->debug = $debug; + + foreach ($configInitializers as $initializer) { + $initializer->init(); + } + } + + /** + * @param string $view + * + * @internal public visibility for compatibility with Sf 2.8 + */ + public function render($view, array $parameters = [], Response $response = null): Response + { + $parameters['is_legacy_admin_page'] = version_compare(_PS_VERSION_, '1.7.4.0', '<'); + + return parent::render($view, $parameters, $response); + } + + final protected static function getConfigPermission(): array + { + $role = \Access::sluggifyModule([ + 'name' => 'inpostizi', + ], \Access::getAuthorizationFromLegacy('configure')); + + return [$role, null]; + } + + protected function checkAccess(): void + { + foreach ($this->getRequiredPermissions() as [$attributes, $subject]) { + $this->denyAccessUnlessGranted($attributes, $subject); + } + } + + /** + * @return iterable + */ + protected function getRequiredPermissions(): iterable + { + yield self::getConfigPermission(); + } + + protected function trans(string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string + { + return $this->context->getTranslator()->trans($id, $parameters, $domain, $locale); + } + + protected function renderNav(Request $request): string + { + $pages = [ + 'general' => [ + 'route' => 'admin_inpost_izi_config_general', + 'title' => $this->translator->l('Configuration', ConfigurationController::TRANSLATION_SOURCE), + ], + 'consents' => [ + 'route' => 'admin_inpost_izi_config_consents', + 'title' => $this->translator->l('Consents', ConfigurationController::TRANSLATION_SOURCE), + ], + 'shipping' => [ + 'route' => 'admin_inpost_izi_config_shipping', + 'title' => $this->translator->l('Shipping configuration', ConfigurationController::TRANSLATION_SOURCE), + ], + 'gui' => [ + 'route' => 'admin_inpost_izi_config_gui', + 'title' => $this->translator->l('GUI configuration', ConfigurationController::TRANSLATION_SOURCE), + ], + 'support' => [ + 'route' => 'admin_inpost_izi_config_support', + 'title' => $this->translator->l('Support', ConfigurationController::TRANSLATION_SOURCE), + ], + ]; + + if (null !== $this->apiConfiguration->getClientCredentials() && $this->isGranted(PageVoter::READ, 'AdminProducts_')) { + $pages['products'] = [ + 'route' => 'admin_inpost_izi_products_index', + 'title' => $this->translator->l('Hot products', HotProductController::TRANSLATION_SOURCE), + 'active_checker' => static function (Request $request): bool { + return $request->attributes->has('_inpost_izi_hot_product_page'); + }, + ]; + } + + return $this->renderView('@Modules/inpostizi/views/templates/admin/config/nav.html.twig', [ + 'nav_items' => array_map(function (array $page) use ($request): array { + return [ + 'url' => $this->generateUrl($page['route']), + 'label' => $page['title'], + 'active' => isset($page['active_checker']) + ? $page['active_checker']($request) + : $page['route'] === $request->attributes->get('_route'), + ]; + }, $pages), + ]); + } + + protected function handleError(\Throwable $e, Request $request): void + { + $this->logger = $this->logger ?? new NullLogger(); + $this->logger->critical('An error occurred while processing the request: {exception}', [ + 'route' => $request->attributes->get('_route'), + 'exception' => $e, + ]); + + if ($this->debug) { + throw $e; + } + + $this->addFlash('error', $this->trans('An unexpected error occurred. [%type% code %code%]', [ + '%type%' => get_class($e), + '%code%' => $e->getCode(), + ], 'Admin.Notifications.Error')); + } + + final protected function isGranted($attributes, $subject = null): bool + { + if (parent::isGranted($attributes, $subject)) { + return true; + } + + if (null !== $subject || !str_starts_with($attributes, 'ROLE_')) { + return false; + } + + return $this->checkUserRole($attributes); + } + + /** + * Checks module configuration role using the user entity instead of the security token, since the token may not contain + * all the user roles if it was reloaded from session. + * + * Implementing a custom security voter (PS does not provide a default one) would have been a cleaner solution, + * but that could cause problems if the container cache was not refreshed after disabling or uninstalling the module. + */ + private function checkUserRole(string $role): bool + { + if (null === $user = $this->getUser()) { + return false; + } + + $userRoles = array_map(static function ($role): string { + if ($role instanceof Role) { + return $role->getRole(); + } + + return (string) $role; + }, $user->getRoles()); + + return in_array($role, $userRoles, true); + } +} diff --git a/modules/inpostizi/src/Controller/Admin/AbstractController.php b/modules/inpostizi/src/Controller/Admin/AbstractController.php new file mode 100644 index 00000000..bd825141 --- /dev/null +++ b/modules/inpostizi/src/Controller/Admin/AbstractController.php @@ -0,0 +1,18 @@ +checkAccess(); + + $command = $commandFactory->create(); + + $form = $this->createForm(GeneralConfigurationType::class, $command); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + try { + $bus->handle($form->getData()); + $this->addFlash('success', $this->trans('Successful update.', [], 'Admin.Notifications.Success')); + + return $this->redirectToRoute('admin_inpost_izi_config_general'); + } catch (\Throwable $e) { + $this->handleError($e, $request); + } + } + + return $this->render('@Modules/inpostizi/views/templates/admin/config/general.html.twig', [ + 'form' => $form->createView(), + 'layoutTitle' => $this->translator->l('Configuration', self::TRANSLATION_SOURCE), + 'headerTabContent' => $this->renderNav($request), + ]); + } + + /** + * @Route(path="/consents", name="admin_inpost_izi_config_consents", methods={"GET", "POST"}) + */ + public function consentConfig(Request $request, ConsentsConfigurationInterface $configuration, CommandBusInterface $bus): Response + { + $this->checkAccess(); + + $command = new UpdateConsentsConfigurationCommand(...$configuration->getConsents()); + + $form = $this->createForm(ConsentsConfigurationType::class, $command); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + try { + $bus->handle($form->getData()); + $this->addFlash('success', $this->trans('Successful update.', [], 'Admin.Notifications.Success')); + + return $this->redirectToRoute('admin_inpost_izi_config_consents'); + } catch (\Throwable $e) { + $this->handleError($e, $request); + } + } + + return $this->render('@Modules/inpostizi/views/templates/admin/config/consents.html.twig', [ + 'form' => $form->createView(), + 'layoutTitle' => $this->translator->l('Consents', self::TRANSLATION_SOURCE), + 'headerTabContent' => $this->renderNav($request), + ]); + } + + /** + * @param GuiConfiguration $configuration + * + * @Route(path="/gui", name="admin_inpost_izi_config_gui", methods={"GET", "POST"}) + */ + public function guiConfig(Request $request, GuiConfigurationInterface $configuration, CommandBusInterface $bus): Response + { + $this->checkAccess(); + + $form = $this->createForm(GuiConfigurationType::class, $configuration->copy()); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $command = new UpdateGuiConfigurationCommand($form->getData()); + + try { + $bus->handle($command); + $this->addFlash('success', $this->trans('Successful update.', [], 'Admin.Notifications.Success')); + + return $this->redirectToRoute('admin_inpost_izi_config_gui'); + } catch (\Throwable $e) { + $this->handleError($e, $request); + } + } + + return $this->render('@Modules/inpostizi/views/templates/admin/config/gui.html.twig', [ + 'form' => $form->createView(), + 'layoutTitle' => $this->translator->l('GUI configuration', self::TRANSLATION_SOURCE), + 'headerTabContent' => $this->renderNav($request), + 'widget_js_uri' => $this->apiConfiguration->getEnvironment()->getWidgetJavaScriptUri(), + 'merchant_client_id' => $this->apiConfiguration->getMerchantClientId(), + ]); + } + + /** + * @param ShippingConfiguration $configuration + * + * @Route(path="/shipping", name="admin_inpost_izi_config_shipping", methods={"GET", "POST"}) + */ + public function shippingConfig(Request $request, ShippingConfigurationInterface $configuration, CommandBusInterface $bus): Response + { + $this->checkAccess(); + + $form = $this->createForm(ShippingConfigurationType::class, $configuration->copy()); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $command = new UpdateShippingConfigurationCommand($form->getData()); + + try { + $bus->handle($command); + $this->addFlash('success', $this->trans('Successful update.', [], 'Admin.Notifications.Success')); + + return $this->redirectToRoute('admin_inpost_izi_config_shipping'); + } catch (\Throwable $e) { + $this->handleError($e, $request); + } + } + + return $this->render('@Modules/inpostizi/views/templates/admin/config/shipping.html.twig', [ + 'form' => $form->createView(), + 'layoutTitle' => $this->translator->l('Shipping configuration', self::TRANSLATION_SOURCE), + 'headerTabContent' => $this->renderNav($request), + ]); + } + + /** + * @param AdvancedConfiguration $configuration + * + * @Route(path="/support", name="admin_inpost_izi_config_support", methods={"GET"}) + */ + public function support(Request $request, AdvancedConfigurationInterface $configuration, CommandBusInterface $bus): Response + { + $this->checkAccess(); + + $form = $this->createForm(AdvancedConfigurationType::class, $configuration->copy(), [ + 'action' => $this->generateUrl('admin_inpost_izi_config_support_save', $request->query->all()), + ]); + + return $this->render('@Modules/inpostizi/views/templates/admin/config/support.html.twig', [ + 'layoutTitle' => $this->translator->l('Support', self::TRANSLATION_SOURCE), + 'headerTabContent' => $this->renderNav($request), + 'form' => $form->createView(), + 'status' => $bus->handle(new CheckStatusCommand()), + ]); + } + + /** + * @param AdvancedConfiguration $configuration + * + * @Route(path="/support", name="admin_inpost_izi_config_support_save", methods={"POST"}) + */ + public function supportSave(Request $request, AdvancedConfigurationInterface $configuration, CommandBusInterface $bus): Response + { + if (!$this->isGranted(...self::getConfigPermission())) { + return new JsonResponse([ + 'success' => false, + 'message' => 'Access Denied.', + ], 403); + } + + $form = $this->createForm(AdvancedConfigurationType::class, $configuration->copy()); + $form->handleRequest($request); + + if (!$form->isSubmitted()) { + return new JsonResponse([ + 'success' => false, + 'message' => 'Malformed request.', + ], 400); + } + + if (!$form->isValid()) { + return new JsonResponse([ + 'success' => false, + 'message' => (string) $form->getErrors(), + ], 422); + } + + $command = new UpdateAdvancedConfigurationCommand($form->getData()); + + try { + $bus->handle($command); + + return new JsonResponse([ + 'success' => true, + 'message' => $this->trans('Successful update.', [], 'Admin.Notifications.Success'), + ]); + } catch (\Exception $e) { + return new JsonResponse([ + 'success' => false, + 'message' => $this->trans('Oops... looks like an unexpected error occurred', [], 'Admin.Notifications.Error'), + ], 500); + } + } + + /** + * @Route(path="/download-data", name="admin_inpost_izi_config_download_data", methods={"GET"}) + */ + public function downloadData(CommandBusInterface $bus): Response + { + $this->checkAccess(); + + $callback = $bus->handle(new DownloadModuleDataCommand()); + $response = new StreamedResponse($callback); + + $disposition = $response->headers->makeDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, 'module_data.zip'); + + $response->headers->add([ + 'Content-Type' => 'application/x-zip', + 'Content-Disposition' => $disposition, + ]); + + return $response; + } +} diff --git a/modules/inpostizi/src/Controller/Admin/HotProductController.php b/modules/inpostizi/src/Controller/Admin/HotProductController.php new file mode 100644 index 00000000..a5854e3b --- /dev/null +++ b/modules/inpostizi/src/Controller/Admin/HotProductController.php @@ -0,0 +1,355 @@ + $configInitializers + */ + public function __construct(Context $shopContext, LegacyTranslator $translator, \Context $context, ApiConfigurationInterface $apiConfiguration, iterable $configInitializers, bool $debug = false) + { + parent::__construct($translator, $context, $configInitializers, $apiConfiguration, $debug); + $this->shopContext = $shopContext; + } + + /** + * @Route(name="admin_inpost_izi_products_index", methods={"GET"}) + */ + public function index(Request $request, HotProductRepositoryInterface $repository, HotProductViewDataFactory $viewDataFactory): Response + { + $this->checkAccess(); + + if (null === $this->apiConfiguration->getClientCredentials()) { + return $this->redirectToRoute('admin_inpost_izi_config_general'); + } + + if (null !== $shopId = $this->shopContext->getContextShopID()) { + $shopId = (int) $shopId; + } + + $products = $repository->findAll($shopId); + $viewData = $viewDataFactory->createForProducts($products); + + if (!$statusAvailable = $viewData->isStatusAvailable()) { + $this->addFlash('warning', $this->translator->l('Failed to fetch product statuses from the API.', self::TRANSLATION_SOURCE)); + } + + return $this->render('@Modules/inpostizi/views/templates/admin/hot_products/index.html.twig', [ + 'products' => $viewData->getProducts(), + 'is_status_available' => $statusAvailable, + 'is_multistore_context' => null === $shopId, + 'layoutTitle' => $this->translator->l('Hot products', self::TRANSLATION_SOURCE), + 'headerTabContent' => $this->renderNav($request), + ]); + } + + /** + * @Route(path="/new", name="admin_inpost_izi_products_create", methods={"GET", "POST"}) + */ + public function create(Request $request, CommandBusInterface $bus): Response + { + $this->checkAccess(); + + if (null === $this->apiConfiguration->getClientCredentials()) { + return $this->redirectToRoute('admin_inpost_izi_config_general'); + } + + if (null !== $shopId = $this->shopContext->getContextShopID()) { + $shopId = (int) $shopId; + } else { + $this->addFlash('warning', $this->translator->l('You are creating a hot product in a multistore context. Product data associated with the default shop will be sent to the Basket App.', self::TRANSLATION_SOURCE)); + } + + $form = $this->createForm(CreateHotProductType::class, new CreateHotProductCommand($shopId)); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + try { + $bus->handle($form->getData()); + $this->addFlash('success', $this->translator->l('Hot product has been created successfully and will be awaiting approval.', self::TRANSLATION_SOURCE)); + + return $this->redirectToRoute('admin_inpost_izi_products_index'); + } catch (HotProductExistsException $e) { + $this->addFlash('error', $this->translator->l('There already exists a hot product for product and combination.', self::TRANSLATION_SOURCE)); + } catch (\Throwable $e) { + $this->handleUpdateError($e, $request); + } + } + + return $this->render('@Modules/inpostizi/views/templates/admin/hot_products/create.html.twig', [ + 'form' => $form->createView(), + 'layoutTitle' => $this->translator->l('New hot product', self::TRANSLATION_SOURCE), + 'headerTabContent' => $this->renderNav($request), + 'is_ps_176' => str_starts_with(_PS_VERSION_, '1.7.6'), + ]); + } + + /** + * @Route(path="/{id}/edit", name="admin_inpost_izi_products_edit", methods={"GET", "POST"}, requirements={"id"="\d+"}) + */ + public function edit(int $id, Request $request, CommandBusInterface $bus, HotProductRepositoryInterface $repository, ProductRepository $productRepository): Response + { + $this->checkAccess(); + + if (null === $this->apiConfiguration->getClientCredentials()) { + return $this->redirectToRoute('admin_inpost_izi_config_general'); + } + + if (null === $product = $repository->find($id)) { + $this->addFlash('error', $this->translator->l('Hot product was not found.', self::TRANSLATION_SOURCE)); + + return $this->redirectToRoute('admin_inpost_izi_products_index'); + } + + $command = UpdateHotProductCommand::for($product)->setCreateIfNotFound(true); + $form = $this->createForm(UpdateHotProductType::class, $command); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + try { + /** @var Status $status */ + $status = $bus->handle($form->getData()); + if (Status::Active() === $status) { + $this->addFlash('success', $this->trans('Successful update.', [], 'Admin.Notifications.Success')); + } else { + $this->addFlash('success', $this->translator->l('Hot product has been updated successfully and will be awaiting approval.', self::TRANSLATION_SOURCE)); + } + + return $this->redirectToRoute('admin_inpost_izi_products_index'); + } catch (\Throwable $e) { + $this->handleUpdateError($e, $request); + } + } + + return $this->render('@Modules/inpostizi/views/templates/admin/hot_products/edit.html.twig', [ + 'form' => $form->createView(), + 'layoutTitle' => sprintf( + $this->translator->l('Edit hot product: %s', self::TRANSLATION_SOURCE), + $productRepository->getProductNameByProductId($product->getProductId(), (int) $this->context->language->id, $product->getCombinationId()) + ), + 'headerTabContent' => $this->renderNav($request), + ]); + } + + /** + * @Route(path="/{id}/delete", name="admin_inpost_izi_products_delete", methods={"DELETE"}, requirements={"id"="\d+"}) + */ + public function delete(int $id, Request $request, CommandBusInterface $bus): Response + { + $this->checkAccess(); + + if (null === $this->apiConfiguration->getClientCredentials()) { + return $this->redirectToRoute('admin_inpost_izi_config_general'); + } + + if (!$this->isCsrfTokenValid('inpost-izi-delete-product', $request->request->get('_csrf_token'))) { + $this->addFlash('error', $this->translator->l('The CSRF token is invalid.', self::TRANSLATION_SOURCE)); + + return $this->redirectToRoute('admin_inpost_izi_products_index'); + } + + $command = new DeleteHotProductCommand($id); + + try { + $bus->handle($command); + $this->addFlash('success', $this->translator->l('Hot product has been deleted successfully.', self::TRANSLATION_SOURCE)); + } catch (\Throwable $e) { + $this->handleUpdateError($e, $request); + } + + return $this->redirectToRoute('admin_inpost_izi_products_index'); + } + + /** + * @Route(path="/import/{referenceId}", name="admin_inpost_izi_products_api_import", methods={"POST"}) + */ + public function import(string $referenceId, Request $request, CommandBusInterface $bus): Response + { + $this->checkAccess(); + + if (null === $this->apiConfiguration->getClientCredentials()) { + return $this->redirectToRoute('admin_inpost_izi_config_general'); + } + + if (!$this->isCsrfTokenValid('inpost-izi-import-product', $request->request->get('_csrf_token'))) { + $this->addFlash('error', $this->translator->l('The CSRF token is invalid.', self::TRANSLATION_SOURCE)); + + return $this->redirectToRoute('admin_inpost_izi_products_index'); + } + + if (null !== $shopId = $this->shopContext->getContextShopID()) { + $shopId = (int) $shopId; + } + + try { + $command = new ImportHotProductCommand($referenceId, $shopId); + $bus->handle($command); + + $this->addFlash('success', $this->translator->l('Product has been imported successfully.', self::TRANSLATION_SOURCE)); + } catch (HotProductExistsException $e) { + $this->addFlash('error', $this->translator->l('There already exists a hot product for product and combination.', self::TRANSLATION_SOURCE)); + } catch (\Throwable $e) { + $this->handleUpdateError($e, $request); + } + + return $this->redirectToRoute('admin_inpost_izi_products_index'); + } + + /** + * @Route(path="/delete-remote/{referenceId}", name="admin_inpost_izi_products_api_delete", methods={"DELETE"}) + */ + public function deleteRemote(string $referenceId, Request $request, CommandBusInterface $bus): Response + { + $this->checkAccess(); + + if (null === $this->apiConfiguration->getClientCredentials()) { + return $this->redirectToRoute('admin_inpost_izi_config_general'); + } + + if (!$this->isCsrfTokenValid('inpost-izi-delete-remote-product', $request->request->get('_csrf_token'))) { + $this->addFlash('error', $this->translator->l('The CSRF token is invalid.', self::TRANSLATION_SOURCE)); + + return $this->redirectToRoute('admin_inpost_izi_products_index'); + } + + try { + $command = new DeleteRemoteProductCommand($referenceId); + $bus->handle($command); + + $this->addFlash('success', $this->translator->l('Product has been deleted from API successfully.', self::TRANSLATION_SOURCE)); + } catch (\Throwable $e) { + $this->handleUpdateError($e, $request); + } + + return $this->redirectToRoute('admin_inpost_izi_products_index'); + } + + /** + * @internal + * + * @Route(path="/autocomplete", name="admin_inpost_izi_products_autocomplete", methods={"GET"}) + */ + public function autocomplete(Request $request, ProductRepository $repository): Response + { + $this->denyAccessUnlessGranted(...self::getProductsReadPermission()); + + if ('' === $query = (string) $request->query->get('query')) { + throw new UnprocessableEntityHttpException('Query cannot be empty.'); + } + + if (0 >= $page = $request->query->getInt('page', 1)) { + throw new UnprocessableEntityHttpException('Page number must be greater than 0.'); + } + + $qb = $repository->createSearchQueryBuilder($query, (int) $this->context->language->id, (int) $this->context->shop->id); + $countQb = clone $qb; + + $results = $qb + ->limit(10, ($page - 1) * 10) + ->build() + ->getResult(); + + $count = (int) $countQb + ->setSelect('COUNT(DISTINCT p.id_product)') + ->build() + ->getSingleScalarResult(); + + if ($page < ceil($count / 10)) { + $parameters = array_merge($request->attributes->get('_route_params'), $request->query->all(), ['page' => $page + 1]); + $nextPage = $this->generateUrl($request->attributes->get('_route'), $parameters); + } else { + $nextPage = null; + } + + return new JsonResponse([ + 'results' => array_map(static function (\Product $product): array { + $label = $product->name ?? 'Product #' . $product->id; + + if ($product->reference) { + $label .= ' (ref. ' . $product->reference . ')'; + } + + return [ + 'value' => $product->id, + 'text' => $label, + ]; + }, $results), + 'next_page' => $nextPage, + ]); + } + + protected function getRequiredPermissions(): iterable + { + yield from parent::getRequiredPermissions(); + yield self::getProductsReadPermission(); + } + + private static function getProductsReadPermission(): array + { + // appending underscore to controller name was required before PS 1.7.5 + return [PageVoter::READ, 'AdminProducts_']; + } + + private function handleUpdateError(\Throwable $e, Request $request): void + { + if ($e instanceof HotProductExceptionInterface) { + $this->addFlash('error', $e->getMessage()); + } elseif ($e instanceof MaxProductLimitReachedException) { + $this->addFlash('error', $this->translator->l('Maximum number of hot products reached.', self::TRANSLATION_SOURCE)); + } elseif ($e instanceof BasketAppException) { + $this->addFlash('error', sprintf('Basket App API error "%s": "%s".', $e->getError()->getCode(), $e->getMessage())); + } elseif ($e instanceof NetworkExceptionInterface || $e instanceof OAuth2ExceptionInterface) { + $this->addFlash('error', 'API connection error.'); + } elseif ($e instanceof HttpExceptionInterface) { + $this->addFlash('error', sprintf('Unexpected Basket App API response status code: %d.', $e->getResponse()->getStatusCode())); + } else { + $this->handleError($e, $request); + } + } +} diff --git a/modules/inpostizi/src/Controller/Api/AbstractApiController.php b/modules/inpostizi/src/Controller/Api/AbstractApiController.php new file mode 100644 index 00000000..511d0cd4 --- /dev/null +++ b/modules/inpostizi/src/Controller/Api/AbstractApiController.php @@ -0,0 +1,54 @@ +serializer = $serializer; + $this->bus = $bus; + } + + /** + * @template T + * + * @param class-string $class + * + * @return T + */ + protected function decodeRequest(Request $request, string $class) + { + try { + return $this->serializer->deserialize($request->getContent(), $class, 'json', [ + 'datetime_format' => BasketAppClientInterface::DATETIME_FORMAT, + 'datetime_timezone' => BasketAppClientInterface::DATETIME_ZONE, + ]); + } catch (UnexpectedValueException $e) { + throw new MalformedRequestException('Could not decode request.', 0, $e); + } catch (ExceptionInterface $e) { + throw new InternalServerErrorException('Could not decode request.', 0, $e); + } + } +} diff --git a/modules/inpostizi/src/Controller/Api/BasketController.php b/modules/inpostizi/src/Controller/Api/BasketController.php new file mode 100644 index 00000000..c97f4f7f --- /dev/null +++ b/modules/inpostizi/src/Controller/Api/BasketController.php @@ -0,0 +1,83 @@ +bus->handle(new GetBasketCommand($basketId)); + + return $this->basketResponse($basket); + } + + public function confirm(string $basketId, Request $request): JsonResponse + { + $confirmation = $this->decodeRequest($request, BindingConfirmation::class); + $command = new ConfirmBasketBindingCommand($basketId, $confirmation); + + /** @var Basket $basket */ + $basket = $this->bus->handle($command); + + return $this->basketResponse($basket); + } + + public function update(string $basketId, Request $request): JsonResponse + { + $event = $this->decodeRequest($request, BasketEvent::class); + $command = new UpdateBasketCommand($basketId, $event); + + /** @var Basket $basket */ + $basket = $this->bus->handle($command); + + return $this->basketResponse($basket); + } + + public function deleteBinding(string $basketId): JsonResponse + { + $this->bus->handle(new DeleteBasketBindingCommand($basketId)); + + return JsonResponse::create()->setContent(null); + } + + public function addProduct(string $productId, Request $request): JsonResponse + { + $basketId = $this->decodeRequest($request, BasketId::class); + $command = new AddProductToBasketCommand($productId, $basketId); + + /** @var IdentifiableBasket $basket */ + $basket = $this->bus->handle($command); + + return $this->basketResponse($basket); + } + + /** + * @param Basket|IdentifiableBasket $basket + */ + private function basketResponse($basket): JsonResponse + { + $data = $this->serializer->serialize($basket, 'json', [ + 'datetime_format' => BasketAppClientInterface::DATETIME_FORMAT, + 'datetime_timezone' => BasketAppClientInterface::DATETIME_ZONE, + ]); + + return JsonResponse::create()->setContent($data); + } +} diff --git a/modules/inpostizi/src/Controller/Api/OrderController.php b/modules/inpostizi/src/Controller/Api/OrderController.php new file mode 100644 index 00000000..38df770c --- /dev/null +++ b/modules/inpostizi/src/Controller/Api/OrderController.php @@ -0,0 +1,59 @@ +decodeRequest($request, CreateOrderRequest::class); + $command = new CreateOrderCommand($data); + + /** @var Order $order */ + $order = $this->bus->handle($command); + + return $this->orderResponse($order, 201); + } + + public function get(string $orderId): JsonResponse + { + /** @var Order $order */ + $order = $this->bus->handle(new GetOrderCommand($orderId)); + + return $this->orderResponse($order); + } + + public function update(string $orderId, Request $request): JsonResponse + { + $event = $this->decodeRequest($request, OrderEvent::class); + $command = new UpdateOrderCommand($orderId, $event); + + /** @var OrderStatusData $orderStatus */ + $orderStatus = $this->bus->handle($command); + + return new JsonResponse($orderStatus); + } + + private function orderResponse(Order $order, int $status = 200): JsonResponse + { + $data = $this->serializer->serialize($order, 'json', [ + 'datetime_format' => BasketAppClientInterface::DATETIME_FORMAT, + 'datetime_timezone' => BasketAppClientInterface::DATETIME_ZONE, + ]); + + return JsonResponse::create(null, $status)->setContent($data); + } +} diff --git a/modules/inpostizi/src/Controller/Api/ProductController.php b/modules/inpostizi/src/Controller/Api/ProductController.php new file mode 100644 index 00000000..e327d88b --- /dev/null +++ b/modules/inpostizi/src/Controller/Api/ProductController.php @@ -0,0 +1,47 @@ +query->get('page_index'); + $pageSize = $request->query->get('page_size'); + $productIds = $request->query->get('product_ids'); + + if (null !== $productIds && !is_array($productIds)) { + $productIds = explode(',', $productIds); + } + + $command = new GetProductsCommand( + $request->attributes->getInt('_inpost_izi_shop_id'), + $pageIndex ? (int) $pageIndex : null, + $pageSize ? (int) $pageSize : null, + $productIds + ); + + /** @var Products $products */ + $products = $this->bus->handle($command); + + return $this->productsResponse($products); + } + + private function productsResponse(Products $products): JsonResponse + { + $data = $this->serializer->serialize($products, 'json', [ + 'datetime_format' => BasketAppClientInterface::DATETIME_FORMAT, + 'datetime_timezone' => BasketAppClientInterface::DATETIME_ZONE, + ]); + + return JsonResponse::create()->setContent($data); + } +} diff --git a/modules/inpostizi/src/Controller/WidgetController.php b/modules/inpostizi/src/Controller/WidgetController.php new file mode 100644 index 00000000..9d44d76f --- /dev/null +++ b/modules/inpostizi/src/Controller/WidgetController.php @@ -0,0 +1,177 @@ +module = $module; + $this->context = $context; + $this->bus = $bus; + $this->container = $container; + } + + public static function getSubscribedServices(): array + { + return [ + '?' . AuthorizationCheckerInterface::class, + ]; + } + + public function getBindingKey(Request $request): JsonResponse + { + try { + $this->denyAccessUnlessGranted(BindingWidgetVoter::VIEW, $request); + + if (!\Validate::isLoadedObject($this->context->cart)) { + return new JsonResponse([ + 'message' => $this->module->l('Cart does not exist.', self::TRANSLATION_SOURCE), + 'error_code' => 'CART_NOT_FOUND', + ], 404); + } + + $command = new GetBasketBindingKeyCommand( + $this->createBasket(), + $request->query->getBoolean('refresh') + ); + + /** @var BasketBindingKey $result */ + $result = $this->bus->handle($command); + $this->context->cookie->inpostizi_basket_id = $result->getBasketId(); + + return new JsonResponse($result->getBindingKey()); + } catch (\Exception $e) { + return $this->handleException($e); + } + } + + public function getOrderConfirmationUrl(): JsonResponse + { + try { + $command = new GetOrderConfirmationUrlCommand((string) $this->context->cookie->inpostizi_basket_id); + + /** @var string $url */ + $url = $this->bus->handle($command); + + return new JsonResponse($url); + } catch (\Exception $e) { + return $this->handleException($e); + } + } + + private function createBasket(): BasketInterface + { + if (!\Validate::isLoadedObject($this->context->cart)) { + throw new BadRequestHttpException($this->module->l('Cart does not exist.', self::TRANSLATION_SOURCE)); + } + + return new Cart($this->context->cart); + } + + private function handleException(\Exception $e): JsonResponse + { + if ($e instanceof HttpExceptionInterface) { + return new JsonResponse([ + 'message' => $e->getMessage(), + ], $e->getStatusCode(), $e->getHeaders()); + } + + if ($e instanceof NetworkExceptionInterface || $e instanceof BasketAppException || $e instanceof BasketAppHttpException) { + return new JsonResponse([ + 'message' => $this->module->l('There was a problem communicating with the mobile application. Please try again later.', self::TRANSLATION_SOURCE), + ], 502); + } + + if ($e instanceof \DomainException) { + return new JsonResponse([ + 'message' => $this->module->l('Your request could not be processed.', self::TRANSLATION_SOURCE), + ], 422); + } + + return new JsonResponse([ + 'message' => $this->module->l('Something went wrong. Please try again later.', self::TRANSLATION_SOURCE), + ], 500); + } + + /** + * @param mixed $attributes + * @param mixed $subject + */ + private function denyAccessUnlessGranted($attributes, $subject = null, string $message = 'Access Denied.'): void + { + if ($this->isGranted($attributes, $subject)) { + return; + } + + throw new AccessDeniedHttpException($message); + } + + private function isGranted($attributes, $subject = null): bool + { + if (null === $authChecker = $this->get(AuthorizationCheckerInterface::class)) { + return true; + } + + return $authChecker->isGranted($attributes, $subject); + } + + /** + * @template T of object + * + * @param class-string $name + * + * @return T|null + */ + private function get(string $name) + { + if (!$this->container->has($name)) { + return null; + } + + return $this->container->get($name); + } +} diff --git a/modules/inpostizi/src/Currency/PriceConverter.php b/modules/inpostizi/src/Currency/PriceConverter.php new file mode 100644 index 00000000..16bd22c6 --- /dev/null +++ b/modules/inpostizi/src/Currency/PriceConverter.php @@ -0,0 +1,68 @@ + + */ + private $repository; + + /** + * @var PrestaShopConfiguration + */ + private $configuration; + + /** + * @param ObjectRepositoryInterface<\Currency> $repository + */ + public function __construct(ObjectRepositoryInterface $repository, PrestaShopConfiguration $configuration) + { + $this->repository = $repository; + $this->configuration = $configuration; + } + + public function convert(float $amount, \Currency $target, ?\Currency $from = null): float + { + if (null === $from) { + $from = $this->getCurrency(); + } + + if ((int) $target->id === (int) $from->id) { + return $amount; + } + + return \Tools::convertPriceFull($amount, $from, $target); + } + + public function convertByIds(float $amount, int $targetCurrencyId, ?int $fromCurrencyId = null): float + { + if ($fromCurrencyId === $targetCurrencyId) { + return $amount; + } + + $from = $this->getCurrency($fromCurrencyId); + $target = $this->getCurrency($targetCurrencyId); + + return $this->convert($amount, $target, $from); + } + + private function getCurrency(?int $currencyId = null): \Currency + { + if (null === $currencyId) { + $currencyId = $this->configuration->getDefaultCurrencyId(); + } + + if (null !== $currency = $this->repository->find($currencyId)) { + return $currency; + } + + throw new \RuntimeException(sprintf('Currency %d does not exist.', $currencyId)); + } +} diff --git a/modules/inpostizi/src/Currency/PriceConverterInterface.php b/modules/inpostizi/src/Currency/PriceConverterInterface.php new file mode 100644 index 00000000..524d4a3b --- /dev/null +++ b/modules/inpostizi/src/Currency/PriceConverterInterface.php @@ -0,0 +1,18 @@ +db = $db ?? \Db::getInstance(); + } + + public function getPlatformVersion(): string + { + return $this->db->getVersion(); + } + + /** + * @template T + * + * @param \Closure(): T $closure function returning false on errors + * + * @return T value returned by $closure + * + * @throws \PrestaShopDatabaseException if $closure failed due to a DB error + */ + public function execute(\Closure $closure) + { + try { + $result = $closure(); + } catch (\PrestaShopDatabaseException $e) { + throw $this->normalizeException($e); + } catch (\PrestaShopException $e) { + if (!$e->getPrevious() instanceof \PDOException) { + throw $e; + } + + throw $this->normalizeException($e); + } + + if (false !== $result || !$error = $this->db->getNumberError()) { + return $result; + } + + throw new \PrestaShopDatabaseException($this->db->getMsgError(), $error); + } + + /** + * @return int number of affected rows + * + * @throws \PrestaShopDatabaseException on failure + */ + public function executeStatement(string $sql): int + { + $this->execute(function () use ($sql) { + return $this->db->execute($sql); + }); + + return (int) $this->db->Affected_Rows(); + } + + /** + * @param string $sql + * + * @return array|false + */ + public function fetchAssociative(string $sql) + { + return $this->execute(function () use ($sql) { + return $this->db->getRow($sql); + }); + } + + public function fetchAllAssociative(string $sql): array + { + return $this->execute(function () use ($sql) { + return $this->db->executeS($sql); + }); + } + + public function fetchFirstColumn(string $sql): array + { + $result = $this->execute(function () use ($sql) { + return $this->db->query($sql); + }); + + $rows = []; + + while (false !== $row = $this->db->nextRow($result)) { + $rows[] = array_shift($row); + } + + return $rows; + } + + /** + * @return mixed|false + */ + public function fetchOne(string $sql) + { + return $this->execute(function () use ($sql) { + return $this->db->getValue($sql); + }); + } + + /** + * @return string|int The ID of the last inserted row + */ + public function getLastInsertId() + { + return $this->db->Insert_ID(); + } + + public function insert(string $table, array $data): void + { + $this->execute(function () use ($table, $data) { + return $this->db->insert($table, $data, true); + }); + } + + /** + * @param array $data column-value pairs + * @param array $criteria update criteria + * + * @return int Number of affected rows + */ + public function update(string $table, array $data, array $criteria = []): int + { + $this->execute(function () use ($table, $data, $criteria) { + $where = $this->getWhereConditions($criteria); + + return $this->db->update($table, $data, $where, 0, true); + }); + + return (int) $this->db->Affected_Rows(); + } + + private function normalizeException(\PrestaShopException $e): \PrestaShopDatabaseException + { + $previous = $e->getPrevious(); + $errorCode = $previous instanceof \PDOException + ? $previous->errorInfo[1] ?? $this->db->getNumberError() + : $this->db->getNumberError(); + + return new \PrestaShopDatabaseException($e->getMessage(), $errorCode, $e); + } + + /** + * @param array $criteria delete criteria + * + * @return int Number of affected rows + */ + public function delete(string $table, array $criteria = []): int + { + $this->execute(function () use ($table, $criteria) { + $where = $this->getWhereConditions($criteria); + + return $this->db->delete($table, $where); + }); + + return (int) $this->db->Affected_Rows(); + } + + /** + * @param array $criteria + */ + private function getWhereConditions(array $criteria): string + { + if ([] === $criteria) { + return ''; + } + + $conditions = []; + + foreach ($criteria as $key => $value) { + if (is_int($key)) { + // $value should be raw SQL + $conditions[] = (string) $value; + + continue; + } + + $column = bqSQL($key); + if (null === $value) { + $conditions[] = sprintf('`%s` IS NULL', $column); + } else { + $conditions[] = sprintf('`%s` = \'%s\'', $column, pSQL($value)); + } + } + + return implode(' AND ', $conditions); + } +} diff --git a/modules/inpostizi/src/DependencyInjection/Argument/ServiceClosureArgument.php b/modules/inpostizi/src/DependencyInjection/Argument/ServiceClosureArgument.php new file mode 100644 index 00000000..c612c5cc --- /dev/null +++ b/modules/inpostizi/src/DependencyInjection/Argument/ServiceClosureArgument.php @@ -0,0 +1,27 @@ +value = $reference; + } + + public function getValue(): Reference + { + return $this->value; + } +} diff --git a/modules/inpostizi/src/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php b/modules/inpostizi/src/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php new file mode 100644 index 00000000..ddcfa36b --- /dev/null +++ b/modules/inpostizi/src/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php @@ -0,0 +1,78 @@ +pass = $pass; + $this->locatorTag = $locatorTag; + } + + public static function decorateRemovingPasses(ContainerBuilder $container, string $locatorTag): void + { + $passConfig = $container->getCompilerPassConfig(); + $removingPasses = $passConfig->getRemovingPasses(); + + foreach ($removingPasses as $key => $pass) { + if (!$pass instanceof RepeatedPass) { + continue; + } + + $removingPasses[$key] = new RepeatedPass(array_map(static function (RepeatablePassInterface $pass) use ($locatorTag) { + if (!$pass instanceof DecoratedPass) { + return $pass; + } + + return new AnalyzeServiceReferencesPass($pass, $locatorTag); + }, $pass->getPasses())); + } + + $passConfig->setRemovingPasses($removingPasses); + } + + public function setRepeatedPass(RepeatedPass $repeatedPass): void + { + $this->pass->setRepeatedPass($repeatedPass); + } + + public function process(ContainerBuilder $container): void + { + $this->pass->process($container); + $graph = $container->getCompiler()->getServiceReferenceGraph(); + + $aliases = array_map(static function (Alias $alias) { + return (string) $alias; + }, $container->getAliases()); + + foreach ($container->findTaggedServiceIds($this->locatorTag) as $locatorId => $tags) { + $definition = $container->getDefinition($locatorId); + + /** @var ServiceClosureArgument $value */ + foreach ($definition->getArgument(0) as $value) { + $id = (string) $value->getValue(); + $id = $aliases[$id] ?? $id; + + $graph->connect( + $locatorId, + $definition, + $id, + $container->getDefinition($id) + ); + } + } + } +} diff --git a/modules/inpostizi/src/DependencyInjection/Compiler/ProvideServiceLocatorFactoriesPass.php b/modules/inpostizi/src/DependencyInjection/Compiler/ProvideServiceLocatorFactoriesPass.php new file mode 100644 index 00000000..3ede142b --- /dev/null +++ b/modules/inpostizi/src/DependencyInjection/Compiler/ProvideServiceLocatorFactoriesPass.php @@ -0,0 +1,116 @@ +locatorTag = $locatorTag; + } + + public function process(ContainerBuilder $container): void + { + foreach ($container->findTaggedServiceIds($this->locatorTag) as $id => $tags) { + $definition = $container->getDefinition($id); + $argument = $this->prepareFactories($container, $id, $tags); + $definition->addArgument($argument); + } + } + + /** + * @return array + */ + private function prepareFactories(ContainerBuilder $container, string $id, array $tags): array + { + $aliases = array_flip(array_map(static function (Alias $alias) { + return (string) $alias; + }, $container->getAliases())); + + $refMaps = []; + + foreach ($tags as $attributes) { + if (!isset($attributes['tag'])) { + throw new InvalidArgumentException(sprintf('Service locator "%s" definition is invalid: "tag" attribute is missing.', $id)); + } + + $refMaps[] = $this->processTag($container, $attributes); + } + + $refMap = array_merge(...$refMaps); + ksort($refMap); + + return array_map(static function (Reference $reference) use ($container, $aliases) { + $id = (string) $reference; + $definition = $container->getDefinition($id); + $reference = isset($aliases[$id]) ? new Reference($aliases[$id]) : $reference; + + $class = $definition->getClass(); + + if (null !== $class && (class_exists($class) || interface_exists($class))) { + $reference = new TypedReference((string) $reference, $class); + } + + return new ServiceClosureArgument($reference); + }, $refMap); + } + + private function processTag(ContainerBuilder $container, array $attributes): array + { + $refMap = []; + + foreach ($container->findTaggedServiceIds($attributes['tag']) as $id => $tags) { + foreach ($tags as $tag) { + $locatorId = $this->resolveServiceId($container, $id, $attributes, $tag); + $refMap[$locatorId] = new Reference($id); + } + } + + return $refMap; + } + + private function resolveServiceId(ContainerBuilder $container, string $id, array $attributes, array $tag): string + { + if (!isset($attributes['index_by'])) { + return $id; + } + + $indexBy = $attributes['index_by']; + + if (isset($tag[$indexBy])) { + return $tag[$indexBy]; + } + + if (!isset($attributes['default_index_method'])) { + return $id; + } + + $method = $attributes['default_index_method']; + + if (null === $class = $container->getDefinition($id)->getClass()) { + throw new RuntimeException(sprintf('The definition for "%s" has no class.', $id)); + } + + if (!is_callable([$class, $method])) { + throw new RuntimeException(sprintf('%s::%s is not callable.', $class, $method)); + } + + return $class::$method(); + } +} diff --git a/modules/inpostizi/src/DependencyInjection/Compiler/TaggedIteratorsCollectorPass.php b/modules/inpostizi/src/DependencyInjection/Compiler/TaggedIteratorsCollectorPass.php new file mode 100644 index 00000000..69bcfe5e --- /dev/null +++ b/modules/inpostizi/src/DependencyInjection/Compiler/TaggedIteratorsCollectorPass.php @@ -0,0 +1,55 @@ +getDefinitions() as $definition) { + $this->processDefinition($definition, $container); + } + } + + private function processDefinition(Definition $definition, ContainerBuilder $container): void + { + foreach ($definition->getArguments() as $index => $argument) { + if (!is_string($argument)) { + continue; + } + + if (!preg_match('/^!tagged (.*)$/', $argument, $matches)) { + continue; + } + + $tagName = $matches[1]; + $value = $this->findAndSortTaggedServices($tagName, $container); + + $definition->replaceArgument($index, $value); + } + } + + private function findAndSortTaggedServices(string $tagName, ContainerBuilder $container): array + { + $services = []; + + foreach ($container->findTaggedServiceIds($tagName, true) as $serviceId => $attributes) { + $priority = $attributes[0]['priority'] ?? 0; + $services[$priority][] = new Reference($serviceId); + } + + if ($services) { + krsort($services); + $services = array_merge(...$services); + } + + return $services; + } +} diff --git a/modules/inpostizi/src/DependencyInjection/ContainerBuilder.php b/modules/inpostizi/src/DependencyInjection/ContainerBuilder.php new file mode 100644 index 00000000..ac00457b --- /dev/null +++ b/modules/inpostizi/src/DependencyInjection/ContainerBuilder.php @@ -0,0 +1,37 @@ +getValue(); + + return function () use ($reference) { + return $this->resolveServices($reference); + }; + } + + return parent::resolveServices($value); + } + + public function compile(bool $resolveEnvPlaceholders = false): void + { + parent::compile(...func_get_args()); + $this->compiled = true; + } + + public function isCompiled(): bool + { + return $this->compiled; + } +} diff --git a/modules/inpostizi/src/DependencyInjection/ContainerFactory.php b/modules/inpostizi/src/DependencyInjection/ContainerFactory.php new file mode 100644 index 00000000..55dd28bc --- /dev/null +++ b/modules/inpostizi/src/DependencyInjection/ContainerFactory.php @@ -0,0 +1,116 @@ +cacheDir = rtrim($cacheDir, '/') . '/'; + $this->hookDispatcher = $hookDispatcher ?? new HookDispatcher(); + } + + /** + * @template T of ContainerInterface + * + * @param class-string $className + * + * @return T + */ + public function create(string $className, iterable $resources): ContainerInterface + { + [$namespace, $shortName] = $this->resolveClassName($className); + $cachePath = sprintf('%s%s.php', $this->cacheDir, $shortName); + $cache = new ConfigCache($cachePath, _PS_MODE_DEV_); + + if (!$cache->isFresh()) { + $container = $this->buildContainer($resources); + + $this->dumpContainer($container, $cache, $namespace, $shortName); + } + + require_once $cachePath; + + return new $className(); + } + + private function resolveClassName(string $className): array + { + if (false === $pos = strrpos($className, '\\')) { + return ['', $className]; + } + + return [ + substr($className, 0, $pos), + substr($className, $pos + 1), + ]; + } + + private function buildContainer(iterable $resources): ContainerBuilder + { + $container = new ContainerBuilder(); + + $container->addResource(new FileResource(__FILE__)); + + $container->setParameter('kernel.root_dir', _PS_ROOT_DIR_ . '/app'); + $container->setParameter('kernel.project_dir', _PS_ROOT_DIR_); + $container->setParameter('kernel.debug', _PS_MODE_DEV_); + $container->setParameter('kernel.environment', _PS_MODE_DEV_ ? 'dev' : 'prod'); + + $fileLocator = new FileLocator(); + $loader = new DelegatingLoader(new LoaderResolver([ + new YamlFileLoader($container, $fileLocator), + new XmlFileLoader($container, $fileLocator), + new PhpFileLoader($container, $fileLocator), + new ClosureLoader($container), + ])); + + foreach ($resources as $resource) { + $loader->load($resource); + } + + $this->hookDispatcher->dispatch('actionInPostIziBuildContainer', [ + 'loader' => $loader, + ]); + + $container->isCompiled() || $container->compile(false); + + return $container; + } + + private function dumpContainer(ContainerBuilder $container, ConfigCacheInterface $cache, string $namespace, string $className): void + { + $dumper = new PhpDumper($container); + $cache->write( + $dumper->dump([ + 'namespace' => $namespace, + 'class' => $className, + 'debug' => false, + ]), + $container->getResources() + ); + } +} diff --git a/modules/inpostizi/src/DependencyInjection/Dumper/PhpDumper.php b/modules/inpostizi/src/DependencyInjection/Dumper/PhpDumper.php new file mode 100644 index 00000000..372fd0f9 --- /dev/null +++ b/modules/inpostizi/src/DependencyInjection/Dumper/PhpDumper.php @@ -0,0 +1,50 @@ +getValue(); + $code = $this->dumpValue($reference); + + $returnedType = ''; + if ($reference instanceof TypedReference) { + $returnedType = sprintf(': %s\%s', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE >= $reference->getInvalidBehavior() ? '' : '?', $reference->getType()); + } + + $code = sprintf('return %s;', $code); + + return sprintf("function ()%s {\n %s\n }", $returnedType, $code); + } + + protected function dumpValue($value, bool $interpolate = true): string + { + if (!isset($this->parentMethod)) { + $this->parentMethod = \Closure::bind(function ($value, $interpolate) { + return $this->dumpValue($value, $interpolate); + }, $this, BaseDumper::class); + } + + return ($this->parentMethod)($value, $interpolate); + } +} diff --git a/modules/inpostizi/src/DependencyInjection/Exception/ContainerNotFoundException.php b/modules/inpostizi/src/DependencyInjection/Exception/ContainerNotFoundException.php new file mode 100644 index 00000000..279af461 --- /dev/null +++ b/modules/inpostizi/src/DependencyInjection/Exception/ContainerNotFoundException.php @@ -0,0 +1,13 @@ +type = $type; + + parent::__construct($id, $invalidBehavior); + } + + public function getType(): string + { + return $this->type; + } +} diff --git a/modules/inpostizi/src/Entities/BasketInterface.php b/modules/inpostizi/src/Entities/BasketInterface.php new file mode 100644 index 00000000..d993d6ab --- /dev/null +++ b/modules/inpostizi/src/Entities/BasketInterface.php @@ -0,0 +1,23 @@ + + */ +final class BasketSession implements SwitchableBasketSessionInterface +{ + /** + * @var InPostIziBasketSession + */ + private $model; + + /** + * @var BasketInterface<\Cart> + */ + private $cart; + + /** + * @var BindingConfirmation|null + */ + private $confirmation; + + /** + * @var CreateOrderRequest|null + */ + private $orderRequest; + + /** + * @var callable(): BindingConfirmation|null + */ + private $confirmationFactory; + + /** + * @var callable(): CreateOrderRequest|null + */ + private $orderRequestFactory; + + private $basketSwitched = false; + + private function __construct(BasketInterface $cart, InPostIziBasketSession $model) + { + $this->cart = $cart; + $this->model = $model; + } + + /** + * @param BasketInterface<\Cart> $basket + */ + public static function new(BasketInterface $basket, Uuid $uuid): self + { + if (self::isBasketFinalized($basket)) { + throw new \DomainException(sprintf('Basket %s was already finalized.', $basket->getId())); + } + + $model = new InPostIziBasketSession(); + $model->session_id = (int) $basket->getId(); + $model->cart_id = (string) $uuid; + $model->id_shop = (int) $basket->getEntity()->id_shop; + + return new self($basket, $model); + } + + /** + * @param BasketInterface<\Cart> $basket + */ + public static function existing(InPostIziBasketSession $model, BasketInterface $basket, SerializerInterface $serializer): self + { + $session = new self($basket, $model); + + $session->confirmationFactory = \Closure::bind(function () use ($serializer) { + if (null === $this->model->confirmation_response) { + return null; + } + + $value = base64_decode($this->model->confirmation_response); + + return $serializer->deserialize($value, BindingConfirmation::class, 'json'); + }, $session); + + $session->orderRequestFactory = \Closure::bind(function () use ($serializer) { + if (null === $this->model->order_details) { + return null; + } + + $value = base64_decode($this->model->order_details); + + return $serializer->deserialize($value, CreateOrderRequest::class, 'json'); + }, $session); + + return $session; + } + + /** + * @internal + */ + public static function isFinalized(BasketSessionInterface $session): bool + { + if (null !== $session->getOrderId()) { + return true; + } + + return self::isBasketFinalized($session->getBasket()); + } + + public function getBasketId(): string + { + return $this->model->cart_id; + } + + public function isBasketBound(): bool + { + $confirmation = $this->getBindingConfirmation(); + + return null !== $confirmation && BindingStatus::Success() === $confirmation->getStatus(); + } + + public function getBindingConfirmation(): ?BindingConfirmation + { + if (null === $this->confirmation && null !== $this->confirmationFactory) { + $this->confirmation = ($this->confirmationFactory)(); + } + + return $this->confirmation; + } + + public function setBindingConfirmation(BindingConfirmation $confirmation): void + { + $this->confirmation = $confirmation; + $this->model->confirmation_response = base64_encode(json_encode($confirmation)); + } + + public function unbind(): void + { + $this->confirmation = null; + $this->model->confirmation_response = null; + } + + public function updatedBy(BasketEvent $event): void + { + $this->model->coupons = true; + } + + public function wasUpdated(): bool + { + $updated = (bool) $this->model->coupons; + $this->model->coupons = false; + + return $updated; + } + + public function getOrderId(): ?string + { + if (0 >= $orderId = (int) $this->model->order_id) { + return null; + } + + return (string) $orderId; + } + + public function getOrderConfirmationUrl(): ?string + { + return $this->model->redirect_url; + } + + public function finalize(string $orderId, string $orderConfirmationUrl, CreateOrderRequest $request): void + { + $this->orderRequest = $request; + + $this->model->order_id = (int) $orderId; + $this->model->redirect_url = $orderConfirmationUrl; + $this->model->order_details = base64_encode(json_encode($request)); + } + + public function wasUserRedirected(): bool + { + return $this->model->redirected; + } + + public function redirect(): void + { + $this->model->redirected = true; + } + + /** + * @return BasketInterface<\Cart> + */ + public function getBasket(): BasketInterface + { + return $this->cart; + } + + public function getBindingApiKey(): ?string + { + return $this->model->binding_api_key; + } + + public function setBindingApiKey(?string $key): void + { + $this->model->binding_api_key = $key; + } + + /** + * @param BasketInterface<\Cart> $basket + */ + public function switchBasket(BasketInterface $basket): void + { + if (null !== $this->model->order_id) { + throw new \DomainException('Cannot switch basket for finalized order.'); + } + + $this->cart = $basket; + $this->model->session_id = (int) $basket->getId(); + $this->basketSwitched = true; + } + + public function wasBasketSwitched(): bool + { + return $this->basketSwitched; + } + + /** + * @internal + */ + public function getModel(): InPostIziBasketSession + { + return $this->model; + } + + /** + * @internal + */ + public function getOrderRequest(): ?CreateOrderRequest + { + if (null === $this->orderRequest && null !== $this->orderRequestFactory) { + $this->orderRequest = ($this->orderRequestFactory)(); + } + + return $this->orderRequest; + } + + public function getShopId(): ?int + { + return $this->model->id_shop === null ? null : (int) $this->model->id_shop; + } + + public function setShopId(int $idShop) + { + $this->model->id_shop = $idShop; + } + + private static function isBasketFinalized(BasketInterface $basket): bool + { + if (is_callable([$basket, 'isFinalized'])) { + return $basket->isFinalized(); + } + + @trigger_error(sprintf('Not implementing "isFinalized()" in "%s" is deprecated since 2.1.0.', get_class($basket)), E_USER_DEPRECATED); + + return false; + } +} diff --git a/modules/inpostizi/src/Entities/BasketSessionInterface.php b/modules/inpostizi/src/Entities/BasketSessionInterface.php new file mode 100644 index 00000000..57c5aef8 --- /dev/null +++ b/modules/inpostizi/src/Entities/BasketSessionInterface.php @@ -0,0 +1,48 @@ + + */ +final class Cart implements BasketInterface +{ + private $cart; + + public function __construct(\Cart $cart) + { + $this->cart = $cart; + } + + public function getId(): int + { + return (int) $this->cart->id; + } + + public function getEntity(): \Cart + { + return $this->cart; + } + + public function isFinalized(): bool + { + return $this->cart->orderExists(); + } +} diff --git a/modules/inpostizi/src/Entities/CartProxy.php b/modules/inpostizi/src/Entities/CartProxy.php new file mode 100644 index 00000000..a8cc706f --- /dev/null +++ b/modules/inpostizi/src/Entities/CartProxy.php @@ -0,0 +1,56 @@ + + */ +final class CartProxy implements BasketInterface +{ + /** + * @var int + */ + private $cartId; + + /** + * @var ObjectManagerInterface + */ + private $manager; + + /** + * @var \Cart|null + */ + private $cart; + + public function __construct(int $cartId, ObjectManagerInterface $manager) + { + $this->cartId = $cartId; + $this->manager = $manager; + } + + public function getId(): int + { + return $this->cartId; + } + + public function getEntity(): \Cart + { + return $this->cart ?? ($this->cart = $this->getCart()); + } + + public function isFinalized(): bool + { + return $this->getEntity()->orderExists(); + } + + private function getCart(): \Cart + { + return $this->manager + ->getRepository(\Cart::class) + ->find($this->cartId); + } +} diff --git a/modules/inpostizi/src/Entities/SwitchableBasketSessionInterface.php b/modules/inpostizi/src/Entities/SwitchableBasketSessionInterface.php new file mode 100644 index 00000000..41d87972 --- /dev/null +++ b/modules/inpostizi/src/Entities/SwitchableBasketSessionInterface.php @@ -0,0 +1,17 @@ + + */ +interface SwitchableBasketSessionInterface extends BasketSessionInterface +{ + public function switchBasket(BasketInterface $basket); + + public function wasBasketSwitched(): bool; +} diff --git a/modules/inpostizi/src/Enum/Enum.php b/modules/inpostizi/src/Enum/Enum.php new file mode 100644 index 00000000..e2414ed4 --- /dev/null +++ b/modules/inpostizi/src/Enum/Enum.php @@ -0,0 +1,175 @@ +_name = $name; + $this->_value = $value; + } + + /** + * @return static + */ + final public static function __callStatic(string $name, array $arguments) + { + $cases = self::casesByName(); + + if (!isset($cases[$name])) { + throw new \DomainException(sprintf('Case "%s" does not exist in "%s".', $name, static::class)); + } + + return $cases[$name]; + } + + final public static function compareValues(self $e1, self $e2): int + { + return $e1->_value <=> $e2->_value; + } + + /** + * @return static[] + */ + final public static function cases(): array + { + return array_values(static::casesByName()); + } + + /** + * @return string|T|null + */ + final public function __get(string $property) + { + switch ($property) { + case 'name': + return $this->_name; + case 'value': + return $this->_value; + default: + trigger_error(sprintf('Undefined property: %s::$%s', static::class, $property), E_USER_WARNING); + + return null; + } + } + + final public function __set(string $property, $value): void + { + switch ($property) { + case 'name': + case 'value': + throw new \Error(sprintf('Cannot redefine readonly property %s::$%s', static::class, $property)); + default: + throw new \Error(sprintf('Cannot create dynamic property %s::$%s', static::class, $property)); + } + } + + final public function __isset(string $property): bool + { + switch ($property) { + case 'name': + case 'value': + return true; + default: + return false; + } + } + + final public function __unset(string $property): void + { + switch ($property) { + case 'name': + case 'value': + throw new \Error(sprintf('Cannot unset readonly property %s::$%s', static::class, $property)); + default: + break; + } + } + + /** + * @return T + */ + #[\ReturnTypeWillChange] + final public function jsonSerialize() + { + return $this->_value; + } + + /** + * @return array + */ + final protected static function casesByName(): array + { + $class = static::class; + + if (!isset(self::$casesByName[$class])) { + self::init($class); + } + + return self::$casesByName[$class]; + } + + /** + * @return array + */ + final protected static function casesByValue(): array + { + $class = static::class; + + if (!isset(self::$casesByValue[$class])) { + self::init($class); + } + + return self::$casesByValue[$class]; + } + + private static function init(string $class): void + { + self::$casesByName[$class] = self::$casesByValue[$class] = []; + + $reflection = new \ReflectionClass($class); + + foreach ($reflection->getConstants() as $name => $value) { + $name = str_replace(' ', '', ucwords(str_replace('_', ' ', strtolower($name)))); + $case = new static($name, $value); + self::$casesByName[$class][$name] = $case; + self::$casesByValue[$class][$value] = $case; + } + } + + private function __clone() + { + } +} diff --git a/modules/inpostizi/src/Enum/IntEnum.php b/modules/inpostizi/src/Enum/IntEnum.php new file mode 100644 index 00000000..1932e39a --- /dev/null +++ b/modules/inpostizi/src/Enum/IntEnum.php @@ -0,0 +1,34 @@ + + */ +abstract class IntEnum extends Enum +{ + /** + * @return static + */ + final public static function from(?int $value): self + { + $value = (int) $value; + $cases = static::casesByValue(); + + if (!isset($cases[$value])) { + throw new \UnexpectedValueException(sprintf('%d is not a valid backing value for enum "%s"', $value, static::class)); + } + + return $cases[$value]; + } + + /** + * @return static|null + */ + final public static function tryFrom(?int $value): ?self + { + $cases = self::casesByValue(); + + return $cases[(int) $value] ?? null; + } +} diff --git a/modules/inpostizi/src/Enum/StringEnum.php b/modules/inpostizi/src/Enum/StringEnum.php new file mode 100644 index 00000000..6554d4d0 --- /dev/null +++ b/modules/inpostizi/src/Enum/StringEnum.php @@ -0,0 +1,34 @@ + + */ +abstract class StringEnum extends Enum +{ + /** + * @return static + */ + final public static function from(?string $value): self + { + $value = $value ?? '0'; + $cases = static::casesByValue(); + + if (!isset($cases[$value])) { + throw new \UnexpectedValueException(sprintf('"%s" is not a valid backing value for enum "%s"', $value, static::class)); + } + + return $cases[$value]; + } + + /** + * @return static|null + */ + final public static function tryFrom(?string $value): ?self + { + $cases = self::casesByValue(); + + return $cases[$value ?? '0'] ?? null; + } +} diff --git a/modules/inpostizi/src/Environment/AuthServerUriCollection.php b/modules/inpostizi/src/Environment/AuthServerUriCollection.php new file mode 100644 index 00000000..ff39ea1a --- /dev/null +++ b/modules/inpostizi/src/Environment/AuthServerUriCollection.php @@ -0,0 +1,30 @@ +environment = $environment; + } + + public function getAuthorizationEndpointUri(): string + { + throw new \LogicException('Not implemented.'); + } + + public function getTokenEndpointUri(): string + { + return $this->environment->getAuthServerTokenEndpointUri(); + } +} diff --git a/modules/inpostizi/src/Environment/EnvironmentFactory.php b/modules/inpostizi/src/Environment/EnvironmentFactory.php new file mode 100644 index 00000000..f706245d --- /dev/null +++ b/modules/inpostizi/src/Environment/EnvironmentFactory.php @@ -0,0 +1,38 @@ + environments by type name + */ + private static $environments = []; + + public function createEnvironment(EnvironmentType $type): EnvironmentInterface + { + $name = $type->name; + + return self::$environments[$name] ?? (self::$environments[$name] = $this->doCreate($type)); + } + + private function doCreate(EnvironmentType $type): EnvironmentInterface + { + switch ($type) { + case EnvironmentType::Production(): + return new ProductionEnvironment(); + case EnvironmentType::Sandbox(): + return new SandboxEnvironment(); + case EnvironmentType::Uat(): + if (class_exists(UatEnvironment::class)) { + return new UatEnvironment(); + } + + // no break + default: + throw new \LogicException('Unsupported environment type.'); + } + } +} diff --git a/modules/inpostizi/src/Environment/EnvironmentFactoryInterface.php b/modules/inpostizi/src/Environment/EnvironmentFactoryInterface.php new file mode 100644 index 00000000..c960df26 --- /dev/null +++ b/modules/inpostizi/src/Environment/EnvironmentFactoryInterface.php @@ -0,0 +1,10 @@ +dispatcher = $dispatcher; + $this->isLegacyDispatcher = !$this->dispatcher instanceof ContractsEventDispatcherInterface; + } + + /** + * {@inheritDoc} + */ + public function dispatch(Event $event, ?string $eventName = null): Event + { + $eventName = $eventName ?? get_class($event); + + return $this->isLegacyDispatcher + ? $this->dispatcher->dispatch($eventName, $event) + : $this->dispatcher->dispatch($event, $eventName); + } + + public function addListener(string $eventName, callable $listener, int $priority = 0): void + { + $this->dispatcher->addListener($eventName, $listener, $priority); + } + + /** + * Forwards calls to the inner dispatcher. + * + * @return mixed + */ + public function __call(string $name, array $arguments) + { + return $this->dispatcher->$name(...$arguments); + } +} diff --git a/modules/inpostizi/src/Event/CartUpdatedEvent.php b/modules/inpostizi/src/Event/CartUpdatedEvent.php new file mode 100644 index 00000000..e3b48ef5 --- /dev/null +++ b/modules/inpostizi/src/Event/CartUpdatedEvent.php @@ -0,0 +1,23 @@ +cart = $cart; + } + + public function getCart(): \Cart + { + return $this->cart; + } +} diff --git a/modules/inpostizi/src/Event/CreateShipmentRequestEvent.php b/modules/inpostizi/src/Event/CreateShipmentRequestEvent.php new file mode 100644 index 00000000..af3d6042 --- /dev/null +++ b/modules/inpostizi/src/Event/CreateShipmentRequestEvent.php @@ -0,0 +1,35 @@ +controller = $controller; + $this->request = $request; + } + + public function getController(): AdminInPostConfirmedShipmentsController + { + return $this->controller; + } + + public function getRequest(): Request + { + return $this->request; + } +} diff --git a/modules/inpostizi/src/Event/CreateShipmentRequestProcessedEvent.php b/modules/inpostizi/src/Event/CreateShipmentRequestProcessedEvent.php new file mode 100644 index 00000000..511f6616 --- /dev/null +++ b/modules/inpostizi/src/Event/CreateShipmentRequestProcessedEvent.php @@ -0,0 +1,23 @@ +controller = $controller; + } + + public function getController(): AdminInPostConfirmedShipmentsController + { + return $this->controller; + } +} diff --git a/modules/inpostizi/src/Event/Event.php b/modules/inpostizi/src/Event/Event.php new file mode 100644 index 00000000..7494863f --- /dev/null +++ b/modules/inpostizi/src/Event/Event.php @@ -0,0 +1,18 @@ +locator = $locator; + } + + public static function getSubscribedServices(): array + { + return [ + CartListener::class, + OrderListener::class, + ShipmentListener::class, + UpdateHotProductsListener::class, + ReplaceOrderNotificationRecipientListener::class, + UpdateBasketAnalyticsListener::class, + '?' . BasketAppClientProvider::class, + '?' . UpdateCartRulesListener::class, + '?' . CreateShipmentListener::class, + ]; + } + + public function create(): EventDispatcherInterface + { + $dispatcher = new EventDispatcher(); + + foreach (self::getSubscribedServices() as $serviceId) { + $className = '?' === $serviceId[0] ? \Tools::substr($serviceId, 1) : $serviceId; + + $this->addListeners($dispatcher, $className); + } + + return $dispatcher; + } + + /** + * @param class-string $className + */ + private function addListeners(EventDispatcherInterface $dispatcher, string $className): void + { + foreach ($className::getSubscribedEvents() as $eventName => $parameters) { + foreach ($this->normalizeListeners($parameters) as $listener) { + [$method, $priority] = $listener; + + $dispatcher->addListener($eventName, function ($event) use ($className, $method) { + return $this->locator->get($className)->{$method}($event); + }, $priority); + } + } + } + + private function normalizeListeners($parameters): array + { + if (is_string($parameters)) { + return [[$parameters, 0]]; + } + + if (is_string($parameters[0])) { + return [[$parameters[0], $parameters[1] ?? 0]]; + } + + return array_map(static function ($listener) { + return [$listener[0], $listener[1] ?? 0]; + }, $parameters); + } +} diff --git a/modules/inpostizi/src/Event/EventDispatcherInterface.php b/modules/inpostizi/src/Event/EventDispatcherInterface.php new file mode 100644 index 00000000..08af495b --- /dev/null +++ b/modules/inpostizi/src/Event/EventDispatcherInterface.php @@ -0,0 +1,21 @@ +order = $order; + } + + public function getOrder(): \Order + { + return $this->order; + } +} diff --git a/modules/inpostizi/src/Event/OrderStatusUpdatedEvent.php b/modules/inpostizi/src/Event/OrderStatusUpdatedEvent.php new file mode 100644 index 00000000..dcbc8d74 --- /dev/null +++ b/modules/inpostizi/src/Event/OrderStatusUpdatedEvent.php @@ -0,0 +1,34 @@ +orderId = $orderId; + $this->newOrderStatus = $newOrderStatus; + } + + public function getOrderId(): int + { + return $this->orderId; + } + + public function getNewOrderStatus(): \OrderState + { + return $this->newOrderStatus; + } +} diff --git a/modules/inpostizi/src/Event/ShipmentEvent.php b/modules/inpostizi/src/Event/ShipmentEvent.php new file mode 100644 index 00000000..ffcdecd6 --- /dev/null +++ b/modules/inpostizi/src/Event/ShipmentEvent.php @@ -0,0 +1,27 @@ +shipment = $shipment; + } + + public function getShipment(): \InPostShipmentModel + { + return $this->shipment; + } +} diff --git a/modules/inpostizi/src/Event/ValidateOrderEvent.php b/modules/inpostizi/src/Event/ValidateOrderEvent.php new file mode 100644 index 00000000..809a286b --- /dev/null +++ b/modules/inpostizi/src/Event/ValidateOrderEvent.php @@ -0,0 +1,21 @@ +order = $order; + } + + public function getOrder(): \Order + { + return $this->order; + } +} diff --git a/modules/inpostizi/src/EventListener/CartListener.php b/modules/inpostizi/src/EventListener/CartListener.php new file mode 100644 index 00000000..13528670 --- /dev/null +++ b/modules/inpostizi/src/EventListener/CartListener.php @@ -0,0 +1,137 @@ + + */ + private $sessionRepository; + + /** + * @var CommandBusInterface + */ + private $bus; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @var array + */ + private $updatedCartIds = []; + + /** + * @param BasketSessionRepositoryInterface $sessionRepository + */ + public function __construct(ApiConfigurationInterface $configuration, \Context $context, BasketSessionRepositoryInterface $sessionRepository, CommandBusInterface $bus, LoggerInterface $logger) + { + $this->configuration = $configuration; + $this->context = $context; + $this->sessionRepository = $sessionRepository; + $this->bus = $bus; + $this->logger = $logger; + } + + public static function getSubscribedEvents(): array + { + return [ + CartUpdatedEvent::class => 'onCartUpdated', + ]; + } + + public function onCartUpdated(CartUpdatedEvent $event): void + { + if (null === $this->configuration->getClientCredentials()) { + return; + } + + $cart = $event->getCart(); + + if (0 >= $cartId = (int) $cart->id) { + return; + } + + if ([] === $this->updatedCartIds) { + register_shutdown_function(function () { + $this->updateBaskets(); + }); + } + + $this->updatedCartIds[$cartId] = $cartId; + } + + private function updateBaskets(): void + { + foreach ($this->updatedCartIds as $cartId) { + try { + $this->handleCurrentCartChange($cartId); + $command = new UpdateBasketCommand($cartId); + + $this->bus->handle($command); + } catch (\Throwable $throwable) { + $this->logger->critical('Could not send updated cart #{cartId} data: {error}', [ + 'cartId' => $cartId, + 'error' => $throwable, + ]); + } + } + } + + private function handleCurrentCartChange(int $cartId): void + { + if (null !== $this->sessionRepository->findByEntityId($cartId)) { + return; + } + + if (!$this->context->controller instanceof \FrontControllerCore || $cartId !== (int) $this->context->cart->id) { + return; + } + + // the user changed his current cart (e.g. using the reorder option) + + if (null === $currentBasketId = $this->context->cookie->inpostizi_basket_id ?? null) { + return; + } + + if (null === $session = $this->sessionRepository->findByBasketId($currentBasketId)) { + return; + } + + if (null !== $orderId = $session->getOrderId()) { + $this->logger->notice('Order #{orderId}: basket ID was not removed from customer cookie.', [ + 'orderId' => $orderId, + ]); + + unset($this->context->cookie->inpostizi_basket_id); + } else { + $session->switchBasket(new Cart($this->context->cart)); + $this->sessionRepository->persist($session); + } + } +} diff --git a/modules/inpostizi/src/EventListener/CreateShipmentListener.php b/modules/inpostizi/src/EventListener/CreateShipmentListener.php new file mode 100644 index 00000000..2d36ac2a --- /dev/null +++ b/modules/inpostizi/src/EventListener/CreateShipmentListener.php @@ -0,0 +1,87 @@ +orderDataRepository = $orderDataRepository; + $this->translator = $translator; + } + + public static function getSubscribedEvents(): array + { + return [ + CreateShipmentRequestEvent::class => 'onCreateShipmentRequest', + CreateShipmentRequestProcessedEvent::class => 'onShipmentRequestProcessed', + ]; + } + + public function onCreateShipmentRequest(CreateShipmentRequestEvent $event): void + { + $idOrder = $event->getRequest()->get('id_order'); + $email = $event->getRequest()->get('email'); + if (!$this->isValidParams($idOrder, $email)) { + return; + } + + if (null === $orderData = $this->orderDataRepository->getOrderData($idOrder)) { + return; + } + + if (null !== $orderData->getDelivery()->getEmail() && $email !== $orderData->getDelivery()->getEmail()) { + $this->validMail = $orderData->getDelivery()->getEmail(); + $this->removeService(); + } + } + + private function isValidParams($idOrder, $email) + { + return !empty($idOrder) && !empty($email); + } + + private function removeService() + { + unset($_POST['service']); + $this->processed = true; + } + + public function onShipmentRequestProcessed(CreateShipmentRequestProcessedEvent $event) + { + if (!$this->processed) { + return; + } + $event->getController()->errors = [sprintf($this->translator->l('In order for the shipment to be processed correctly by InPost Pay, the email address must match the one received when creating the order (%s).', self::TRANSLATION_SOURCE), $this->validMail)]; + $this->processed = false; + $this->validMail = null; + } +} diff --git a/modules/inpostizi/src/EventListener/OrderListener.php b/modules/inpostizi/src/EventListener/OrderListener.php new file mode 100644 index 00000000..5dc88655 --- /dev/null +++ b/modules/inpostizi/src/EventListener/OrderListener.php @@ -0,0 +1,161 @@ + + */ + private $repository; + + /** + * @var CommandBusInterface + */ + private $bus; + + /** + * @var LoggerInterface + */ + private $logger; + + private $ordersAddressUpdated = []; + + /** + * @param ObjectRepositoryInterface<\Order> $repository + */ + public function __construct(ApiConfigurationInterface $configuration, ObjectRepositoryInterface $repository, CommandBusInterface $bus, LoggerInterface $logger) + { + $this->configuration = $configuration; + $this->repository = $repository; + $this->bus = $bus; + $this->logger = $logger; + } + + public static function getSubscribedEvents(): array + { + return [ + OrderStatusUpdatedEvent::class => 'onOrderStatusUpdated', + OrderEvent::BEFORE_UPDATE => 'onBeforeOrderAddressDeliveryUpdated', + OrderEvent::UPDATED => 'onOrderAddressDeliveryUpdated', + ]; + } + + public function onOrderStatusUpdated(OrderStatusUpdatedEvent $event): void + { + if (null === $this->configuration->getClientCredentials()) { + return; + } + + if (0 >= $orderId = $event->getOrderId()) { + return; + } + + if (null === $order = $this->repository->find($orderId)) { + return; + } + + if ('inpostizi' !== $order->module) { + return; + } + + $eventTime = \DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $order->date_upd); + $command = new UpdateOrderStatusCommand((string) $event->getOrderId(), $eventTime, $this->getMerchantOrderStatus($order, $event->getNewOrderStatus())); + + try { + $this->bus->handle($command); + } catch (\Throwable $e) { + $this->logger->critical('Could not send order #{orderId} status update event data: {error}', [ + 'orderId' => $orderId, + 'error' => $e, + 'orderStatusId' => (int) $event->getNewOrderStatus()->id, + ]); + } + } + + public function onBeforeOrderAddressDeliveryUpdated(OrderEvent $event): void + { + if (null === $this->configuration->getClientCredentials()) { + return; + } + + $order = $event->getOrder(); + + if ('inpostizi' !== $order->module) { + return; + } + + if (0 >= $orderId = (int) $order->id) { + return; + } + + if (isset($this->ordersAddressUpdated[$orderId])) { + return; + } + + if (null === $currentOrder = $this->repository->find($orderId)) { + return; + } + + if ((int) $currentOrder->id_address_delivery === (int) $order->id_address_delivery) { + return; + } + + $this->ordersAddressUpdated[$order->id] = true; + } + + public function onOrderAddressDeliveryUpdated(OrderEvent $event): void + { + $order = $event->getOrder(); + $orderId = (int) $event->getOrder()->id; + + if (!isset($this->ordersAddressUpdated[$orderId])) { + return; + } + + unset($this->ordersAddressUpdated[$orderId]); + + $eventTime = \DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $order->date_upd); + $command = new UpdateOrderAddressDeliveryCommand((string) $orderId, $eventTime); + + try { + $this->bus->handle($command); + } catch (\Throwable $e) { + $this->logger->critical('Could not send order #{orderId} address delivery update event data: {error}', [ + 'orderId' => $orderId, + 'error' => $e, + ]); + } + } + + private function getMerchantOrderStatus(\Order $order, \OrderState $orderState): ?MerchantOrderStatus + { + if ($order->getOrderPayments()) { + return null; + } + + if ($orderState->id === (int) \Configuration::get('PS_OS_CANCELED')) { + return MerchantOrderStatus::OrderRejected(); + } + + return null; + } +} diff --git a/modules/inpostizi/src/EventListener/ShipmentListener.php b/modules/inpostizi/src/EventListener/ShipmentListener.php new file mode 100644 index 00000000..d393640f --- /dev/null +++ b/modules/inpostizi/src/EventListener/ShipmentListener.php @@ -0,0 +1,127 @@ + + */ + private $repository; + + /** + * @var CommandBusInterface + */ + private $bus; + + private $trackingNumberUpdated = []; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @param ObjectRepositoryInterface<\InPostShipmentModel> $repository + */ + public function __construct(ApiConfigurationInterface $configuration, ObjectRepositoryInterface $repository, CommandBusInterface $bus, ?LoggerInterface $logger = null) + { + $this->configuration = $configuration; + $this->repository = $repository; + $this->bus = $bus; + $this->logger = $logger ?? new NullLogger(); + } + + public static function getSubscribedEvents(): array + { + return [ + ShipmentEvent::CREATED => 'onShipmentCreated', + ShipmentEvent::BEFORE_UPDATE => 'onBeforeShipmentUpdated', + ShipmentEvent::UPDATED => 'onShipmentUpdated', + ]; + } + + public function onShipmentCreated(ShipmentEvent $event): void + { + if (null === $this->configuration->getClientCredentials()) { + return; + } + + $shipment = $event->getShipment(); + + if ('' === $shipment->tracking_number || null === $shipment->tracking_number) { + return; + } + + $this->onTrackingNumberUpdated($shipment); + } + + public function onBeforeShipmentUpdated(ShipmentEvent $event): void + { + if (null === $this->configuration->getClientCredentials()) { + return; + } + + $shipment = $event->getShipment(); + + if ('' === $shipment->tracking_number || null === $shipment->tracking_number || 0 >= $shipmentId = (int) $shipment->id) { + return; + } + + if (null === $currentState = $this->repository->find($shipmentId)) { + return; + } + + if ($currentState->tracking_number === $shipment->tracking_number) { + return; + } + + $this->trackingNumberUpdated[$shipmentId] = true; + } + + public function onShipmentUpdated(ShipmentEvent $event): void + { + $shipment = $event->getShipment(); + $shipmentId = (int) $shipment->id; + + if (!isset($this->trackingNumberUpdated[$shipmentId])) { + return; + } + + unset($this->trackingNumberUpdated[$shipmentId]); + + $this->onTrackingNumberUpdated($shipment); + } + + private function onTrackingNumberUpdated(\InPostShipmentModel $shipment): void + { + $eventTime = \DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $shipment->date_add); + $command = new UpdateOrderTrackingNumbersCommand((string) $shipment->id_order, $eventTime); + + try { + $this->bus->handle($command); + } catch (\Throwable $e) { + $this->logger->critical('Could not send order #{orderId} tracking numbers update event data: {error}', [ + 'orderId' => (int) $shipment->id_order, + 'error' => $e, + 'shipmentId' => (int) $shipment->id, + ]); + } + } +} diff --git a/modules/inpostizi/src/Form/BasketAppClientProvider.php b/modules/inpostizi/src/Form/BasketAppClientProvider.php new file mode 100644 index 00000000..e6aaadbd --- /dev/null +++ b/modules/inpostizi/src/Form/BasketAppClientProvider.php @@ -0,0 +1,76 @@ +configuration = $configuration; + $this->clientFactory = $clientFactory; + $this->client = $client; + } + + public static function getSubscribedEvents(): array + { + return [ + ApiConfigurationValidatedEvent::class => 'onApiConfigurationValidated', + ]; + } + + /** + * @return (PaymentsApiClientInterface&BasketAppClientInterface)|null + */ + public function getClient(): ?BasketAppClientInterface + { + if (null === $this->configuration->getClientCredentials()) { + return null; + } + + return $this->client ?? ($this->client = $this->clientFactory->create($this->configuration)); + } + + public function onApiConfigurationValidated(ApiConfigurationValidatedEvent $event): void + { + $newConfiguration = $event->getConfiguration(); + + if ($this->doesConfigurationChange($newConfiguration)) { + $this->client = null; + } + + $this->configuration = $newConfiguration; + } + + private function doesConfigurationChange(ApiConfigurationInterface $newConfiguration): bool + { + return $this->configuration->getClientCredentials() !== $newConfiguration->getClientCredentials() + || $this->configuration->getEnvironment() !== $newConfiguration->getEnvironment(); + } +} diff --git a/modules/inpostizi/src/Form/ChoiceList/AvailablePaymentOptionChoiceLoader.php b/modules/inpostizi/src/Form/ChoiceList/AvailablePaymentOptionChoiceLoader.php new file mode 100644 index 00000000..0006408c --- /dev/null +++ b/modules/inpostizi/src/Form/ChoiceList/AvailablePaymentOptionChoiceLoader.php @@ -0,0 +1,81 @@ +clientProvider = $clientProvider; + } + + public function loadChoiceList($value = null): ChoiceListInterface + { + return new ArrayChoiceList($this->getChoices(), $value); + } + + public function loadChoicesForValues(array $values, $value = null): array + { + return $this->loadChoiceList($value)->getChoicesForValues($values); + } + + public function loadValuesForChoices(array $choices, $value = null): array + { + return $this->loadChoiceList($value)->getValuesForChoices($choices); + } + + private function getChoices(): iterable + { + $client = $this->clientProvider->getClient(); + + if (isset($this->choices) && $this->client === $client) { + return $this->choices; + } + + $this->client = $client; + + if (null === $this->client) { + return $this->choices = PaymentType::cases(); + } + + try { + $availableOptions = $this->client->getAvailablePaymentOptions(); + } catch (\Exception $e) { + return $this->choices = PaymentType::cases(); + } + + $choices = $availableOptions->getPaymentTypes(); + + usort($choices, static function (PaymentType $type1, PaymentType $type2): int { + $allTypes = PaymentType::cases(); + + $index1 = array_search($type1, $allTypes, true); + $index2 = array_search($type2, $allTypes, true); + + return $index1 <=> $index2; + }); + + return $this->choices = $choices; + } +} diff --git a/modules/inpostizi/src/Form/ChoiceList/CarrierChoiceLoader.php b/modules/inpostizi/src/Form/ChoiceList/CarrierChoiceLoader.php new file mode 100644 index 00000000..922b7d1b --- /dev/null +++ b/modules/inpostizi/src/Form/ChoiceList/CarrierChoiceLoader.php @@ -0,0 +1,75 @@ + + */ + private $repository; + + private $choices; + + /** + * @param ObjectRepositoryInterface<\Carrier> $repository + */ + public function __construct(ObjectRepositoryInterface $repository) + { + $this->repository = $repository; + } + + /** + * {@inheritDoc} + */ + public function loadChoiceList($value = null): ChoiceListInterface + { + return new ArrayChoiceList($this->getChoices(), $value); + } + + /** + * {@inheritDoc} + */ + public function loadChoicesForValues(array $values, $value = null): array + { + return $this->loadChoiceList($value)->getChoicesForValues($values); + } + + /** + * {@inheritDoc} + */ + public function loadValuesForChoices(array $choices, $value = null): array + { + $choices = array_map(function ($choice): ?\Carrier { + if ($choice instanceof \Carrier) { + return $choice; + } + + return $this->getChoices()[$choice] ?? null; + }, $choices); + + return $this->loadChoiceList($value)->getValuesForChoices($choices); + } + + private function getChoices(): iterable + { + if (isset($this->choices)) { + return $this->choices; + } + + $this->choices = []; + + foreach ($this->repository->findBy(['deleted' => 0]) as $carrier) { + $this->choices[$carrier->id_reference] = $carrier; + } + + return $this->choices; + } +} diff --git a/modules/inpostizi/src/Form/ChoiceList/LazyChoiceLoader.php b/modules/inpostizi/src/Form/ChoiceList/LazyChoiceLoader.php new file mode 100644 index 00000000..ab52d024 --- /dev/null +++ b/modules/inpostizi/src/Form/ChoiceList/LazyChoiceLoader.php @@ -0,0 +1,65 @@ + + * + * @see \Symfony\Component\Form\ChoiceList\Loader\LazyChoiceLoader + */ +final class LazyChoiceLoader implements ChoiceLoaderInterface +{ + /** + * @var ChoiceLoaderInterface + */ + private $loader; + + /** + * @var ChoiceListInterface|null + */ + private $choiceList; + + public function __construct(ChoiceLoaderInterface $loader) + { + $this->loader = $loader; + } + + /** + * @param callable|null $value + */ + public function loadChoiceList($value = null): ChoiceListInterface + { + return $this->choiceList ?? $this->choiceList = new ArrayChoiceList([], $value); + } + + /** + * @param callable|null $value + */ + public function loadChoicesForValues(array $values, $value = null): array + { + $choices = $this->loader->loadChoicesForValues($values, $value); + $this->choiceList = new ArrayChoiceList($choices, $value); + + return $choices; + } + + /** + * @param callable|null $value + */ + public function loadValuesForChoices(array $choices, $value = null): array + { + $values = $this->loader->loadValuesForChoices($choices, $value); + + if (null === $this->choiceList || $this->choiceList->getValuesForChoices($choices) !== $values) { + $this->loadChoicesForValues($values, $value); + } + + return $values; + } +} diff --git a/modules/inpostizi/src/Form/ChoiceList/ObjectModelChoiceLoader.php b/modules/inpostizi/src/Form/ChoiceList/ObjectModelChoiceLoader.php new file mode 100644 index 00000000..596929a8 --- /dev/null +++ b/modules/inpostizi/src/Form/ChoiceList/ObjectModelChoiceLoader.php @@ -0,0 +1,120 @@ + + */ + private $class; + + /** + * @var QueryBuilder|null + */ + private $queryBuilder; + + /** + * @var int|null + */ + private $languageId; + + /** + * @var int|null + */ + private $shopId; + + /** + * @var T[] + */ + private $choices; + + /** + * @param class-string $class + * @param QueryBuilder|null $queryBuilder + */ + public function __construct(ObjectManagerInterface $manager, string $class, ?QueryBuilder $queryBuilder = null, ?int $languageId = null, ?int $shopId = null) + { + $this->manager = $manager; + $this->class = $class; + $this->queryBuilder = $queryBuilder; + $this->languageId = $languageId; + $this->shopId = $shopId; + } + + public function loadChoiceList($value = null): ChoiceListInterface + { + if (!isset($this->choices)) { + $this->choices = $this->getChoices(); + } + + return new ArrayChoiceList($this->choices, $value); + } + + public function loadValuesForChoices(array $choices, $value = null): array + { + if ([] === $choices) { + return []; + } + + if (null !== $value || isset($this->choices)) { + return $this->loadChoiceList($value)->getValuesForChoices($choices); + } + + $values = []; + + foreach ($choices as $i => $choice) { + if ($choice instanceof $this->class) { + $values[$i] = (string) $choice->id; + } elseif (is_int($choice)) { + $values[$i] = (string) $choice; + } + } + + return $values; + } + + public function loadChoicesForValues(array $values, $value = null): array + { + if ([] === $values) { + return []; + } + + if (null !== $value || isset($this->choices) || null !== $this->queryBuilder) { + return $this->loadChoiceList($value)->getChoicesForValues($values); + } + + return $this->findByIdsMaintainingOrder( + $this->class, + $values, + $this->languageId, + $this->shopId + ); + } + + private function getChoices(): array + { + return $this->queryBuilder + ? $this->queryBuilder + ->setLanguageId($this->languageId) + ->build() + ->getResult() + : $this->manager + ->getRepository($this->class) + ->findAll($this->languageId, $this->shopId); + } +} diff --git a/modules/inpostizi/src/Form/ChoiceList/OrderStateChoiceLoader.php b/modules/inpostizi/src/Form/ChoiceList/OrderStateChoiceLoader.php new file mode 100644 index 00000000..d1b8c510 --- /dev/null +++ b/modules/inpostizi/src/Form/ChoiceList/OrderStateChoiceLoader.php @@ -0,0 +1,75 @@ + $repository + */ + public function __construct(ObjectRepositoryInterface $repository) + { + $this->repository = $repository; + } + + /** + * {@inheritDoc} + */ + public function loadChoiceList($value = null): ChoiceListInterface + { + return new ArrayChoiceList($this->getChoices(), $value); + } + + /** + * {@inheritDoc} + */ + public function loadChoicesForValues(array $values, $value = null): array + { + return $this->loadChoiceList($value)->getChoicesForValues($values); + } + + /** + * {@inheritDoc} + */ + public function loadValuesForChoices(array $choices, $value = null): array + { + $choices = array_map(function ($choice): ?\OrderState { + if ($choice instanceof \OrderState) { + return $choice; + } + + return $this->getChoices()[$choice] ?? null; + }, $choices); + + return $this->loadChoiceList($value)->getValuesForChoices($choices); + } + + private function getChoices(): iterable + { + if (isset($this->choices)) { + return $this->choices; + } + + $this->choices = []; + + foreach ($this->repository->findAll() as $orderState) { + $this->choices[$orderState->id] = $orderState; + } + + return $this->choices; + } +} diff --git a/modules/inpostizi/src/Form/ChoiceList/ProductImageTypeChoiceLoader.php b/modules/inpostizi/src/Form/ChoiceList/ProductImageTypeChoiceLoader.php new file mode 100644 index 00000000..0d79c5ad --- /dev/null +++ b/modules/inpostizi/src/Form/ChoiceList/ProductImageTypeChoiceLoader.php @@ -0,0 +1,75 @@ + $repository + */ + public function __construct(ObjectRepositoryInterface $repository) + { + $this->repository = $repository; + } + + /** + * {@inheritDoc} + */ + public function loadChoiceList($value = null): ChoiceListInterface + { + return new ArrayChoiceList($this->getChoices(), $value); + } + + /** + * {@inheritDoc} + */ + public function loadChoicesForValues(array $values, $value = null): array + { + return $this->loadChoiceList($value)->getChoicesForValues($values); + } + + /** + * {@inheritDoc} + */ + public function loadValuesForChoices(array $choices, $value = null): array + { + $choices = array_map(function ($choice): ?\ImageType { + if ($choice instanceof \ImageType) { + return $choice; + } + + return $this->getChoices()[$choice] ?? null; + }, $choices); + + return $this->loadChoiceList($value)->getValuesForChoices($choices); + } + + private function getChoices(): iterable + { + if (isset($this->choices)) { + return $this->choices; + } + + $this->choices = []; + + foreach ($this->repository->getProductImageTypes() as $imageType) { + $this->choices[$imageType->id] = $imageType; + } + + return $this->choices; + } +} diff --git a/modules/inpostizi/src/Form/DataMapper/ClientCredentialsDataMapper.php b/modules/inpostizi/src/Form/DataMapper/ClientCredentialsDataMapper.php new file mode 100644 index 00000000..8bd924f3 --- /dev/null +++ b/modules/inpostizi/src/Form/DataMapper/ClientCredentialsDataMapper.php @@ -0,0 +1,76 @@ +setData($viewData->getClientId()); + $forms['clientSecret']->setData($viewData->getClientSecret()); + } + + /** + * @param ClientCredentialsInterface|null $viewData + */ + public function mapFormsToData($forms, &$viewData): void + { + if ($forms instanceof \Traversable) { + $forms = iterator_to_array($forms); + } + + $clientId = $forms['clientId']->getData(); + $clientSecret = $forms['clientSecret']->getData(); + + if ($this->areSameCredentials($viewData, $clientId, $clientSecret)) { + return; + } + + if (null === $clientId && null === $clientSecret) { + $viewData = null; + } else { + $viewData = new ClientCredentials($clientId ?? '', $clientSecret ?? ''); + } + } + + private function areSameCredentials($credentials, $clientId, $clientSecret): bool + { + if (null === $credentials) { + return false; + } + + if (!$credentials instanceof ClientCredentialsInterface) { + throw new UnexpectedTypeException($credentials, ClientCredentialsInterface::class); + } + + if ($clientId !== $credentials->getClientId()) { + return false; + } + + return $clientSecret === $credentials->getClientSecret() + || MaskedPasswordType::MASKED_VALUE === $clientSecret; + } +} diff --git a/modules/inpostizi/src/Form/DataTransformer/CombinationToAttributeIdsTransformer.php b/modules/inpostizi/src/Form/DataTransformer/CombinationToAttributeIdsTransformer.php new file mode 100644 index 00000000..1c0a30de --- /dev/null +++ b/modules/inpostizi/src/Form/DataTransformer/CombinationToAttributeIdsTransformer.php @@ -0,0 +1,81 @@ +repository = $repository; + $this->productId = $productId; + } + + /** + * @param \Combination|null $value + * + * @return int[]|null + */ + public function transform($value): ?array + { + if (null === $value) { + return null; + } + + if (!$value instanceof \Combination) { + throw new TransformationFailedException(sprintf('Expected an instance of %s.', \Combination::class)); + } + + if ($this->productId !== (int) $this->productId) { + throw new TransformationFailedException(sprintf('Expected a combination of product #%d.', $this->productId)); + } + + $attributes = $this->repository->getSimpleAttributesByCombinationId((int) $value->id); + + return array_map(static function ($attribute): int { + return $attribute->id; + }, $attributes); + } + + /** + * @param int[]|null $value + */ + public function reverseTransform($value): ?\Combination + { + if (null === $value) { + return null; + } + + if (!is_array($value)) { + throw new TransformationFailedException('Expected an array.'); + } + + $attributeIds = array_map('intval', $value); + $combination = $this->repository->findByProductAndAttributeIds($this->productId, ...$attributeIds); + + if (null === $combination) { + throw new TransformationFailedException('Combination does not exist.'); + } + + return $combination; + } +} diff --git a/modules/inpostizi/src/Form/DataTransformer/DateTimeImmutableToDateTimeTransformer.php b/modules/inpostizi/src/Form/DataTransformer/DateTimeImmutableToDateTimeTransformer.php new file mode 100644 index 00000000..808fa54c --- /dev/null +++ b/modules/inpostizi/src/Form/DataTransformer/DateTimeImmutableToDateTimeTransformer.php @@ -0,0 +1,48 @@ += 70300) { + return \DateTime::createFromImmutable($value); + } + + return \DateTime::createFromFormat('U.u', $value->format('U.u'))->setTimezone($value->getTimezone()); + } + + public function reverseTransform($value): ?\DateTimeImmutable + { + if (null === $value) { + return null; + } + + if (!$value instanceof \DateTime) { + throw new TransformationFailedException('Expected a \DateTime.'); + } + + return \DateTimeImmutable::createFromMutable($value); + } +} diff --git a/modules/inpostizi/src/Form/DataTransformer/EnumDataTransformer.php b/modules/inpostizi/src/Form/DataTransformer/EnumDataTransformer.php new file mode 100644 index 00000000..795879c0 --- /dev/null +++ b/modules/inpostizi/src/Form/DataTransformer/EnumDataTransformer.php @@ -0,0 +1,60 @@ + $className + */ + public function __construct(string $className) + { + $this->className = $className; + } + + public function transform($value) + { + if (null === $value) { + return null; + } + + if (!$value instanceof Enum) { + throw new TransformationFailedException('Expected an enum.'); + } + + return $value->value; + } + + public function reverseTransform($value) + { + if (null === $value) { + return null; + } + + if (!is_string($value) && !is_int($value)) { + throw new TransformationFailedException('Expected a string or an integer.'); + } + + /** @var class-string $class */ + $class = $this->className; + + try { + return $class::from($value); + } catch (\Exception $e) { + throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); + } + } +} diff --git a/modules/inpostizi/src/Form/DataTransformer/ObjectModelCollectionToIdsTransformer.php b/modules/inpostizi/src/Form/DataTransformer/ObjectModelCollectionToIdsTransformer.php new file mode 100644 index 00000000..3d03261d --- /dev/null +++ b/modules/inpostizi/src/Form/DataTransformer/ObjectModelCollectionToIdsTransformer.php @@ -0,0 +1,84 @@ + + */ + private $class; + + /** + * @var int|null + */ + private $languageId; + + /** + * @var int|null + */ + private $shopId; + + /** + * @param class-string $class + */ + public function __construct(ObjectManagerInterface $manager, string $class, ?int $languageId = null, ?int $shopId = null) + { + $this->manager = $manager; + $this->class = $class; + $this->languageId = $languageId; + $this->shopId = $shopId; + } + + public function transform($value): ?array + { + if (null === $value) { + return null; + } + + if (!is_array($value)) { + throw new TransformationFailedException('Expected an array.'); + } + + return array_map(function ($element): int { + if (!$element instanceof $this->class) { + throw new TransformationFailedException(sprintf('Expected an instance of "%s".', $this->class)); + } + + return (int) $element->id; + }, $value); + } + + /** + * @return T[]|null + */ + public function reverseTransform($value): ?array + { + if (null === $value) { + return null; + } + + if (!is_array($value)) { + throw new TransformationFailedException('Expected an array.'); + } + + return $this->findByIdsMaintainingOrder( + $this->class, + $value, + $this->languageId, + $this->shopId + ); + } +} diff --git a/modules/inpostizi/src/Form/DataTransformer/ObjectModelToIdTransformer.php b/modules/inpostizi/src/Form/DataTransformer/ObjectModelToIdTransformer.php new file mode 100644 index 00000000..bf1b0dd2 --- /dev/null +++ b/modules/inpostizi/src/Form/DataTransformer/ObjectModelToIdTransformer.php @@ -0,0 +1,78 @@ + + */ + private $class; + + /** + * @var int|null + */ + private $languageId; + + /** + * @var int|null + */ + private $shopId; + + /** + * @param class-string $class + */ + public function __construct(ObjectManagerInterface $manager, string $class, ?int $languageId = null, ?int $shopId = null) + { + $this->manager = $manager; + $this->shopId = $shopId; + $this->languageId = $languageId; + $this->class = $class; + } + + /** + * @param T|null $value + */ + public function transform($value): ?int + { + if (null === $value) { + return null; + } + + if (!$value instanceof $this->class) { + throw new TransformationFailedException(sprintf('Expected an instance of "%s".', $this->class)); + } + + return (int) $value->id; + } + + /** + * @return T|null + */ + public function reverseTransform($value): ?\ObjectModel + { + if (null === $value) { + return null; + } + + if (!is_int($value)) { + throw new TransformationFailedException('Expected an integer.'); + } + + return $this->manager->find($this->class, $value, $this->languageId, $this->shopId); + } +} diff --git a/modules/inpostizi/src/Form/Event/ApiConfigurationValidatedEvent.php b/modules/inpostizi/src/Form/Event/ApiConfigurationValidatedEvent.php new file mode 100644 index 00000000..f9dd3736 --- /dev/null +++ b/modules/inpostizi/src/Form/Event/ApiConfigurationValidatedEvent.php @@ -0,0 +1,26 @@ +configuration = $configuration; + } + + public function getConfiguration(): ApiConfigurationInterface + { + return $this->configuration; + } +} diff --git a/modules/inpostizi/src/Form/EventListener/ReindexDataListener.php b/modules/inpostizi/src/Form/EventListener/ReindexDataListener.php new file mode 100644 index 00000000..a0112851 --- /dev/null +++ b/modules/inpostizi/src/Form/EventListener/ReindexDataListener.php @@ -0,0 +1,30 @@ + ['onPreSubmit', 50], + ]; + } + + public function onPreSubmit(FormEvent $event): void + { + $data = $event->getData() ?? []; + + if (!is_array($data)) { + return; + } + + $event->setData(array_values($data)); + } +} diff --git a/modules/inpostizi/src/Form/Extension/DependencyInjectionExtension.php b/modules/inpostizi/src/Form/Extension/DependencyInjectionExtension.php new file mode 100644 index 00000000..dcd60385 --- /dev/null +++ b/modules/inpostizi/src/Form/Extension/DependencyInjectionExtension.php @@ -0,0 +1,89 @@ + + */ + private $typeExtensions; + + /** + * @param array $typeExtensions type extensions by type name + */ + public function __construct(ContainerInterface $typeContainer, array $typeExtensions = []) + { + $this->typeContainer = $typeContainer; + $this->typeExtensions = $typeExtensions; + } + + /** + * @param string $name + */ + public function getType($name): FormTypeInterface + { + $serviceId = $this->getFormTypeServiceId($name); + + if (!$this->typeContainer->has($serviceId)) { + throw new InvalidArgumentException(sprintf('The field type "%s" is not registered in the service container.', $name)); + } + + return $this->typeContainer->get($serviceId); + } + + /** + * @param string $name + */ + public function hasType($name): bool + { + return $this->typeContainer->has($this->getFormTypeServiceId($name)); + } + + /** + * @param string $name + * + * @return FormTypeExtensionInterface[] + */ + public function getTypeExtensions($name): array + { + return $this->typeExtensions[$name] ?? []; + } + + /** + * @param string $name + */ + public function hasTypeExtensions($name): bool + { + return isset($this->typeExtensions[$name]); + } + + public function getTypeGuesser(): ?FormTypeGuesserInterface + { + return null; + } + + private function getFormTypeServiceId(string $name): string + { + return strtolower($name); // Sf 2.8 normalizes all service ids by converting them to lowercase + } +} diff --git a/modules/inpostizi/src/Form/FormFactoryFactory.php b/modules/inpostizi/src/Form/FormFactoryFactory.php new file mode 100644 index 00000000..c25b7b1e --- /dev/null +++ b/modules/inpostizi/src/Form/FormFactoryFactory.php @@ -0,0 +1,59 @@ +validator = $validator ?? Validation::createValidator(); + } + + /** + * @param ContainerInterface $container service locator of {@see FormTypeInterface} by their class names + * @param array $typeExtensions type extensions by type name + */ + public function create(ContainerInterface $container, array $typeExtensions = []): FormFactoryInterface + { + return Forms::createFormFactoryBuilder() + ->addExtension(new ValidatorExtension($this->validator)) + ->addExtension(new CsrfExtension(new CsrfTokenManager())) + ->addExtension(new HttpFoundationExtension()) + ->addExtension($this->createDependencyInjectionExtension($container, $typeExtensions)) + ->getFormFactory(); + } + + private function createDependencyInjectionExtension(ContainerInterface $container, array $typeExtensions): FormExtensionInterface + { + if (\Tools::version_compare(_PS_VERSION_, '1.7.4')) { + return new DependencyInjectionExtensionPolyfill($container, $typeExtensions); + } + + return new DependencyInjectionExtension($container, $typeExtensions, []); + } +} diff --git a/modules/inpostizi/src/Form/Type/AdvancedConfigurationType.php b/modules/inpostizi/src/Form/Type/AdvancedConfigurationType.php new file mode 100644 index 00000000..2dfa84fd --- /dev/null +++ b/modules/inpostizi/src/Form/Type/AdvancedConfigurationType.php @@ -0,0 +1,48 @@ +translator = $translator; + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $switchClass = class_exists(SwitchType::class) + ? SwitchType::class + : SwitchTypePolyfill::class; + + $builder + ->add('debugEnabled', $switchClass, [ + 'required' => false, + 'choices' => [ + $this->translator->l('Disable debug mode', self::TRANSLATION_SOURCE) => false, + $this->translator->l('Enable debug mode', self::TRANSLATION_SOURCE) => true, + ], + ]); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => AdvancedConfiguration::class, + ]); + } +} diff --git a/modules/inpostizi/src/Form/Type/ApiConfigurationType.php b/modules/inpostizi/src/Form/Type/ApiConfigurationType.php new file mode 100644 index 00000000..bc0a4db2 --- /dev/null +++ b/modules/inpostizi/src/Form/Type/ApiConfigurationType.php @@ -0,0 +1,48 @@ +translator = $translator; + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + ->add('environmentType', EnvironmentChoiceType::class, [ + 'label' => $this->translator->l('Environment', self::TRANSLATION_SOURCE), + 'help' => $this->translator->l('Select the environment on which you want to show the InPost Pay service. Remember to make sure that the service in your store is working properly before switching to the production environment', self::TRANSLATION_SOURCE), + ]) + ->add('merchantClientId', TextType::class, [ + 'label' => $this->translator->l('Merchant client ID', self::TRANSLATION_SOURCE), + 'help' => $this->translator->l('Merchant client ID is a value assigned by InPost. In order to obtain the value for the sandbox environment, send a request to integracjapay@inpost.pl. The value for the production environment can be obtained in the merchant panel.', self::TRANSLATION_SOURCE), + ]) + ->add('clientCredentials', ClientCredentialsType::class, [ + 'required' => false, + 'label' => false, + ]); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => ApiConfiguration::class, + ]); + } +} diff --git a/modules/inpostizi/src/Form/Type/CartRuleOptionsType.php b/modules/inpostizi/src/Form/Type/CartRuleOptionsType.php new file mode 100644 index 00000000..2d025344 --- /dev/null +++ b/modules/inpostizi/src/Form/Type/CartRuleOptionsType.php @@ -0,0 +1,75 @@ +translator = $translator; + $this->context = $context ?? \Context::getContext(); + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + ->add('omnibus', ChoiceType::class, [ + 'label' => $this->translator->l('Falls under the Omnibus Directive', self::TRANSLATION_SOURCE), + 'expanded' => true, + 'choices' => [ + $this->translator->l('Yes', self::TRANSLATION_SOURCE) => true, + $this->translator->l('No', self::TRANSLATION_SOURCE) => false, + ], + ]) + ->add('promoDetailsPageId', ObjectModelType::class, [ + 'required' => false, + 'class' => \CMS::class, + 'input' => 'id', + 'choice_label' => static function (\CMS $page): string { + return (string) $page->meta_title; + }, + 'label' => $this->translator->l('Promotion details page', self::TRANSLATION_SOURCE), + 'placeholder' => $this->translator->l('Use default page', self::TRANSLATION_SOURCE), + 'help' => implode("\n", [ + sprintf($this->translator->l('In order for the available promotion data to be passed to the mobile app, the "%s" option must be enabled for the cart rule.', self::TRANSLATION_SOURCE), $this->context->getTranslator()->trans('Highlight', [], 'Admin.Catalog.Feature')), + $this->translator->l('If neither the default value nor the cart rule specific value is configured, the available promotion data will not be passed to the mobile app.', self::TRANSLATION_SOURCE), + $this->translator->l('The default page can be selected on the module configuration page.', self::TRANSLATION_SOURCE), + ]), + ]); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'label' => $this->translator->l('InPost Pay options', self::TRANSLATION_SOURCE), + ]); + } + + public function getBlockPrefix(): string + { + return 'inpostizi_cart_rule_options'; + } +} diff --git a/modules/inpostizi/src/Form/Type/ClientCredentialsType.php b/modules/inpostizi/src/Form/Type/ClientCredentialsType.php new file mode 100644 index 00000000..a00cdbc0 --- /dev/null +++ b/modules/inpostizi/src/Form/Type/ClientCredentialsType.php @@ -0,0 +1,48 @@ +translator = $translator; + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + ->add('clientId', TextType::class, [ + 'label' => $this->translator->l('Client ID', self::TRANSLATION_SOURCE), + 'help' => $this->translator->l('Please note that the client ID varies depending on the selected environment. To get a sandbox Client ID contact us through the contact form. To get a production Client ID log in to InPost and complete your store details.', self::TRANSLATION_SOURCE), + ]) + ->add('clientSecret', MaskedPasswordType::class, [ + 'label' => $this->translator->l('Client secret', self::TRANSLATION_SOURCE), + 'help' => $this->translator->l('Note that Client Secret varies depending on the environment you choose. To get sandboxed Client Secret contact us through the contact form. To get production Client Secret log in to InPost and complete your store details.', self::TRANSLATION_SOURCE), + 'always_empty' => false, + ]) + ->setDataMapper(new ClientCredentialsDataMapper()); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => ClientCredentials::class, + 'empty_data' => null, + ]); + } +} diff --git a/modules/inpostizi/src/Form/Type/Compatibility/CategoryChoiceTreeType.php b/modules/inpostizi/src/Form/Type/Compatibility/CategoryChoiceTreeType.php new file mode 100644 index 00000000..a78e5ca0 --- /dev/null +++ b/modules/inpostizi/src/Form/Type/Compatibility/CategoryChoiceTreeType.php @@ -0,0 +1,191 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +declare(strict_types=1); + +namespace izi\prestashop\Form\Type\Compatibility; + +use PrestaShop\PrestaShop\Adapter\Category\CategoryDataProvider; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * Polyfill for PS < 1.7.6. + * + * @see \PrestaShopBundle\Form\Admin\Type\CategoryChoiceTreeType + * @see \PrestaShop\PrestaShop\Adapter\Form\ChoiceProvider\CategoryTreeChoiceProvider + */ +class CategoryChoiceTreeType extends AbstractType +{ + /** + * @var CategoryDataProvider + */ + private $dataProvider; + + public function __construct(CategoryDataProvider $dataProvider) + { + $this->dataProvider = $dataProvider; + } + + public function getBlockPrefix(): string + { + return 'inpostizi_category_choice_tree'; + } + + public function buildView(FormView $view, FormInterface $form, array $options): void + { + $selectedData = []; + + if (null !== $form->getData()) { + $selectedData = is_array($form->getData()) ? $form->getData() : [$form->getData()]; + } + + $view->vars['multiple'] = $options['multiple']; + $view->vars['choices_tree'] = $this->getFormattedChoicesTree($options, $selectedData); + $view->vars['choice_label'] = $options['choice_label']; + $view->vars['choice_value'] = $options['choice_value']; + $view->vars['choice_children'] = $options['choice_children']; + $view->vars['disabled_values'] = $options['disabled_values']; + $view->vars['selected_values'] = $selectedData; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver + ->setDefaults([ + 'choice_label' => 'name', + 'choice_value' => 'id_category', + 'choice_children' => 'children', + 'disabled_values' => [], + 'multiple' => false, + 'only_active' => false, + 'root_category_id' => null, + 'choices_tree' => null, + 'constraints' => [], + 'compound' => false, + ]) + ->setAllowedTypes('choice_value', 'string') + ->setAllowedTypes('choice_label', 'string') + ->setAllowedTypes('choice_children', 'string') + ->setAllowedTypes('disabled_values', 'array') + ->setAllowedTypes('multiple', 'bool') + ->setAllowedTypes('only_active', 'bool') + ->setAllowedTypes('root_category_id', ['null', 'int']) + ->setAllowedTypes('choices_tree', ['null', 'array']) + ->setNormalizer('root_category_id', function (Options $options, ?int $value): int { + return $value ?? $this->getRootCategoryId(); + }) + ->setNormalizer('choices_tree', function (Options $options, ?array $value): array { + return $value ?? $this->getChoices($options['root_category_id'], $options['only_active']); + }); + } + + private function getFormattedChoicesTree(array $options, array $selectedData): array + { + $tree = $options['choices_tree']; + + foreach ($tree as &$choice) { + $this->fillChoiceWithChildrenSelection( + $choice, + $options['choice_value'], + $options['choice_children'], + $selectedData + ); + } + + return $tree; + } + + private function fillChoiceWithChildrenSelection(array &$choice, string $choiceValueName, string $choiceChildrenName, array $selectedValues): bool + { + $isSelected = false; + $isChildrenSelected = false; + + if (in_array($choice[$choiceValueName], $selectedValues, true)) { + $isSelected = true; + } + + if (isset($choice[$choiceChildrenName])) { + foreach ($choice[$choiceChildrenName] as &$child) { + $selected = $this->fillChoiceWithChildrenSelection( + $child, + $choiceValueName, + $choiceChildrenName, + $selectedValues + ); + + if ($selected) { + $isChildrenSelected = true; + } + } + unset($child); + } + + $choice['has_selected_children'] = $isChildrenSelected; + + return $isSelected || $isChildrenSelected; + } + + public function getChoices(int $rootCategoryId, bool $onlyActive): array + { + $categories = $this->dataProvider->getNestedCategories($rootCategoryId, false, $onlyActive); + + $choices = []; + foreach ($categories as $category) { + $choices[] = $this->buildChoiceTree($category); + } + + return $choices; + } + + private function buildChoiceTree(array $category): array + { + $tree = [ + 'id_category' => $category['id_category'], + 'name' => $category['name'], + ]; + + if (isset($category['children'])) { + foreach ($category['children'] as $childCategory) { + $tree['children'][] = $this->buildChoiceTree($childCategory); + } + } + + return $tree; + } + + private function getRootCategoryId(): int + { + if (is_callable([$this->dataProvider, 'getRootCategory'])) { + return (int) $this->dataProvider->getRootCategory()->id; + } + + return (int) \Category::getRootCategory()->id; + } +} diff --git a/modules/inpostizi/src/Form/Type/Consent/ConsentLinkType.php b/modules/inpostizi/src/Form/Type/Consent/ConsentLinkType.php new file mode 100644 index 00000000..516b8c4d --- /dev/null +++ b/modules/inpostizi/src/Form/Type/Consent/ConsentLinkType.php @@ -0,0 +1,142 @@ + CMS pages by ID + */ + private $cmsChoices; + + /** + * @param CmsPageRepository $cmsPageRepository + */ + public function __construct(LegacyTranslator $translator, \Context $context, ObjectRepositoryInterface $cmsPageRepository) + { + $this->translator = $translator; + $this->context = $context; + $this->cmsPageRepository = $cmsPageRepository; + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $translatableClass = class_exists(TranslatableType::class) + ? TranslatableType::class + : TranslatableTypePolyfill::class; + + $builder + ->add('id', TextType::class, [ + 'label' => $this->translator->l('Identifier', self::TRANSLATION_SOURCE), + 'help' => $this->translator->l('Unique link identifier. Placeholders created by adding the prefix "#" to identifiers will be replaced with corresponding links in the description.', self::TRANSLATION_SOURCE), + 'attr' => [ + 'maxlength' => Uuid::CANONICAL_FORMAT_LENGTH, + ], + ]) + ->add('cmsPageId', ChoiceType::class, [ + 'choice_loader' => $this, + 'choice_value' => 'id', + 'choice_label' => 'meta_title', + 'label' => $this->translator->l('Details page', ConsentType::TRANSLATION_SOURCE), + 'help' => $this->translator->l('Specifies the page to which your customer will be redirected for a target who clicks on a given consent in the InPost mobile app.', ConsentType::TRANSLATION_SOURCE), + ]) + ->add('labels', $translatableClass, [ + 'required' => false, + 'type' => TextType::class, + 'label' => $this->translator->l('Link text', self::TRANSLATION_SOURCE), + 'help' => $this->translator->l('If left empty, "link" will be displayed.', self::TRANSLATION_SOURCE), + ]); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => ConsentLink::class, + ]); + } + + private function getCmsPageChoices(): array + { + if (isset($this->cmsChoices)) { + return $this->cmsChoices; + } + + $this->cmsChoices = []; + + $languageId = (int) $this->context->language->id; + $shopId = (int) $this->context->shop->id; + + foreach ($this->cmsPageRepository->findActiveByLanguageAndShopId($languageId, $shopId) as $cmsPage) { + $this->cmsChoices[$cmsPage->id] = $cmsPage; + } + + return $this->cmsChoices; + } + + /** + * {@inheritDoc} + */ + public function loadChoiceList($value = null): ChoiceListInterface + { + return new ArrayChoiceList($this->getCmsPageChoices(), $value); + } + + /** + * {@inheritDoc} + */ + public function loadChoicesForValues(array $values, $value = null): array + { + return $this->loadChoiceList($value)->getChoicesForValues($values); + } + + /** + * {@inheritDoc} + */ + public function loadValuesForChoices(array $choices, $value = null): array + { + $choices = array_map(function ($choice): ?\CMS { + if ($choice instanceof \CMS) { + return $choice; + } + + return $this->getCmsPageChoices()[$choice] ?? null; + }, $choices); + + return $this->loadChoiceList($value)->getValuesForChoices($choices); + } +} diff --git a/modules/inpostizi/src/Form/Type/Consent/ConsentRequirementChoiceType.php b/modules/inpostizi/src/Form/Type/Consent/ConsentRequirementChoiceType.php new file mode 100644 index 00000000..c6d55e08 --- /dev/null +++ b/modules/inpostizi/src/Form/Type/Consent/ConsentRequirementChoiceType.php @@ -0,0 +1,49 @@ +translator = $translator; + } + + public function getParent(): string + { + return ChoiceType::class; + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder->addModelTransformer(new EnumDataTransformer(ConsentRequirementType::class)); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'choices' => [ + $this->translator->l('Optional', self::TRANSLATION_SOURCE) => ConsentRequirementType::Optional()->value, + $this->translator->l('Always required', self::TRANSLATION_SOURCE) => ConsentRequirementType::RequiredAlways()->value, + $this->translator->l('Required once', self::TRANSLATION_SOURCE) => ConsentRequirementType::RequiredOnce()->value, + ], + ]); + } +} diff --git a/modules/inpostizi/src/Form/Type/Consent/ConsentType.php b/modules/inpostizi/src/Form/Type/Consent/ConsentType.php new file mode 100644 index 00000000..aa357265 --- /dev/null +++ b/modules/inpostizi/src/Form/Type/Consent/ConsentType.php @@ -0,0 +1,96 @@ +translator = $translator; + } + + public function finishView(FormView $view, FormInterface $form, array $options): void + { + $view['additionalLinks']->vars['add_entry_label'] = $this->translator->l('Add another link', self::TRANSLATION_SOURCE); + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $translatableClass = class_exists(TranslatableType::class) + ? TranslatableType::class + : TranslatableTypePolyfill::class; + + $builder + ->add('descriptions', $translatableClass, [ + 'label' => $this->translator->l('Consent text in the mobile app', self::TRANSLATION_SOURCE), + 'type' => TextType::class, + 'help' => nl2br(sprintf( + "%s %s\n %s", + $this->translator->l('Add a description to be displayed with the consent in the InPost mobile app.', self::TRANSLATION_SOURCE), + $this->translator->l('If blank in a language other than the shop\'s default, the value for the default language will be used.', self::TRANSLATION_SOURCE), + $this->translator->l('Use link identifiers prefixed with "#" to control the position of links.', self::TRANSLATION_SOURCE) + )), + ]) + ->add('requirementType', ConsentRequirementChoiceType::class, [ + 'label' => $this->translator->l('Requiredness', self::TRANSLATION_SOURCE), + 'help' => $this->translator->l('Specify whether consent is required or optional.', self::TRANSLATION_SOURCE), + ]) + ->add('link', ConsentLinkType::class, [ + 'label' => false, + ]) + ->add('additionalLinks', CollectionType::class, [ + 'entry_type' => ConsentLinkType::class, + 'by_reference' => false, + 'allow_add' => true, + 'allow_delete' => true, + 'entry_options' => [ + 'label' => false, + ], + 'error_bubbling' => false, + 'label' => $this->translator->l('Links', self::TRANSLATION_SOURCE), + 'attr' => [ + 'data-max-count' => Consent::ADDITIONAL_LINKS_COUNT_MAX, + ], + ]) + ->get('additionalLinks') + ->addEventSubscriber(new ReindexDataListener()); + + // usages moved to another file - kept for AdminTranslationsController message extraction purpose + // ->l('Details page'), + // ->l('Specifies the page to which your customer will be redirected for a target who clicks on a given consent in the InPost mobile app.'), + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => Consent::class, + 'validation_groups' => new GroupSequence(['Default', Consent::VALIDATION_GROUP]), + ]); + } +} diff --git a/modules/inpostizi/src/Form/Type/ConsentsConfigurationType.php b/modules/inpostizi/src/Form/Type/ConsentsConfigurationType.php new file mode 100644 index 00000000..9f9a2cc9 --- /dev/null +++ b/modules/inpostizi/src/Form/Type/ConsentsConfigurationType.php @@ -0,0 +1,65 @@ +translator = $translator; + } + + public function finishView(FormView $view, FormInterface $form, array $options): void + { + $view['consents']->vars['add_entry_label'] = $this->translator->l('Add another consent', self::TRANSLATION_SOURCE); + $view['consents']->vars['max_count_message'] = sprintf($this->translator->l('The maximum number of consents is %d.', self::TRANSLATION_SOURCE), UpdateConsentsConfigurationCommand::CONSENTS_COUNT_MAX); + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + ->add('consents', CollectionType::class, [ + 'entry_type' => ConsentType::class, + 'by_reference' => false, + 'allow_add' => true, + 'allow_delete' => true, + 'entry_options' => [ + 'label' => false, + ], + 'label' => false, + 'attr' => [ + 'data-max-count' => UpdateConsentsConfigurationCommand::CONSENTS_COUNT_MAX, + ], + ]) + ->get('consents') + ->addEventSubscriber(new ReindexDataListener()); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => UpdateConsentsConfigurationCommand::class, + 'validation_groups' => new GroupSequence(['Default', Consent::VALIDATION_GROUP]), + 'label' => false, + ]); + } +} diff --git a/modules/inpostizi/src/Form/Type/EnvironmentChoiceType.php b/modules/inpostizi/src/Form/Type/EnvironmentChoiceType.php new file mode 100644 index 00000000..752673d5 --- /dev/null +++ b/modules/inpostizi/src/Form/Type/EnvironmentChoiceType.php @@ -0,0 +1,59 @@ +translator = $translator; + } + + public function getParent(): string + { + return ChoiceType::class; + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder->addModelTransformer(new EnumDataTransformer(EnvironmentType::class)); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'choices' => $this->getChoices(), + ]); + } + + private function getChoices(): array + { + $choices = [ + $this->translator->l('Sandbox', self::TRANSLATION_SOURCE) => EnvironmentType::Sandbox()->value, + $this->translator->l('Production', self::TRANSLATION_SOURCE) => EnvironmentType::Production()->value, + ]; + + if (!class_exists(UatEnvironment::class)) { + return $choices; + } + + return array_merge([ + $this->translator->l('UAT', self::TRANSLATION_SOURCE) => EnvironmentType::Uat()->value, + ], $choices); + } +} diff --git a/modules/inpostizi/src/Form/Type/GeneralConfigurationType.php b/modules/inpostizi/src/Form/Type/GeneralConfigurationType.php new file mode 100644 index 00000000..08c0a0be --- /dev/null +++ b/modules/inpostizi/src/Form/Type/GeneralConfigurationType.php @@ -0,0 +1,163 @@ +translator = $translator; + $this->eventDispatcher = $eventDispatcher; + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $switchClass = class_exists(SwitchType::class) + ? SwitchType::class + : SwitchTypePolyfill::class; + + $builder + ->add('enableForEveryone', ChoiceType::class, [ + 'choices' => [ + $this->translator->l('to everyone', self::TRANSLATION_SOURCE) => true, + $this->translator->l('to testers', self::TRANSLATION_SOURCE) => false, + ], + 'property_path' => 'generalConfiguration.enabledForEveryone', + 'label' => $this->translator->l('Display widget', self::TRANSLATION_SOURCE), + 'help' => $this->translator->l('If you select "to testers" the widget will be visible only to those who are supposed to see it. To display the widget in this mode in a web browser, type the address of your store with \'?showIzi=true\' Example: https://mojsklep.pl?showIzi=true', self::TRANSLATION_SOURCE), + ]) + ->add('thankYouDisplayHook', ChoiceType::class, [ + 'choices' => [ + DisplayPaymentReturn::getHookName() => DisplayPaymentReturn::getHookName(), + DisplayOrderConfirmation::getHookName() => DisplayOrderConfirmation::getHookName(), + DisplayIziThankYou::getHookName() => DisplayIziThankYou::getHookName(), + ], + 'property_path' => 'generalConfiguration.thankYouDisplayHook', + 'label' => $this->translator->l('Order confirmation page display hook', self::TRANSLATION_SOURCE), + 'help' => sprintf($this->translator->l('If you choose the \'%s\' hook you have to manually implement it in the templates/checkout/order-confirmation.tpl file \'{hook h="%s" order=$order}\'.', self::TRANSLATION_SOURCE), DisplayIziThankYou::getHookName(), DisplayIziThankYou::getHookName()), + ]) + ->add('productCardDisplayHook', ChoiceType::class, [ + 'choices' => [ + DisplayProductAdditionalInfo::getHookName() => DisplayProductAdditionalInfo::getHookName(), + DisplayProductActions::getHookName() => DisplayProductActions::getHookName(), + ], + 'property_path' => 'generalConfiguration.productCardDisplayHook', + 'label' => $this->translator->l('Product page hook used to display widget', self::TRANSLATION_SOURCE), + 'help' => sprintf($this->translator->l('You can choose a different hook if you have problems displaying the InPost Pay widget on the product page.', self::TRANSLATION_SOURCE), DisplayIziThankYou::getHookName(), DisplayIziThankYou::getHookName()), + ]) + ->add('checkoutButtonDisplayHook', ChoiceType::class, [ + 'choices' => [ + DisplayCheckoutSummaryTop::getHookName() => DisplayCheckoutSummaryTop::getHookName(), + DisplayIziCheckoutButton::getHookName() => DisplayIziCheckoutButton::getHookName(), + ], + 'property_path' => 'generalConfiguration.checkoutButtonDisplayHook', + 'label' => $this->translator->l('Checkout process hook used to display widget', self::TRANSLATION_SOURCE), + 'help' => sprintf($this->translator->l('If you choose the \'%s\' hook you have to manually implement it in the template \'{hook h="%s"}\'.', self::TRANSLATION_SOURCE), DisplayIziCheckoutButton::getHookName(), DisplayIziCheckoutButton::getHookName()), + ]) + ->add('fullPageCacheModuleInUse', $switchClass, [ + 'property_path' => 'generalConfiguration.fullPageCacheModuleInUse', + 'label' => $this->translator->l('Full page cache in use', self::TRANSLATION_SOURCE), + 'help' => $this->translator->l('Use this option if you are using the full page cache module or a varnish, lightspeed cache.', self::TRANSLATION_SOURCE), + ]) + ->add('sendAnalyticsData', $switchClass, [ + 'property_path' => 'generalConfiguration.sendAnalyticsData', + 'label' => $this->translator->l('Send analytics data', self::TRANSLATION_SOURCE), + 'help' => $this->translator->l('Use this option if you want to send analytics data to InPostPay', self::TRANSLATION_SOURCE), + ]) + ->add('apiConfiguration', ApiConfigurationType::class, [ + 'label' => false, + 'error_mapping' => [ + '.' => 'clientCredentials.clientSecret', + ], + ]) + ->add('ordersConfiguration', OrdersConfigurationType::class, [ + 'label' => false, + ]) + ->add('productConfiguration', ProductConfigurationType::class, [ + 'label' => $this->translator->l('Product images configuration', self::TRANSLATION_SOURCE), + ]) + ->add('maxSuggestedProducts', IntegerType::class, [ + 'property_path' => 'generalConfiguration.maxSuggestedProducts', + 'required' => false, + 'label' => $this->translator->l('Maximum number of suggested products', self::TRANSLATION_SOURCE), + 'help' => $this->translator->l('To show suggested products, complete the Accessories Products section in the product configuration.', self::TRANSLATION_SOURCE), + 'attr' => [ + 'placeholder' => $this->translator->l('unlimited', self::TRANSLATION_SOURCE), + 'min' => 0, + ], + ]) + ->add('defaultPromoDetailsPageId', ObjectModelType::class, [ + 'property_path' => 'generalConfiguration.defaultPromoDetailsPageId', + 'required' => false, + 'class' => \CMS::class, + 'input' => 'id', + 'choice_label' => static function (\CMS $page): string { + return (string) $page->meta_title; + }, + 'label' => $this->translator->l('Default promotion details page', self::TRANSLATION_SOURCE), + 'placeholder' => '--', + 'help' => nl2br(implode("\n", [ + $this->translator->l('The page to use as the promotion details link for highlighted discounts if no specific page is configured for the cart rule.', self::TRANSLATION_SOURCE), + $this->translator->l('If neither the default value nor the cart rule specific value is configured, the available promotion data will not be passed to the mobile app.', CartRuleOptionsType::TRANSLATION_SOURCE), + ])), + ]); + + if (null === $this->eventDispatcher) { + return; + } + + $builder + ->get('apiConfiguration') + ->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) { + if (!$event->getForm()->isValid()) { + return; + } + + $this->eventDispatcher->dispatch(new ApiConfigurationValidatedEvent($event->getData())); + }, -100); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => UpdateGeneralConfigurationCommand::class, + 'validation_groups' => new GroupSequence(['Default', 'API']), + ]); + } +} diff --git a/modules/inpostizi/src/Form/Type/GuiConfigurationType.php b/modules/inpostizi/src/Form/Type/GuiConfigurationType.php new file mode 100644 index 00000000..d158ccc3 --- /dev/null +++ b/modules/inpostizi/src/Form/Type/GuiConfigurationType.php @@ -0,0 +1,119 @@ +translator = $translator; + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + /** @var BindingPlace $bindingPlace */ + foreach ($options['binding_places'] as $bindingPlace) { + $builder->add($bindingPlace->value, WidgetDisplayConfigurationType::class, [ + 'label' => $bindingPlace->trans($this->translator), + 'property_path' => 'displayConfigurations[' . $bindingPlace->value . ']', + 'binding_place' => $bindingPlace, + 'description' => $this->getDescriptionByBindingPlace($bindingPlace), + ]); + } + + $builder + ->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) { + /** @var GuiConfigurationInterface|null $data */ + if (null === $data = $event->getData()) { + return; + } + + $productConfiguration = $data->getDisplayConfiguration($bindingPlace = BindingPlace::ProductCard()); + + if (!$productConfiguration instanceof ProductRestrictionsProviderInterface) { + return; + } + + $form = $event->getForm(); + $form->add($bindingPlace->value, ProductPageDisplayConfigurationType::class, [ + 'label' => $bindingPlace->trans($this->translator), + 'description' => $this->getDescriptionByBindingPlace($bindingPlace), + 'property_path' => 'displayConfigurations[' . $bindingPlace->value . ']', + ]); + }); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver + ->setDefaults([ + 'data_class' => DTO\GuiConfiguration::class, + 'binding_places' => GuiConfiguration::getConfigurableBindingPlaces(), + ]) + ->setAllowedTypes('binding_places', ['array'/*, BindingPlace::class . '[]'*/]) + ->setAllowedValues('binding_places', static function (array $value) { + foreach ($value as $bindingPlace) { + if (!$bindingPlace instanceof BindingPlace) { + return false; + } + + if (!$bindingPlace->canDisplayBindingWidget()) { + return false; + } + } + + return true; + }); + } + + private function getDescriptionByBindingPlace(BindingPlace $bindingPlace): string + { + switch ($bindingPlace) { + case BindingPlace::ProductCard(): + return $this->translator->l('This widget will be displayed in the product page.', self::TRANSLATION_SOURCE); + case BindingPlace::BasketSummary(): + return $this->translator->l('This widget will be displayed in the cart page, under submit button.', self::TRANSLATION_SOURCE); + case BindingPlace::LoginPage(): + return $this->translator->l('This widget will be displayed in the login page, under form submit button.', self::TRANSLATION_SOURCE); + case BindingPlace::RegisterFormPage(): + return $this->translator->l('This widget will be displayed in the register page, above register form.', self::TRANSLATION_SOURCE); + case BindingPlace::CheckoutPage(): + return $this->translator->l('This widget will be displayed in the checkout page, above products summary.', self::TRANSLATION_SOURCE); + case BindingPlace::MiniCartPage(): + return $this->translator->l('This widget will be displayed in the cart preview. To display this hook you have to register custom hook in your template "{hook h=\'displayIziCartPreviewButton\'}"', self::TRANSLATION_SOURCE); + default: + return ''; + } + + // translations moved to BindingPlace enum, kept for AdminModuleTranslations message discovery + // ->l('Cart page', self::TRANSLATION_SOURCE), + // ->l('Product card', self::TRANSLATION_SOURCE), + // ->l('Login page', self::TRANSLATION_SOURCE), + // ->l('Register page', self::TRANSLATION_SOURCE), + // ->l('Checkout page', self::TRANSLATION_SOURCE), + // ->l('Cart preview', self::TRANSLATION_SOURCE), + } +} diff --git a/modules/inpostizi/src/Form/Type/Image/ImageTypeChoiceType.php b/modules/inpostizi/src/Form/Type/Image/ImageTypeChoiceType.php new file mode 100644 index 00000000..5075d885 --- /dev/null +++ b/modules/inpostizi/src/Form/Type/Image/ImageTypeChoiceType.php @@ -0,0 +1,49 @@ +choiceLoader = $choiceLoader; + } + + public function getParent(): string + { + return ChoiceType::class; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'choice_loader' => $this->choiceLoader, + 'choice_value' => 'id', + 'choice_label' => function (\ImageType $imageType): string { + $name = $imageType->name ?? ''; + + if ($imageType->width && $imageType->height) { + $name .= sprintf(' (%d x %d)', $imageType->width, $imageType->height); + } + + return $name; + }, + ]); + } +} diff --git a/modules/inpostizi/src/Form/Type/MaskedPasswordType.php b/modules/inpostizi/src/Form/Type/MaskedPasswordType.php new file mode 100644 index 00000000..7b089173 --- /dev/null +++ b/modules/inpostizi/src/Form/Type/MaskedPasswordType.php @@ -0,0 +1,27 @@ +vars['value'] && $form->getData()) { + $view->vars['value'] = self::MASKED_VALUE; + } + } + + public function getParent(): string + { + return PasswordType::class; + } +} diff --git a/modules/inpostizi/src/Form/Type/ObjectModelAutocompleteType.php b/modules/inpostizi/src/Form/Type/ObjectModelAutocompleteType.php new file mode 100644 index 00000000..6a56e353 --- /dev/null +++ b/modules/inpostizi/src/Form/Type/ObjectModelAutocompleteType.php @@ -0,0 +1,82 @@ +translator = $translator; + } + + public function getParent(): string + { + return ObjectModelType::class; + } + + public function finishView(FormView $view, FormInterface $form, array $options): void + { + $attr = $view->vars['attr'] ?? []; + + $values = []; + $values['url'] = $options['autocomplete_url']; + + if ($options['min_characters']) { + $values['min-characters'] = $options['min_characters']; + } + + $values['loading-more-text'] = $options['loading_more_text']; + $values['no-results-found-text'] = $options['no_results_found_text']; + $values['no-more-results-text'] = $options['no_more_results_text']; + + foreach ($values as $name => $value) { + $attr['data-' . $name] = $value; + } + + $view->vars['uses_autocomplete'] = true; + $view->vars['attr'] = $attr; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver + ->setRequired([ + 'autocomplete_url', + ]) + ->setDefaults([ + 'loading_more_text' => $this->translator->l('Loading more results...', self::TRANSLATION_SOURCE), + 'no_results_found_text' => $this->translator->l('No results found', self::TRANSLATION_SOURCE), + 'no_more_results_text' => $this->translator->l('No more results', self::TRANSLATION_SOURCE), + 'min_characters' => null, + 'choice_loader' => static function (Options $options, ?ChoiceLoaderInterface $loader): ?ChoiceLoaderInterface { + if (null === $loader) { + return null; + } + + return new LazyChoiceLoader($loader); + }, + ]) + ->setAllowedTypes('autocomplete_url', 'string'); + } +} diff --git a/modules/inpostizi/src/Form/Type/ObjectModelType.php b/modules/inpostizi/src/Form/Type/ObjectModelType.php new file mode 100644 index 00000000..b28cdaaa --- /dev/null +++ b/modules/inpostizi/src/Form/Type/ObjectModelType.php @@ -0,0 +1,160 @@ + + */ + private $choiceLoaders = []; + + public function __construct(ObjectManagerInterface $manager, \Context $context) + { + $this->context = $context; + $this->manager = $manager; + } + + public function getParent(): string + { + return ChoiceType::class; + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + if ('id' !== $options['input']) { + return; + } + + if ($options['multiple']) { + $builder->addModelTransformer(new ReversedTransformer( + new ObjectModelCollectionToIdsTransformer( + $this->manager, + $options['class'], + $options['language_id'], + $options['shop_id'] + ) + )); + } else { + $builder->addModelTransformer(new ReversedTransformer( + new ObjectModelToIdTransformer( + $this->manager, + $options['class'], + $options['language_id'], + $options['shop_id'] + ) + )); + } + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver + ->setRequired(['class']) + ->setDefaults([ + 'language_id' => (int) $this->context->language->id, + 'shop_id' => null, + 'input' => 'object', + 'query_builder' => null, + 'choices' => null, + 'choice_loader' => function (Options $options): ?ChoiceLoaderInterface { + if (null !== $options['choices']) { + return null; + } + + $cacheKey = $this->getChoiceLoaderCacheKey($options['class'], $options['query_builder'], $options['language_id'], $options['shop_id']); + + return $this->choiceLoaders[$cacheKey] ?? $this->choiceLoaders[$cacheKey] = new ObjectModelChoiceLoader( + $this->manager, + $options['class'], + $options['query_builder'], + $options['language_id'], + $options['shop_id'] + ); + }, + 'choice_label' => function (\ObjectModel $model) { + if (is_callable([$model, '__toString'])) { + return (string) $model; + } + + $label = null; + + // might as well use model's "name" property by default... + if (property_exists($model, 'name')) { + if (!is_array($model->name)) { + $label = $model->name; + } elseif ([] !== $model->name) { + $label = $model->name[$this->context->language->id] ?? current($model->name); + } + } + + return $label ?? sprintf('"%s" #%d', get_class($model), $model->id); + }, + 'choice_name' => static function (\ObjectModel $model): string { + return (string) $model->id; + }, + 'choice_value' => static function (?\ObjectModel $model): string { + return null !== $model ? (string) $model->id : ''; + }, + ]) + ->setAllowedTypes('language_id', ['null', 'int']) + ->setAllowedTypes('shop_id', ['null', 'int']) + ->setAllowedTypes('query_builder', ['null', 'callable', QueryBuilder::class]) + ->setAllowedValues('input', ['object', 'id']) + ->setNormalizer('query_builder', function (Options $options, $value): ?QueryBuilder { + if (is_callable($value)) { + $value = $value( + $this->manager->getRepository($options['class']), + $options['language_id'], + $options['shop_id'] + ); + + if (null !== $value && !$value instanceof QueryBuilder) { + throw new UnexpectedTypeException($value, QueryBuilder::class); + } + } + + return $value; + }); + } + + private function getChoiceLoaderCacheKey(string $class, ?QueryBuilder $queryBuilder, ?int $languageId, ?int $shopId): string + { + if (null === $queryBuilder) { + return sprintf('%s_%d_%d', $class, (int) $languageId, (int) $shopId); + } + + $sql = $queryBuilder->build()->getSql(); + + return hash('md5', $sql . (int) $languageId); + } +} diff --git a/modules/inpostizi/src/Form/Type/Order/AvailablePaymentOptionsChoiceType.php b/modules/inpostizi/src/Form/Type/Order/AvailablePaymentOptionsChoiceType.php new file mode 100644 index 00000000..b1923e8a --- /dev/null +++ b/modules/inpostizi/src/Form/Type/Order/AvailablePaymentOptionsChoiceType.php @@ -0,0 +1,94 @@ +choiceLoader = $choiceLoader; + $this->translator = $translator; + } + + public function getParent(): string + { + return ChoiceType::class; + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + if (!$options['multiple'] || null === $options['choice_loader']) { + return; + } + + /** @var ChoiceLoaderInterface $choiceLoader */ + $choiceLoader = $options['choice_loader']; + + $builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) use ($choiceLoader) { + if (null === $data = $event->getData()) { + return; + } + + if (!is_array($data)) { + throw new TransformationFailedException('Expected an array.'); + } + + $choices = $choiceLoader->loadChoiceList()->getChoices(); + + // remove valid but unavailable payment type choices + $data = array_filter($data, static function ($value) use ($choices) { + if (!is_string($value)) { + return true; + } + + if (null === $type = PaymentType::tryFrom($value)) { + return true; + } + + return in_array($type, $choices, true); + }); + + $event->setData($data); + }, 128); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'choice_loader' => $this->choiceLoader, + 'choice_value' => function (PaymentType $paymentType) { + return $paymentType->value; + }, + 'choice_label' => function (PaymentType $paymentType) { + return $this->translator->getLabel($paymentType); + }, + ]); + } +} diff --git a/modules/inpostizi/src/Form/Type/Order/MessageOptionsType.php b/modules/inpostizi/src/Form/Type/Order/MessageOptionsType.php new file mode 100644 index 00000000..a57831c5 --- /dev/null +++ b/modules/inpostizi/src/Form/Type/Order/MessageOptionsType.php @@ -0,0 +1,97 @@ +translator = $translator; + $this->parameterDescriptor = $parameterDescriptor; + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $switchClass = class_exists(SwitchType::class) + ? SwitchType::class + : SwitchTypePolyfill::class; + + $builder + ->add('appendIfApmDelivery', $switchClass, [ + 'label' => $this->translator->l('Append custom content to customer message if APM delivery was selected', self::TRANSLATION_SOURCE), + ]) + ->add('message', TextareaType::class, [ + 'required' => false, + 'label' => $this->translator->l('Message format', self::TRANSLATION_SOURCE), + 'help' => nl2br($this->getMessageFormatDescription()), + 'attr' => [ + 'rows' => 5, + ], + ]) + ->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) { + $data = $event->getData(); + + if ($data instanceof MessageOptions && $data->isCustomFormat()) { + $event->getForm()->remove('appendIfApmDelivery'); + } + }); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => MessageOptions::class, + ]); + } + + private function getMessageFormatDescription(): string + { + return sprintf( + "%s:\n%s\n\n%s\n\n%s", + $this->translator->l('Available parameters', self::TRANSLATION_SOURCE), + $this->formatParameterDescriptions(), + $this->translator->l('Expressions enclosed in double curly braces are evaluated (e.g. `{{ is_pww ? "yes" : "no" }}` will result in "yes" if Weekend Delivery option was selected or "no" otherwise).', self::TRANSLATION_SOURCE), + $this->translator->l('More details can be found in module documentation.', self::TRANSLATION_SOURCE) + ); + } + + private function formatParameterDescriptions(): string + { + $descriptions = []; + + foreach ($this->parameterDescriptor->getDescriptions() as $name => $description) { + $descriptions[] = sprintf('{%s} - %s', $name, $description); + } + + return implode("\n", $descriptions); + } +} diff --git a/modules/inpostizi/src/Form/Type/OrderStateChoiceType.php b/modules/inpostizi/src/Form/Type/OrderStateChoiceType.php new file mode 100644 index 00000000..6764faee --- /dev/null +++ b/modules/inpostizi/src/Form/Type/OrderStateChoiceType.php @@ -0,0 +1,49 @@ +context = $context; + $this->choiceLoader = $choiceLoader; + } + + public function getParent(): string + { + return ChoiceType::class; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'choice_loader' => $this->choiceLoader, + 'choice_value' => 'id', + 'choice_label' => function (\OrderState $orderState): string { + return $orderState->name[$this->context->language->id] ?? ''; + }, + ]); + } +} diff --git a/modules/inpostizi/src/Form/Type/OrderStatusDescriptionMapType.php b/modules/inpostizi/src/Form/Type/OrderStatusDescriptionMapType.php new file mode 100644 index 00000000..9bed45d1 --- /dev/null +++ b/modules/inpostizi/src/Form/Type/OrderStatusDescriptionMapType.php @@ -0,0 +1,48 @@ +context = $context; + $this->choiceLoader = $choiceLoader; + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + /** @var \OrderState $orderState */ + foreach ($this->choiceLoader->loadChoiceList()->getChoices() as $orderState) { + $builder + ->add((string) $orderState->id, TextType::class, [ + 'required' => false, + 'label' => $orderState->name[$this->context->language->id] ?? sprintf('Order state #%d', $orderState->id), + 'attr' => [ + 'placeholder' => $orderState->name[$builder->getName()] ?? '', + ], + ]); + } + } +} diff --git a/modules/inpostizi/src/Form/Type/OrdersConfigurationType.php b/modules/inpostizi/src/Form/Type/OrdersConfigurationType.php new file mode 100644 index 00000000..4312ace1 --- /dev/null +++ b/modules/inpostizi/src/Form/Type/OrdersConfigurationType.php @@ -0,0 +1,82 @@ +translator = $translator; + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $translatableClass = class_exists(TranslatableType::class) ? TranslatableType::class : TranslatableTypePolyfill::class; + $switchClass = class_exists(SwitchType::class) ? SwitchType::class : SwitchTypePolyfill::class; + + $builder + ->add('defaultInitialStatusId', OrderStateChoiceType::class, [ + 'label' => $this->translator->l('Initial order status', self::TRANSLATION_SOURCE), + ]) + ->add('cashOnDeliveryStatusId', OrderStateChoiceType::class, [ + 'label' => $this->translator->l('Initial order status (cash on delivery)', self::TRANSLATION_SOURCE), + ]) + ->add('paidStatusId', OrderStateChoiceType::class, [ + 'label' => $this->translator->l('Paid order status', self::TRANSLATION_SOURCE), + ]) + ->add('statusDescriptionMap', $translatableClass, [ + 'label' => $this->translator->l('Order statuses', self::TRANSLATION_SOURCE), + 'type' => OrderStatusDescriptionMapType::class, + ]) + ->add('allPaymentOptionsEnabled', $switchClass, [ + 'required' => false, + 'label' => $this->translator->l('Enable all available payment options', self::TRANSLATION_SOURCE), + 'help' => nl2br(implode("\n\n", [ + $this->translator->l('Payment methods are specified on the payment gateway contract', self::TRANSLATION_SOURCE), + $this->translator->l('Payment on delivery will be available only if you have an agreement with InPost to provide this service in your store.', self::TRANSLATION_SOURCE), + ])), + ]) + ->add('availablePaymentOptions', AvailablePaymentOptionsChoiceType::class, [ + 'required' => false, + 'label' => $this->translator->l('Enabled payment options', self::TRANSLATION_SOURCE), + 'multiple' => true, + 'expanded' => true, + ]) + ->add('pointOfSaleId', TextType::class, [ + 'label' => $this->translator->l('POS ID', self::TRANSLATION_SOURCE), + 'help' => $this->translator->l('For sandbox environment contact InPost. In the case of a production environment - log into InPost and get the POS ID', self::TRANSLATION_SOURCE), + ]) + ->add('messageOptions', MessageOptionsType::class, [ + 'label' => $this->translator->l('Order message', self::TRANSLATION_SOURCE), + 'error_mapping' => [ + '.' => 'message', + ], + ]); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => OrdersConfiguration::class, + ]); + } +} diff --git a/modules/inpostizi/src/Form/Type/Product/CombinationByAttributesChoiceType.php b/modules/inpostizi/src/Form/Type/Product/CombinationByAttributesChoiceType.php new file mode 100644 index 00000000..d359490c --- /dev/null +++ b/modules/inpostizi/src/Form/Type/Product/CombinationByAttributesChoiceType.php @@ -0,0 +1,137 @@ +manager = $manager; + $this->context = $context; + $this->translator = $translator; + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $repository = $this->getRepository(); + + $attributeClass = $this->repository->getAttributeModelClass(); + $attributesByGroupId = $repository->getAvailableAttributesByProductId( + $options['product_id'], + $options['language_id'] + ); + + // TODO: we currently do not take into account that not all product attribute combinations are possible + /** @var ProductAttribute[] $attributes */ + foreach ($attributesByGroupId as $groupId => $attributes) { + $groupName = $attributes[0]->getGroup()->name; + + $builder->add((string) $groupId, ObjectModelType::class, [ + 'class' => $attributeClass, + 'input' => 'id', + 'label' => $groupName, + 'choices' => array_map(static function (ProductAttribute $attribute) { + return $attribute->getAttribute(); + }, $attributes), + ]); + } + + $builder->addModelTransformer($this->createModelTransformer($repository, $options)); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver + ->setRequired([ + 'product_id', + ]) + ->setDefaults([ + 'language_id' => (int) $this->context->language->id, + 'input' => 'object', + 'error_bubbling' => false, + 'invalid_message' => $this->translator->l('Product combination with selected attributes does not exist.', self::TRANSLATION_SOURCE), + 'empty_data' => static function (FormInterface $form): ?array { + $data = array_map(static function (FormInterface $child) { + return $child->getData(); + }, $form->all()); + + $data = array_filter($data, static function ($attributeId): bool { + return null !== $attributeId; + }); + + if ([] === $data) { + return null; + } + + return $data; + }, + ]) + ->setAllowedTypes('product_id', 'int') + ->setAllowedTypes('language_id', 'int') + ->setAllowedValues('input', ['object', 'id']); + } + + private function createModelTransformer(ObjectRepositoryInterface $repository, array $options): DataTransformerInterface + { + $transformer = new CombinationToAttributeIdsTransformer($repository, $options['product_id']); + + if ('id' !== $options['input']) { + return $transformer; + } + + $modelToIdTransformer = new ObjectModelToIdTransformer($this->manager, \Combination::class, $options['language_id']); + + return new DataTransformerChain([ + new ReversedTransformer($modelToIdTransformer), + $transformer, + ]); + } + + /** + * @return CombinationRepository + */ + private function getRepository(): ObjectRepositoryInterface + { + return $this->repository ?? ($this->repository = $this->manager->getRepository(\Combination::class)); + } +} diff --git a/modules/inpostizi/src/Form/Type/Product/ProductRestrictionsType.php b/modules/inpostizi/src/Form/Type/Product/ProductRestrictionsType.php new file mode 100644 index 00000000..37ffb5ca --- /dev/null +++ b/modules/inpostizi/src/Form/Type/Product/ProductRestrictionsType.php @@ -0,0 +1,143 @@ +translator = $translator; + $this->context = $context; + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $categoryTreeType = class_exists(CategoryChoiceTreeType::class) + ? CategoryChoiceTreeType::class + : CategoryChoiceTreeTypePolyfill::class; + + $switchType = class_exists(SwitchType::class) + ? SwitchType::class + : SwitchTypePolyfill::class; + + $builder + ->add('blockOrder', $switchType, [ + 'empty_data' => false, + 'label' => $this->translator->l('Disallow orders', self::TRANSLATION_SOURCE), + 'help' => $this->translator->l('If enabled, placing an order via the mobile app will not be possible if the cart contains a product that meets any of the conditions below.', self::TRANSLATION_SOURCE), + ]) + ->add('productTypes', ChoiceType::class, [ + 'choices' => ProductType::cases(), + 'choice_value' => function (ProductType $productType): string { + return $productType->value; + }, + 'choice_label' => function (ProductType $productType): string { + return $productType->trans($this->translator); + }, + 'multiple' => true, + 'expanded' => true, + 'required' => false, + 'label' => $this->translator->l('Product type', self::TRANSLATION_SOURCE), + ]) + ->add('categoryIds', $categoryTreeType, [ + 'multiple' => true, + 'required' => false, + 'label' => $this->context->getTranslator()->trans('Default category', [], 'Admin.Catalog.Feature'), + 'empty_data' => [], + ]) + ->add('manufacturerIds', ObjectModelType::class, [ + 'class' => \Manufacturer::class, + 'input' => 'id', + 'multiple' => true, + 'expanded' => true, + 'required' => false, + 'label' => $this->context->getTranslator()->trans('Brand', [], 'Admin.Global'), + ]) + ->add('attributeGroupIds', ObjectModelType::class, [ + 'class' => \AttributeGroup::class, + 'input' => 'id', + 'multiple' => true, + 'expanded' => true, + 'required' => false, + 'label' => $this->context->getTranslator()->trans('Attribute group', [], 'Admin.Catalog.Feature'), + 'help' => $this->translator->l('Widget will not be render if the product combination has an attribute from the selected groups.', self::TRANSLATION_SOURCE), + ]) + ->add('featureIds', ObjectModelType::class, [ + 'class' => \Feature::class, + 'input' => 'id', + 'multiple' => true, + 'expanded' => true, + 'required' => false, + 'label' => $this->context->getTranslator()->trans('Feature', [], 'Admin.Catalog.Feature'), + 'help' => $this->translator->l('Widget will not be render if the product has any of the selected features.', self::TRANSLATION_SOURCE), + ]) + ->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event): void { + $form = $event->getForm(); + + $categoryIdsConfig = $form->get('categoryIds')->getConfig(); + $type = get_class($categoryIdsConfig->getType()->getInnerType()); + + $options = $categoryIdsConfig->getOptions(); + $options['constraints'][] = new Choice([ + 'choices' => $this->flattenCategoryIdChoices($options['choices_tree'], $options['choice_value'], $options['choice_children']), + 'multiple' => $options['multiple'], + 'strict' => true, + ]); + + $form->add('categoryIds', $type, $options); + }); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => ProductRestrictions::class, + ]); + } + + private function flattenCategoryIdChoices(array $choicesTree, string $choiceValueName, string $choiceChildrenName): array + { + $choices = []; + + foreach ($choicesTree as $choice) { + $choices[] = [(string) $choice[$choiceValueName]]; + if ([] === ($choice[$choiceChildrenName] ?? [])) { + continue; + } + + $choices[] = $this->flattenCategoryIdChoices($choice[$choiceChildrenName], $choiceValueName, $choiceChildrenName); + } + + return array_merge(...$choices); + } +} diff --git a/modules/inpostizi/src/Form/Type/ProductConfigurationType.php b/modules/inpostizi/src/Form/Type/ProductConfigurationType.php new file mode 100644 index 00000000..0f1b9dcb --- /dev/null +++ b/modules/inpostizi/src/Form/Type/ProductConfigurationType.php @@ -0,0 +1,48 @@ +translator = $translator; + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + ->add('normalImageTypeId', ImageTypeChoiceType::class, [ + 'label' => $this->translator->l('Product list image type', self::TRANSLATION_SOURCE), + 'help' => $this->translator->l('This image format will be used in the product list in the application.', self::TRANSLATION_SOURCE), + ]) + ->add('smallImageTypeId', ImageTypeChoiceType::class, [ + 'label' => $this->translator->l('Detail image type', self::TRANSLATION_SOURCE), + 'help' => $this->translator->l('This image format will be used in the product details info in the application.', self::TRANSLATION_SOURCE), + ]) + ->add('largeImageTypeId', ImageTypeChoiceType::class, [ + 'label' => $this->translator->l('Large image type', self::TRANSLATION_SOURCE), + 'help' => $this->translator->l('This image format will be used in the image zoom in the application.', self::TRANSLATION_SOURCE), + ]); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => ProductConfiguration::class, + ]); + } +} diff --git a/modules/inpostizi/src/Form/Type/Shipping/CarrierChoiceType.php b/modules/inpostizi/src/Form/Type/Shipping/CarrierChoiceType.php new file mode 100644 index 00000000..c8808520 --- /dev/null +++ b/modules/inpostizi/src/Form/Type/Shipping/CarrierChoiceType.php @@ -0,0 +1,43 @@ +choiceLoader = $choiceLoader; + } + + public function getParent(): string + { + return ChoiceType::class; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'choice_loader' => $this->choiceLoader, + 'choice_value' => 'id_reference', + 'choice_label' => function (\Carrier $carrier): string { + return $carrier->name ?? ''; + }, + ]); + } +} diff --git a/modules/inpostizi/src/Form/Type/Shipping/CarrierMappingType.php b/modules/inpostizi/src/Form/Type/Shipping/CarrierMappingType.php new file mode 100644 index 00000000..0e847a0c --- /dev/null +++ b/modules/inpostizi/src/Form/Type/Shipping/CarrierMappingType.php @@ -0,0 +1,46 @@ +add('referenceId', CarrierChoiceType::class, [ + 'required' => false, + 'label' => false, + 'placeholder' => '--', + ]); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver + ->setDefaults([ + 'data_class' => CarrierMapping::class, + 'empty_data' => function (Options $options) { + return new CarrierMapping(null, $options['service_codes']); + }, + 'service_codes' => [], + ]) + ->setAllowedTypes('service_codes', ['array']) // Sf 2.8 resolver does not understand "FQCN[]" syntax => use normalizer instead + ->setNormalizer('service_codes', static function (Options $options, array $value) { + return array_map(static function ($value) { + if ($value instanceof ServiceCode) { + return $value; + } + + return ServiceCode::from($value); + }, $value); + }); + } +} diff --git a/modules/inpostizi/src/Form/Type/Shipping/CarrierMappingsType.php b/modules/inpostizi/src/Form/Type/Shipping/CarrierMappingsType.php new file mode 100644 index 00000000..32c769ca --- /dev/null +++ b/modules/inpostizi/src/Form/Type/Shipping/CarrierMappingsType.php @@ -0,0 +1,133 @@ +translator = $translator; + $this->serviceNameTranslator = $serviceNameTranslator; + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + foreach (ServiceCode::getAvailableCombinations($options['delivery_type']) as $serviceCodes) { + $name = $this->getChildName(...$serviceCodes); + + $builder->add($name, CarrierMappingType::class, [ + 'service_codes' => $serviceCodes, + 'label' => $this->getChildLabel(...$serviceCodes), + ]); + } + + $builder->setDataMapper($this); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver + ->setRequired([ + 'delivery_type', + ]) + ->setAllowedTypes('delivery_type', DeliveryType::class); + } + + public function mapDataToForms($viewData, $forms): void + { + if (null === $viewData) { + return; + } + + if (!is_array($viewData)) { + throw new UnexpectedTypeException($viewData, 'array'); + } + + foreach ($forms as $form) { + $data = $this->getCarrierMappingByServiceCodes( + $viewData, + $form->getConfig()->getOption('service_codes') + ); + + $form->setData($data); + } + } + + public function mapFormsToData($forms, &$viewData): void + { + $viewData = []; + + foreach ($forms as $form) { + $viewData[] = $form->getData(); + } + } + + private function getChildName(ServiceCode ...$serviceCodes): string + { + if ([] === $serviceCodes) { + return 'default'; + } + + return implode(':', array_map(static function (ServiceCode $serviceCode): string { + return $serviceCode->value; + }, $serviceCodes)); + } + + private function getChildLabel(ServiceCode ...$serviceCodes): string + { + $label = $this->translator->l('Carrier mapping', self::TRANSLATION_SOURCE); + + if ([] === $serviceCodes) { + return $label; + } + + return sprintf('%s (%s)', $label, implode(' + ', array_map(function (ServiceCode $serviceCode): string { + return $this->serviceNameTranslator->getName($serviceCode); + }, $serviceCodes))); + } + + /** + * @param CarrierMapping[] $mappings + * @param ServiceCode[] $serviceCodes + */ + private function getCarrierMappingByServiceCodes(array $mappings, array $serviceCodes): ?CarrierMapping + { + foreach ($mappings as $mapping) { + $mappingServiceCodes = $mapping->getServiceCodes(); + + $diff = array_udiff($serviceCodes, $mappingServiceCodes, [Enum::class, 'compareValues']); + + if ([] === $diff && count($mappingServiceCodes) === count($serviceCodes)) { + return $mapping; + } + } + + return null; + } +} diff --git a/modules/inpostizi/src/Form/Type/Shipping/OptionalServicesType.php b/modules/inpostizi/src/Form/Type/Shipping/OptionalServicesType.php new file mode 100644 index 00000000..69363a08 --- /dev/null +++ b/modules/inpostizi/src/Form/Type/Shipping/OptionalServicesType.php @@ -0,0 +1,97 @@ +serviceNameTranslator = $serviceNameTranslator; + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + /** @var DeliveryType $deliveryType */ + $deliveryType = $options['delivery_type']; + + foreach ($deliveryType->getAvailableServiceCodes() as $serviceCode) { + $name = $serviceCode->value; + + $builder->add($name, ServiceOptionsType::class, [ + 'service_code' => $serviceCode, + 'label' => $this->serviceNameTranslator->getName($serviceCode), + ]); + } + + $builder->setDataMapper($this); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver + ->setRequired([ + 'delivery_type', + ]) + ->setAllowedTypes('delivery_type', DeliveryType::class); + } + + public function mapDataToForms($viewData, $forms): void + { + if (null === $viewData) { + return; + } + + if (!is_array($viewData)) { + throw new UnexpectedTypeException($viewData, 'array'); + } + + foreach ($forms as $form) { + $data = $this->getServiceOptionsByCode( + $viewData, + $form->getConfig()->getOption('service_code') + ); + + $form->setData($data); + } + } + + public function mapFormsToData($forms, &$viewData): void + { + $viewData = []; + + foreach ($forms as $form) { + $viewData[] = $form->getData(); + } + } + + /** + * @param ServiceOptions[] $options + */ + private function getServiceOptionsByCode(array $options, ServiceCode $serviceCode): ?ServiceOptions + { + foreach ($options as $serviceOptions) { + if ($serviceCode === $serviceOptions->getServiceCode()) { + return $serviceOptions; + } + } + + return null; + } +} diff --git a/modules/inpostizi/src/Form/Type/Shipping/ServiceOptionsType.php b/modules/inpostizi/src/Form/Type/Shipping/ServiceOptionsType.php new file mode 100644 index 00000000..99e69477 --- /dev/null +++ b/modules/inpostizi/src/Form/Type/Shipping/ServiceOptionsType.php @@ -0,0 +1,74 @@ +translator = $translator; + $this->context = $context ?? \Context::getContext(); + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder->add('additionalCost', MoneyType::class, [ + 'required' => false, + 'currency' => Currency::Pln()->value, + 'scale' => 2, + 'label' => $this->translator->l('Additional cost', self::TRANSLATION_SOURCE), + 'help' => nl2br(implode("\n", [ + $this->translator->l('Net value of the amount to be added to the carrier\'s price if the service is selected.', self::TRANSLATION_SOURCE), + sprintf($this->translator->l('Cost will not be applied if the "%s" option is not enabled for the carrier.', self::TRANSLATION_SOURCE), $this->context->getTranslator()->trans('Add handling costs', [], 'Admin.Shipping.Feature')), + ])), + ]); + + /** @var ServiceCode $serviceCode */ + $serviceCode = $options['service_code']; + + if (!$serviceCode->isAvailabilityTimeDependent()) { + return; + } + + $builder->add('availabilityRange', TimeOfWeekRangeType::class, [ + 'label' => false, + ]); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver + ->setDefaults([ + 'data_class' => ServiceOptions::class, + 'empty_data' => function (Options $options) { + return new ServiceOptions($options['service_code']); + }, + ]) + ->setRequired(['service_code']) + ->setAllowedTypes('service_code', ServiceCode::class); + } +} diff --git a/modules/inpostizi/src/Form/Type/Shipping/ShippingOptionsType.php b/modules/inpostizi/src/Form/Type/Shipping/ShippingOptionsType.php new file mode 100644 index 00000000..732bd4c5 --- /dev/null +++ b/modules/inpostizi/src/Form/Type/Shipping/ShippingOptionsType.php @@ -0,0 +1,39 @@ +add('carrierMappings', CarrierMappingsType::class, [ + 'delivery_type' => $options['delivery_type'], + 'label' => false, + ]) + ->add('optionalServices', OptionalServicesType::class, [ + 'delivery_type' => $options['delivery_type'], + 'label' => false, + ]); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver + ->setDefaults([ + 'data_class' => ShippingOptions::class, + ]) + ->setRequired([ + 'delivery_type', + ]) + ->setAllowedTypes('delivery_type', DeliveryType::class); + } +} diff --git a/modules/inpostizi/src/Form/Type/Shipping/TimeOfWeekRangeType.php b/modules/inpostizi/src/Form/Type/Shipping/TimeOfWeekRangeType.php new file mode 100644 index 00000000..7c020f77 --- /dev/null +++ b/modules/inpostizi/src/Form/Type/Shipping/TimeOfWeekRangeType.php @@ -0,0 +1,43 @@ +translator = $translator; + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + ->add('start', TimeOfWeekType::class, [ + 'label' => $this->translator->l('Available from', self::TRANSLATION_SOURCE), + 'help' => $this->translator->l('inclusive', self::TRANSLATION_SOURCE), + ]) + ->add('end', TimeOfWeekType::class, [ + 'label' => $this->translator->l('Available to', self::TRANSLATION_SOURCE), + 'help' => $this->translator->l('exclusive', self::TRANSLATION_SOURCE), + ]); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => TimeOfWeekRange::class, + ]); + } +} diff --git a/modules/inpostizi/src/Form/Type/Shipping/TimeOfWeekType.php b/modules/inpostizi/src/Form/Type/Shipping/TimeOfWeekType.php new file mode 100644 index 00000000..24a2419d --- /dev/null +++ b/modules/inpostizi/src/Form/Type/Shipping/TimeOfWeekType.php @@ -0,0 +1,33 @@ +add('weekDay', WeekDayChoiceType::class, [ + 'label' => false, + ]) + ->add('time', TimeType::class, [ + 'label' => false, + 'input' => 'datetime_immutable', + ]); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => TimeOfWeek::class, + ]); + } +} diff --git a/modules/inpostizi/src/Form/Type/Shipping/WeekDayChoiceType.php b/modules/inpostizi/src/Form/Type/Shipping/WeekDayChoiceType.php new file mode 100644 index 00000000..064dbbd7 --- /dev/null +++ b/modules/inpostizi/src/Form/Type/Shipping/WeekDayChoiceType.php @@ -0,0 +1,50 @@ +translator = $translator; + } + + public function getParent(): string + { + return ChoiceType::class; + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder->addModelTransformer(new EnumDataTransformer(WeekDay::class)); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'choices' => [ + $this->translator->l('Monday', self::TRANSLATION_SOURCE) => WeekDay::Monday()->value, + $this->translator->l('Tuesday', self::TRANSLATION_SOURCE) => WeekDay::Tuesday()->value, + $this->translator->l('Wednesday', self::TRANSLATION_SOURCE) => WeekDay::Wednesday()->value, + $this->translator->l('Thursday', self::TRANSLATION_SOURCE) => WeekDay::Thursday()->value, + $this->translator->l('Friday', self::TRANSLATION_SOURCE) => WeekDay::Friday()->value, + $this->translator->l('Saturday', self::TRANSLATION_SOURCE) => WeekDay::Saturday()->value, + $this->translator->l('Sunday', self::TRANSLATION_SOURCE) => WeekDay::Sunday()->value, + ], + ]); + } +} diff --git a/modules/inpostizi/src/Form/Type/ShippingConfigurationType.php b/modules/inpostizi/src/Form/Type/ShippingConfigurationType.php new file mode 100644 index 00000000..d9b0dbb2 --- /dev/null +++ b/modules/inpostizi/src/Form/Type/ShippingConfigurationType.php @@ -0,0 +1,45 @@ +translator = $translator; + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + ->add('courierShippingOptions', ShippingOptionsType::class, [ + 'label' => $this->translator->l('Courier', self::TRANSLATION_SOURCE), + 'delivery_type' => DeliveryType::Courier(), + ]) + ->add('apmShippingOptions', ShippingOptionsType::class, [ + 'label' => $this->translator->l('Parcel Locker', self::TRANSLATION_SOURCE), + 'delivery_type' => DeliveryType::Apm(), + ]); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => ShippingConfiguration::class, + ]); + } +} diff --git a/modules/inpostizi/src/Form/Type/SwitchType.php b/modules/inpostizi/src/Form/Type/SwitchType.php new file mode 100644 index 00000000..32d00581 --- /dev/null +++ b/modules/inpostizi/src/Form/Type/SwitchType.php @@ -0,0 +1,108 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +declare(strict_types=1); + +namespace izi\prestashop\Form\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * Polyfill for PS < 1.7.4. + * + * @see \PrestaShopBundle\Form\Admin\Type\SwitchType + */ +final class SwitchType extends AbstractType +{ + public const TRANS_DOMAIN = 'Admin.Global'; + + /** + * {@inheritdoc} + */ + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'choices' => [ + 'No' => false, + 'Yes' => true, + ], + 'show_choices' => true, + // Force label and switch to be displayed on the same line (mainly useful for base ui kit) + 'inline_switch' => false, + 'multiple' => false, + 'expanded' => false, + 'disabled' => false, + 'choice_translation_domain' => self::TRANS_DOMAIN, + ]); + $resolver->setAllowedTypes('disabled', 'bool'); + } + + /** + * {@inheritdoc} + */ + public function buildView(FormView $view, FormInterface $form, array $options): void + { + if (true === $options['disabled']) { + $view->vars['disabled'] = true; + } + $view->vars['attr']['class'] = 'ps-switch'; + if (isset($options['attr']['class'])) { + $view->vars['attr']['class'] .= ' ' . $options['attr']['class']; + } + $view->vars['show_choices'] = $options['show_choices']; + + // Add a class when inline mode is enabled + if ($options['inline_switch']) { + $rowAttributes = $options['row_attr'] ?? []; + if (!empty($rowAttributes['class'])) { + $rowAttributes['class'] .= ' inline-switch-widget'; + } else { + $rowAttributes['class'] = 'inline-switch-widget'; + } + $view->vars['row_attr'] = $rowAttributes; + } + } + + /** + * {@inheritdoc} + */ + public function getParent(): string + { + return ChoiceType::class; + } + + /** + * {@inheritdoc} + */ + public function getBlockPrefix(): string + { + return 'inpostizi_switch'; + } +} diff --git a/modules/inpostizi/src/Form/Type/TranslatableType.php b/modules/inpostizi/src/Form/Type/TranslatableType.php new file mode 100644 index 00000000..c29f7890 --- /dev/null +++ b/modules/inpostizi/src/Form/Type/TranslatableType.php @@ -0,0 +1,272 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +namespace izi\prestashop\Form\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormError; +use Symfony\Component\Form\FormErrorIterator; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * Polyfill for PS < 1.7.6. + * + * @see \PrestaShopBundle\Form\Admin\Type\TranslatableType + */ +class TranslatableType extends AbstractType +{ + /** + * @var array List of enabled locales + */ + private $enabledLocales; + + /** + * @var array List of all available locales + */ + private $availableLocales; + + /** + * @var int default form language ID + */ + private $defaultFormLanguageId; + + public function __construct(?\Context $context = null, ?array $availableLocales = null) + { + $context = $context ?? \Context::getContext(); + + $this->availableLocales = $availableLocales ?? \Language::getLanguages(false); + $this->enabledLocales = $this->filterEnabledLocales($this->availableLocales); + $this->defaultFormLanguageId = $context->language->id; + } + + /** + * {@inheritdoc} + */ + public function buildForm(FormBuilderInterface $builder, array $options): void + { + foreach ($options['locales'] as $locale) { + $typeOptions = $options['options']; + $typeOptions['label'] = $locale['iso_code']; + + if (!isset($typeOptions['required'])) { + $typeOptions['required'] = false; + } + + $builder->add($locale['id_lang'], $options['type'], $typeOptions); + } + } + + /** + * {@inheritdoc} + */ + public function buildView(FormView $view, FormInterface $form, array $options): void + { + $errors = iterator_to_array($view->vars['errors']); + + $errorsByLocale = $this->getErrorsByLocale($form, $options['locales']); + + if ($errorsByLocale !== null) { + foreach ($errorsByLocale as $errorByLocale) { + $errors[] = new FormError(sprintf('%s: %s', $errorByLocale['locale_name'], $errorByLocale['error_message'])); + } + } + + /** @var FormInterface $varsForm */ + $varsForm = $view->vars['errors']->getForm(); + $view->vars['errors'] = new FormErrorIterator($varsForm, $errors); + $view->vars['locales'] = $options['locales']; + $view->vars['default_locale'] = $this->getDefaultLocale($options['locales']); + $view->vars['hide_locales'] = 1 >= count($options['locales']); + $view->vars['use_tabs'] = !empty($options['use_tabs']) && !empty($options['use_dropdown']); + } + + /** + * {@inheritdoc} + */ + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'type' => TextType::class, + 'options' => [], + 'error_bubbling' => false, + 'only_enabled_locales' => false, + 'locales' => function (Options $options) { + return $options['only_enabled_locales'] + ? $this->enabledLocales + : $this->availableLocales; + }, + // These two options allow to override the default choice of the component between tab and dropdown (by + // default it is based on input type being a textarea) + 'use_tabs' => null, + 'use_dropdown' => null, + ]); + + $resolver->setAllowedTypes('locales', 'array'); + $resolver->setAllowedTypes('options', 'array'); + $resolver->setAllowedTypes('type', 'string'); + $resolver->setAllowedTypes('error_bubbling', 'bool'); + $resolver->setAllowedTypes('use_tabs', ['null', 'bool']); + $resolver->setAllowedTypes('use_dropdown', ['null', 'bool']); + } + + /** + * {@inheritdoc} + */ + public function getBlockPrefix(): string + { + return 'inpostizi_translatable'; + } + + /** + * If there are more than one locale it gets nested errors and if found prepares the errors for usage in twig. + * If there are only one error which is not assigned to the default language then the error is being localised. + */ + private function getErrorsByLocale(FormInterface $form, array $locales): ?array + { + $formErrors = $form->getErrors(true); + + if (0 === $formErrors->count()) { + return null; + } + + if (1 === $formErrors->count()) { + $errorByLocale = $this->getSingleTranslatableErrorExcludingDefaultLocale( + $formErrors, + $form, + $locales + ); + + if (!$errorByLocale) { + return null; + } + + return [$errorByLocale]; + } + + return $this->getTranslatableErrors( + $formErrors, + $form, + $locales + ); + } + + /** + * Gets single error excluding the default locales error since for default locale a language name prefix is not + * required. + */ + private function getSingleTranslatableErrorExcludingDefaultLocale(FormErrorIterator $formErrors, FormInterface $form, array $locales): ?array + { + $errorByLocale = null; + $formError = $formErrors[0]; + $nonDefaultLanguageFormKey = null; + $iteration = 0; + + foreach ($form as $formItem) { + if ($this->doesErrorFormAndCurrentFormMatches($formError->getOrigin(), $formItem)) { + $nonDefaultLanguageFormKey = $iteration; + + break; + } + + ++$iteration; + } + + if (isset($locales[$nonDefaultLanguageFormKey])) { + $errorByLocale = [ + 'locale_name' => $locales[$nonDefaultLanguageFormKey]['name'], + 'error_message' => $formError->getMessage(), + ]; + } + + return $errorByLocale; + } + + /** + * Gets translatable errors ready for popover display and assigned to each language. + */ + private function getTranslatableErrors(FormErrorIterator $formErrors, FormInterface $form, array $locales): ?array + { + $errorsByLocale = null; + $iteration = 0; + foreach ($form as $formItem) { + $doesLocaleExistForInvalidForm = isset($locales[$iteration]) + && $formItem->isSubmitted() + && !$formItem->isValid(); + + if ($doesLocaleExistForInvalidForm) { + foreach ($formErrors as $formError) { + if ($this->doesErrorFormAndCurrentFormMatches($formError->getOrigin(), $formItem)) { + $errorsByLocale[] = [ + 'locale_name' => $locales[$iteration]['name'], + 'error_message' => $formError->getMessage(), + ]; + } + } + } + + ++$iteration; + } + + return $errorsByLocale; + } + + /** + * Determines if the error form matches the given form. Used for mapping the locales for the form fields. + */ + private function doesErrorFormAndCurrentFormMatches(FormInterface $errorForm, FormInterface $currentForm): bool + { + return $errorForm === $currentForm; + } + + /** + * Get default locale. + */ + private function getDefaultLocale(array $locales): array + { + foreach ($locales as $locale) { + if ($this->defaultFormLanguageId === (int) $locale['id_lang']) { + return $locale; + } + } + + return reset($locales); + } + + /** + * Filters only enabled locales + */ + private function filterEnabledLocales(array $availableLocales): array + { + return array_filter($availableLocales, static function (array $locale): bool { + return $locale['active']; + }); + } +} diff --git a/modules/inpostizi/src/Form/Type/Widget/HtmlStylesType.php b/modules/inpostizi/src/Form/Type/Widget/HtmlStylesType.php new file mode 100644 index 00000000..08988c04 --- /dev/null +++ b/modules/inpostizi/src/Form/Type/Widget/HtmlStylesType.php @@ -0,0 +1,69 @@ +translator = $translator; + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + ->add('marginTop', IntegerType::class, [ + 'required' => false, + 'label' => $this->translator->l('Margin top', self::TRANSLATION_SOURCE), + 'unit' => 'px', + ]) + ->add('marginLeft', IntegerType::class, [ + 'required' => false, + 'label' => $this->translator->l('Margin left', self::TRANSLATION_SOURCE), + 'unit' => 'px', + ]) + ->add('marginRight', IntegerType::class, [ + 'required' => false, + 'label' => $this->translator->l('Margin right', self::TRANSLATION_SOURCE), + 'unit' => 'px', + ]) + ->add('marginBottom', IntegerType::class, [ + 'required' => false, + 'label' => $this->translator->l('Margin bottom', self::TRANSLATION_SOURCE), + 'unit' => 'px', + ]) + ->add('justifyContent', ChoiceType::class, [ + 'choices' => [ + $this->translator->l('Left', self::TRANSLATION_SOURCE) => 'start', + $this->translator->l('Center', self::TRANSLATION_SOURCE) => 'center', + $this->translator->l('Right', self::TRANSLATION_SOURCE) => 'end', + ], + 'label' => $this->translator->l('Alignment', 'widgetconfigurationtype'), + 'help' => $this->translator->l('Specifies the orientation of the widget in the space available for it. If your template allocates a narrow space for the widget the setting will not affect the appearance.', 'widgetconfigurationtype'), + 'attr' => [ + 'class' => 'js-widget-attribute-provider', + ], + ]); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => HtmlStyles::class, + ]); + } +} diff --git a/modules/inpostizi/src/Form/Type/Widget/ProductPageDisplayConfigurationType.php b/modules/inpostizi/src/Form/Type/Widget/ProductPageDisplayConfigurationType.php new file mode 100644 index 00000000..bc2dfa2b --- /dev/null +++ b/modules/inpostizi/src/Form/Type/Widget/ProductPageDisplayConfigurationType.php @@ -0,0 +1,65 @@ +translator = $translator; + } + + public function buildView(FormView $view, FormInterface $form, array $options): void + { + $view->vars['binding_place'] = BindingPlace::ProductCard(); + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + ->add('displayConfiguration', WidgetDisplayConfigurationType::class, [ + 'label' => $options['label'], + 'description' => $options['description'], + 'binding_place' => BindingPlace::ProductCard(), + ]) + ->add('productRestrictions', ProductRestrictionsType::class, [ + 'label' => $this->translator->l('Product restrictions', self::TRANSLATION_SOURCE), + 'help' => $this->translator->l('Widget will not be rendered on the product page if the product matches any of the below conditions.', self::TRANSLATION_SOURCE), + ]); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver + ->setDefaults([ + 'data_class' => ProductPageDisplayConfiguration::class, + 'empty_data' => static function () { + $displayConfiguration = WidgetDisplayConfiguration::for(BindingPlace::ProductCard()); + + return new ProductPageDisplayConfiguration($displayConfiguration); + }, + 'description' => '', + ]) + ->setAllowedTypes('description', 'string'); + } +} diff --git a/modules/inpostizi/src/Form/Type/Widget/WidgetConfigurationType.php b/modules/inpostizi/src/Form/Type/Widget/WidgetConfigurationType.php new file mode 100644 index 00000000..2d98d274 --- /dev/null +++ b/modules/inpostizi/src/Form/Type/Widget/WidgetConfigurationType.php @@ -0,0 +1,89 @@ +translator = $translator; + } + + public function buildView(FormView $view, FormInterface $form, array $options): void + { + $view->vars['preview_container_styles'] = $options['preview_container_styles']; + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + ->add('darkMode', ChoiceType::class, [ + 'label' => $this->translator->l('Background', self::TRANSLATION_SOURCE), + 'help' => $this->translator->l('Determines whether the widget is on a light or dark background in your store. The setting affects the font color, make sure it is visible.', self::TRANSLATION_SOURCE), + 'choices' => [ + $this->translator->l('Light', self::TRANSLATION_SOURCE) => false, + $this->translator->l('Dark', self::TRANSLATION_SOURCE) => true, + ], + 'attr' => [ + 'class' => 'js-widget-attribute-provider', + ], + ]) + ->add('variant', WidgetVariantChoiceType::class, [ + 'label' => $this->translator->l('Variant', self::TRANSLATION_SOURCE), + 'help' => $this->translator->l('The widget is available in 2 color variants. Choose the one more suitable for your store\'s color scheme.', self::TRANSLATION_SOURCE), + 'attr' => [ + 'class' => 'js-widget-attribute-provider', + ], + ]) + ->add('frameStyle', WidgetFrameStyleChoiceType::class, [ + 'label' => $this->translator->l('Frame style', self::TRANSLATION_SOURCE), + 'attr' => [ + 'class' => 'js-widget-attribute-provider', + ], + ]) + ->add('size', WidgetSizeChoiceType::class, [ + 'label' => $this->translator->l('Size', self::TRANSLATION_SOURCE), + 'attr' => [ + 'class' => 'js-widget-attribute-provider', + ], + ]) + ->add('maxWidthPx', IntegerType::class, [ + 'required' => false, + 'label' => $this->translator->l('Max width', self::TRANSLATION_SOURCE), + 'attr' => [ + 'class' => 'js-widget-attribute-provider', + ], + 'unit' => 'px', + ]); + + // kept for \AdminTranslationsController translatable message discovery + // ->l('Alignment', self::TRANSLATION_SOURCE) + // ->l('Specifies the orientation of the widget in the space available for it. If your template allocates a narrow space for the widget the setting will not affect the appearance.', self::TRANSLATION_SOURCE) + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver + ->setDefaults([ + 'data_class' => WidgetConfiguration::class, + 'preview_container_styles' => [], + ]) + ->setAllowedTypes('preview_container_styles', 'iterable'); + } +} diff --git a/modules/inpostizi/src/Form/Type/Widget/WidgetDisplayConfigurationType.php b/modules/inpostizi/src/Form/Type/Widget/WidgetDisplayConfigurationType.php new file mode 100644 index 00000000..fcf4c225 --- /dev/null +++ b/modules/inpostizi/src/Form/Type/Widget/WidgetDisplayConfigurationType.php @@ -0,0 +1,85 @@ +translator = $translator; + } + + public function buildView(FormView $view, FormInterface $form, array $options): void + { + $view->vars['description'] = $options['description']; + $view->vars['binding_place'] = $options['binding_place']; + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $switchClass = class_exists(SwitchType::class) + ? SwitchType::class + : SwitchTypePolyfill::class; + + $builder + ->add('displayed', $switchClass, [ + 'required' => false, + 'label' => $this->translator->l('Displayed', self::TRANSLATION_SOURCE), + 'help' => $this->translator->l('In order to increase conversions, we recommend displaying InPost Pay on both the shopping cart tab and the product tab.', self::TRANSLATION_SOURCE), + ]) + ->add('htmlStyles', HtmlStylesType::class, [ + 'label' => false, + ]) + ->addEventListener(FormEvents::PRE_SET_DATA, static function (FormEvent $event) { + $data = $event->getData(); + $event->getForm()->add('widgetConfiguration', WidgetConfigurationType::class, [ + 'label' => false, + 'preview_container_styles' => $data instanceof WidgetDisplayConfiguration ? $data->getHtmlStyles() : [], + ]); + }); + + $builder->get('htmlStyles')->addEventListener(FormEvents::POST_SUBMIT, static function (FormEvent $event) { + $data = $event->getData(); + $event->getForm()->getParent()->add('widgetConfiguration', WidgetConfigurationType::class, [ + 'label' => false, + 'preview_container_styles' => $data ?? [], + ]); + }); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver + ->setRequired('binding_place') + ->setDefaults([ + 'data_class' => WidgetDisplayConfiguration::class, + 'empty_data' => static function (FormInterface $form) { + $bindingPlace = $form->getConfig()->getOption('binding_place'); + + return WidgetDisplayConfiguration::for($bindingPlace); + }, + 'description' => '', + ]) + ->setAllowedTypes('binding_place', BindingPlace::class) + ->setAllowedTypes('description', 'string'); + } +} diff --git a/modules/inpostizi/src/Form/Type/Widget/WidgetFrameStyleChoiceType.php b/modules/inpostizi/src/Form/Type/Widget/WidgetFrameStyleChoiceType.php new file mode 100644 index 00000000..d3c09bd4 --- /dev/null +++ b/modules/inpostizi/src/Form/Type/Widget/WidgetFrameStyleChoiceType.php @@ -0,0 +1,47 @@ +translator = $translator; + } + + public function getParent(): string + { + return ChoiceType::class; + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder->addModelTransformer(new EnumDataTransformer(FrameStyle::class)); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'choices' => [ + $this->translator->l('Rounded', self::TRANSLATION_SOURCE) => FrameStyle::Rounded()->value, + $this->translator->l('Round', self::TRANSLATION_SOURCE) => FrameStyle::Round()->value, + ], + 'required' => false, + 'placeholder' => $this->translator->l('Rectangular', self::TRANSLATION_SOURCE), + ]); + } +} diff --git a/modules/inpostizi/src/Form/Type/Widget/WidgetSizeChoiceType.php b/modules/inpostizi/src/Form/Type/Widget/WidgetSizeChoiceType.php new file mode 100644 index 00000000..48d45ae4 --- /dev/null +++ b/modules/inpostizi/src/Form/Type/Widget/WidgetSizeChoiceType.php @@ -0,0 +1,54 @@ +translator = $translator; + } + + public function getParent(): string + { + return ChoiceType::class; + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder->addModelTransformer(new CallbackTransformer(static function ($value) { + return $value ?? Size::Large(); + }, static function ($value) { + return $value; + })); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'choices' => Size::cases(), + 'choice_value' => static function (?Size $size): string { + if (null === $size) { + return ''; + } + + return $size->value; + }, + 'choice_label' => function (Size $size): string { + return $size->trans($this->translator); + }, + ]); + } +} diff --git a/modules/inpostizi/src/Form/Type/Widget/WidgetVariantChoiceType.php b/modules/inpostizi/src/Form/Type/Widget/WidgetVariantChoiceType.php new file mode 100644 index 00000000..bc8f72a0 --- /dev/null +++ b/modules/inpostizi/src/Form/Type/Widget/WidgetVariantChoiceType.php @@ -0,0 +1,45 @@ +translator = $translator; + } + + public function getParent(): string + { + return ChoiceType::class; + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder->addModelTransformer(new EnumDataTransformer(Variant::class)); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'choices' => [ + $this->translator->l('Yellow', self::TRANSLATION_SOURCE) => Variant::Primary()->value, + $this->translator->l('Black', self::TRANSLATION_SOURCE) => Variant::Secondary()->value, + ], + ]); + } +} diff --git a/modules/inpostizi/src/Form/TypeExtension/ChoicesAsValuesTypeExtension.php b/modules/inpostizi/src/Form/TypeExtension/ChoicesAsValuesTypeExtension.php new file mode 100644 index 00000000..1657fc49 --- /dev/null +++ b/modules/inpostizi/src/Form/TypeExtension/ChoicesAsValuesTypeExtension.php @@ -0,0 +1,31 @@ +isDefined('choices_as_values')) { + return; + } + + $resolver->setDefault('choices_as_values', true); + } +} diff --git a/modules/inpostizi/src/Form/TypeExtension/DatePickerCompatibilityTypeExtension.php b/modules/inpostizi/src/Form/TypeExtension/DatePickerCompatibilityTypeExtension.php new file mode 100644 index 00000000..aa97e659 --- /dev/null +++ b/modules/inpostizi/src/Form/TypeExtension/DatePickerCompatibilityTypeExtension.php @@ -0,0 +1,51 @@ +vars['date_format'])) { + return; + } + + $view->vars['attr']['data-format'] = $options['date_format']; + } + + public function configureOptions(OptionsResolver $resolver): void + { + if ($resolver->isDefined('date_format')) { + return; + } + + $resolver + ->setDefaults([ + 'date_format' => 'YYYY-MM-DD', + ]) + ->setAllowedTypes('date_format', 'string'); + } +} diff --git a/modules/inpostizi/src/Form/TypeExtension/DateTimeImmutableTimeTypeExtension.php b/modules/inpostizi/src/Form/TypeExtension/DateTimeImmutableTimeTypeExtension.php new file mode 100644 index 00000000..781ee703 --- /dev/null +++ b/modules/inpostizi/src/Form/TypeExtension/DateTimeImmutableTimeTypeExtension.php @@ -0,0 +1,50 @@ +addModelTransformer(new TransformerPolyfill(), true); + } + + public function configureOptions(OptionsResolver $resolver): void + { + if (class_exists(DateTimeImmutableToDateTimeTransformer::class)) { + return; + } + + $resolver->addAllowedValues('input', [ + 'datetime_immutable', + ]); + } +} diff --git a/modules/inpostizi/src/Form/TypeExtension/HelpTextExtension.php b/modules/inpostizi/src/Form/TypeExtension/HelpTextExtension.php new file mode 100644 index 00000000..85e354b9 --- /dev/null +++ b/modules/inpostizi/src/Form/TypeExtension/HelpTextExtension.php @@ -0,0 +1,47 @@ +vars['help'] = $options['help'] ?? null; + } + + public function configureOptions(OptionsResolver $resolver): void + { + if ($resolver->isDefined('help')) { + return; + } + + $resolver + ->setDefaults([ + 'help' => null, + ]) + ->setAllowedTypes('help', ['null', 'string']); + } +} diff --git a/modules/inpostizi/src/Form/TypeExtension/UnitTypeExtension.php b/modules/inpostizi/src/Form/TypeExtension/UnitTypeExtension.php new file mode 100644 index 00000000..2bf3e417 --- /dev/null +++ b/modules/inpostizi/src/Form/TypeExtension/UnitTypeExtension.php @@ -0,0 +1,47 @@ +vars['unit'] = $options['unit']; + } + } + + public function configureOptions(OptionsResolver $resolver): void + { + if ($resolver->isDefined('unit')) { + return; + } + + $resolver + ->setDefined('unit') + ->setAllowedTypes('unit', 'string'); + } +} diff --git a/modules/inpostizi/src/Handler/CommandHandlerTrait.php b/modules/inpostizi/src/Handler/CommandHandlerTrait.php new file mode 100644 index 00000000..6e69e697 --- /dev/null +++ b/modules/inpostizi/src/Handler/CommandHandlerTrait.php @@ -0,0 +1,21 @@ +getMethod('__invoke'); + $parameters = $method->getParameters(); + + if ([] === $parameters || null === $commandClass = $parameters[0]->getClass()) { + throw new \LogicException(sprintf('Cannot determine handled command class for %s.', static::class)); + } + + return $commandClass->getName(); + } +} diff --git a/modules/inpostizi/src/Handler/Config/CheckStatusHandler.php b/modules/inpostizi/src/Handler/Config/CheckStatusHandler.php new file mode 100644 index 00000000..d0819196 --- /dev/null +++ b/modules/inpostizi/src/Handler/Config/CheckStatusHandler.php @@ -0,0 +1,38 @@ + + */ + private $statusCheckers; + + /** + * @param iterable $statusCheckers + */ + public function __construct(iterable $statusCheckers) + { + $this->statusCheckers = $statusCheckers; + } + + public function __invoke(CheckStatusCommand $command): ModuleStatus + { + $errors = []; + + foreach ($this->statusCheckers as $statusChecker) { + $errors[] = $statusChecker->checkStatus(); + } + + return new ModuleStatus(...array_merge(...$errors)); + } +} diff --git a/modules/inpostizi/src/Handler/Config/CheckStatusHandlerInterface.php b/modules/inpostizi/src/Handler/Config/CheckStatusHandlerInterface.php new file mode 100644 index 00000000..d0f90afd --- /dev/null +++ b/modules/inpostizi/src/Handler/Config/CheckStatusHandlerInterface.php @@ -0,0 +1,12 @@ +logsDirectory = $logsDirectory; + $this->module = $module; + $this->context = $context; + $this->objectManager = $objectManager; + } + + public function __invoke(DownloadModuleDataCommand $command): callable + { + return function () { + $options = new Archive(); + $options->setFlushOutput(true); + + $zip = new ZipStream(null, $options); + + foreach ($this->getLogFiles() as $file) { + $zip->addFileFromPath($file->getFilename(), $file->getRealPath()); + } + + $configInfo = $this->getConfigInformation(); + $zip->addFile('config.json', json_encode($configInfo, JSON_PRETTY_PRINT)); + + $zip->finish(); + }; + } + + /** + * @return iterable<\SplFileInfo> + */ + private function getLogFiles(): iterable + { + return Finder::create() + ->in($this->logsDirectory) + ->name('izi*.log') + ->files(); + } + + private function getConfigInformation(): array + { + return [ + 'php_version' => phpversion(), + 'prestashop_version' => _PS_VERSION_, + 'server_software' => $_SERVER['SERVER_SOFTWARE'] ?? null, + 'database_version' => $this->objectManager->getConnection()->getPlatformVersion(), + 'database_engine' => _MYSQL_ENGINE_, + 'theme' => $this->context->shop->theme_name, + 'module_version' => $this->module->version, + 'module_hooks' => $this->getRegisteredHooks(), + 'module_config' => $this->getModuleConfiguration(), + ]; + } + + private function getRegisteredHooks(): array + { + /** @var HookRepository $repository */ + $repository = $this->objectManager->getRepository(\Hook::class); + + return array_map(static function (\Hook $hook): string { + return $hook->name; + }, $repository->findByModuleId((int) $this->module->id)); + } + + private function getModuleConfiguration(): array + { + $config = []; + + /** @var ConfigurationRepository $repository */ + $repository = $this->objectManager->getRepository(\Configuration::class); + $cachePrefix = ConfigurationCache::getConfigKeyPrefix(); + + foreach ($repository->findByNamePrefix('INPOST_PAY') as $configuration) { + if (0 === strpos($configuration->name, $cachePrefix)) { + continue; + } + + if (ApiConfiguration::ACCESS_TOKEN === $configuration->name) { + continue; + } + + if (ApiConfiguration::OAUTH2_CLIENT_SECRET === $configuration->name) { + $value = $configuration->value ? 'secret' : $configuration->value; + } else { + $value = $configuration->value; + } + + $config[$configuration->name][] = [ + 'id_shop' => $configuration->id_shop, + 'id_shop_group' => $configuration->id_shop_group, + 'value' => $value, + ]; + } + + return $config; + } +} diff --git a/modules/inpostizi/src/Handler/Config/DownloadModuleDataHandlerInterface.php b/modules/inpostizi/src/Handler/Config/DownloadModuleDataHandlerInterface.php new file mode 100644 index 00000000..69f4ebe2 --- /dev/null +++ b/modules/inpostizi/src/Handler/Config/DownloadModuleDataHandlerInterface.php @@ -0,0 +1,15 @@ +errors = $errors; + } + + public function isOK(): bool + { + return [] === $this->errors; + } + + /** + * @return string[] + */ + public function getErrors(): array + { + return $this->errors; + } +} diff --git a/modules/inpostizi/src/Handler/Config/Status/CacheStatusChecker.php b/modules/inpostizi/src/Handler/Config/Status/CacheStatusChecker.php new file mode 100644 index 00000000..c2412e72 --- /dev/null +++ b/modules/inpostizi/src/Handler/Config/Status/CacheStatusChecker.php @@ -0,0 +1,49 @@ +module = $module; + $this->container = $container; + } + + /** + * {@inheritDoc} + */ + public function checkStatus(): array + { + if ($this->checkContainerVersion()) { + return []; + } + + return [$this->module->l('Container cache is stale.', self::TRANSLATION_SOURCE)]; + } + + private function checkContainerVersion(): bool + { + if (!$this->container->hasParameter('inpost.izi.container_version')) { + return false; + } + + return $this->module->version === $this->container->getParameter('inpost.izi.container_version'); + } +} diff --git a/modules/inpostizi/src/Handler/Config/Status/ConfigurationStatusChecker.php b/modules/inpostizi/src/Handler/Config/Status/ConfigurationStatusChecker.php new file mode 100644 index 00000000..7dd3b765 --- /dev/null +++ b/modules/inpostizi/src/Handler/Config/Status/ConfigurationStatusChecker.php @@ -0,0 +1,83 @@ +translator = $translator; + $this->ordersConfiguration = $ordersConfiguration; + $this->apiConfiguration = $apiConfiguration; + $this->validator = $validator; + } + + /** + * {@inheritDoc} + */ + public function checkStatus(): array + { + return iterator_to_array($this->getErrors()); + } + + private function getErrors(): \Generator + { + $violations = $this->validator->validate($this->ordersConfiguration->copy()); + + if (0 !== count($violations)) { + yield $this->translator->l('Configuration is incomplete - review and submit the form in the general settings tab.', self::TRANSLATION_SOURCE); + + return; + } + + if (null === $this->apiConfiguration->getClientCredentials()) { + yield $this->translator->l('API access credentials are missing.', self::TRANSLATION_SOURCE); + } else { + $violations = $this->validator->validate($this->apiConfiguration, new InPostApiCredentials()); + + /** @var ConstraintViolationInterface $violation */ + foreach ($violations as $violation) { + yield sprintf($this->translator->l('API access problem: %s', self::TRANSLATION_SOURCE), $violation->getMessage()); + } + } + + if (null === $this->apiConfiguration->getMerchantClientId()) { + yield $this->translator->l('Merchant client ID configuration is missing. The InPost Pay Widget will not be displayed.', self::TRANSLATION_SOURCE); + } + } +} diff --git a/modules/inpostizi/src/Handler/Config/Status/DeliveryOptionsStatusChecker.php b/modules/inpostizi/src/Handler/Config/Status/DeliveryOptionsStatusChecker.php new file mode 100644 index 00000000..49757a9a --- /dev/null +++ b/modules/inpostizi/src/Handler/Config/Status/DeliveryOptionsStatusChecker.php @@ -0,0 +1,67 @@ +translator = $translator; + $this->carrierRepository = $carrierRepository; + $this->configuration = $configuration; + } + + public function checkStatus(): array + { + if ($this->isDeliveryOptionAvailable($this->configuration->getApmShippingOptions())) { + return []; + } + + if ($this->isDeliveryOptionAvailable($this->configuration->getCourierShippingOptions())) { + return []; + } + + return [$this->translator->l('No delivery option is available.', self::TRANSLATION_SOURCE)]; + } + + private function isDeliveryOptionAvailable(ShippingOptions $options): bool + { + if (null === $carrierId = $options->getCarrierMapping()->getReferenceId()) { + return false; + } + + if (null === $carrier = $this->carrierRepository->findOneByReferenceId($carrierId)) { + return false; + } + + return $carrier->active && $carrier->isAssociatedToShop(); + } +} diff --git a/modules/inpostizi/src/Handler/Config/Status/StatusCheckerInterface.php b/modules/inpostizi/src/Handler/Config/Status/StatusCheckerInterface.php new file mode 100644 index 00000000..bfe0b3cf --- /dev/null +++ b/modules/inpostizi/src/Handler/Config/Status/StatusCheckerInterface.php @@ -0,0 +1,13 @@ + + */ + private $configuration; + + /** + * @param AdvancedConfiguration $configuration + */ + public function __construct(AdvancedConfigurationInterface $configuration) + { + $this->configuration = $configuration; + } + + public function __invoke(UpdateAdvancedConfigurationCommand $command) + { + $this->configuration->persist($command->getConfiguration()); + } +} diff --git a/modules/inpostizi/src/Handler/Config/UpdateAdvancedConfigurationHandlerInterface.php b/modules/inpostizi/src/Handler/Config/UpdateAdvancedConfigurationHandlerInterface.php new file mode 100644 index 00000000..afd162db --- /dev/null +++ b/modules/inpostizi/src/Handler/Config/UpdateAdvancedConfigurationHandlerInterface.php @@ -0,0 +1,12 @@ +repository = CartRuleOptionsRepository::create(); + $this->originalRepository = $repository; + } else { + $this->repository = $repository; + } + } + + public function __invoke(UpdateCartRuleOptionsCommand $command): void + { + $options = $this->repository->find($cartRuleId = $command->getCartRuleId()); + + if (null === $options) { + $this->addOptions($command); + } else { + $this->updateOptions($options, $command); + } + + if (null !== $this->originalRepository && null !== $isOmnibus = $command->isOmnibus()) { + $this->originalRepository->setOmnibus($cartRuleId, $isOmnibus); + } + } + + private function addOptions(UpdateCartRuleOptionsCommand $command): void + { + $options = new CartRuleOptions($command->getCartRuleId()); + $this->applyChanges($options, $command); + $this->repository->add($options); + } + + private function updateOptions(CartRuleOptions $options, UpdateCartRuleOptionsCommand $command): void + { + $this->applyChanges($options, $command); + $this->repository->update($options); + } + + private function applyChanges(CartRuleOptions $options, UpdateCartRuleOptionsCommand $command): void + { + if (null !== $isOmnibus = $command->isOmnibus()) { + $options->setIsOmnibus($isOmnibus); + } + + $options->setPromoDetailsPageId($command->getPromoDetailsPageId()); + } +} diff --git a/modules/inpostizi/src/Handler/Config/UpdateCartRuleOptionsHandlerInterface.php b/modules/inpostizi/src/Handler/Config/UpdateCartRuleOptionsHandlerInterface.php new file mode 100644 index 00000000..97336d0d --- /dev/null +++ b/modules/inpostizi/src/Handler/Config/UpdateCartRuleOptionsHandlerInterface.php @@ -0,0 +1,10 @@ + + */ + private $configuration; + + /** + * @var ClockInterface + */ + private $clock; + + /** + * @param ConsentsConfiguration $configuration + */ + public function __construct(ConsentsConfigurationInterface $configuration, ClockInterface $clock) + { + $this->configuration = $configuration; + $this->clock = $clock; + } + + public function __invoke(UpdateConsentsConfigurationCommand $command) + { + $now = $this->clock->now(); + + foreach ($command->getConsents() as $consent) { + $consent->setDateUpdated($now); + } + + $this->configuration->persist($command); + } +} diff --git a/modules/inpostizi/src/Handler/Config/UpdateConsentsConfigurationHandlerInterface.php b/modules/inpostizi/src/Handler/Config/UpdateConsentsConfigurationHandlerInterface.php new file mode 100644 index 00000000..fc3353c6 --- /dev/null +++ b/modules/inpostizi/src/Handler/Config/UpdateConsentsConfigurationHandlerInterface.php @@ -0,0 +1,12 @@ + + */ + private $apiConfiguration; + + /** + * @var PersistentConfigurationInterface + */ + private $ordersConfiguration; + + /** + * @var PersistentConfigurationInterface + */ + private $generalConfiguration; + + /** + * @var PersistentConfigurationInterface + */ + private $productConfiguration; + + /** + * @var CacheClearerInterface + */ + private $cacheClearer; + + /** + * @var \Module + */ + private $module; + + /** + * @param ApiConfiguration $apiConfiguration + * @param OrdersConfiguration $ordersConfiguration + * @param GeneralConfiguration $generalConfiguration + * @param ProductConfiguration $productConfiguration + */ + public function __construct(ApiConfigurationInterface $apiConfiguration, OrdersConfigurationInterface $ordersConfiguration, GeneralConfigurationInterface $generalConfiguration, ProductConfigurationInterface $productConfiguration, CacheClearerInterface $cacheClearer, \Module $module) + { + $this->apiConfiguration = $apiConfiguration; + $this->ordersConfiguration = $ordersConfiguration; + $this->generalConfiguration = $generalConfiguration; + $this->productConfiguration = $productConfiguration; + $this->cacheClearer = $cacheClearer; + $this->module = $module; + } + + public function __invoke(UpdateGeneralConfigurationCommand $command) + { + $oldApiConfig = $this->apiConfiguration->copy(); + + $this->apiConfiguration->persist($command->getApiConfiguration()); + $this->ordersConfiguration->persist($command->getOrdersConfiguration()); + $this->generalConfiguration->persist($command->getGeneralConfiguration()); + $this->productConfiguration->persist($command->getProductConfiguration()); + + $this->module->registerHook($command->getGeneralConfiguration()->getProductCardDisplayHook()); + $this->module->registerHook($command->getGeneralConfiguration()->getCheckoutButtonDisplayHook()); + + if ($this->didApiConfigChange($oldApiConfig, $command->getApiConfiguration())) { + $this->cacheClearer->clear(); + } + } + + private function didApiConfigChange(ApiConfigurationInterface $oldConfiguration, ApiConfigurationInterface $newConfiguration): bool + { + if ($oldConfiguration->getClientCredentials() !== $newConfiguration->getClientCredentials()) { + return true; + } + + return $oldConfiguration->getEnvironment() !== $newConfiguration->getEnvironment(); + } +} diff --git a/modules/inpostizi/src/Handler/Config/UpdateGeneralConfigurationHandlerInterface.php b/modules/inpostizi/src/Handler/Config/UpdateGeneralConfigurationHandlerInterface.php new file mode 100644 index 00000000..139683d5 --- /dev/null +++ b/modules/inpostizi/src/Handler/Config/UpdateGeneralConfigurationHandlerInterface.php @@ -0,0 +1,12 @@ + + */ + private $configuration; + + /** + * @param GuiConfiguration $configuration + */ + public function __construct(GuiConfigurationInterface $configuration) + { + $this->configuration = $configuration; + } + + public function __invoke(UpdateGuiConfigurationCommand $command) + { + $this->configuration->persist($command->getConfiguration()); + } +} diff --git a/modules/inpostizi/src/Handler/Config/UpdateGuiConfigurationHandlerInterface.php b/modules/inpostizi/src/Handler/Config/UpdateGuiConfigurationHandlerInterface.php new file mode 100644 index 00000000..8f7f3658 --- /dev/null +++ b/modules/inpostizi/src/Handler/Config/UpdateGuiConfigurationHandlerInterface.php @@ -0,0 +1,12 @@ + + */ + private $configuration; + + /** + * @param ShippingConfiguration $configuration + */ + public function __construct(ShippingConfigurationInterface $configuration) + { + $this->configuration = $configuration; + } + + public function __invoke(UpdateShippingConfigurationCommand $command) + { + $this->configuration->persist($command->getConfiguration()); + } +} diff --git a/modules/inpostizi/src/Handler/Config/UpdateShippingConfigurationHandlerInterface.php b/modules/inpostizi/src/Handler/Config/UpdateShippingConfigurationHandlerInterface.php new file mode 100644 index 00000000..e9404aab --- /dev/null +++ b/modules/inpostizi/src/Handler/Config/UpdateShippingConfigurationHandlerInterface.php @@ -0,0 +1,12 @@ + $repository + */ + public function __construct(BasketSessionRepositoryInterface $repository, BasketsApiClientInterface $client, ?\Context $context = null) + { + $this->context = $context ?? \Context::getContext(); + $this->repository = $repository; + $this->client = $client; + } + + public function __invoke(GetBasketBindingKeyCommand $command): BasketBindingKey + { + $basket = $command->getBasket(); + + if (null === $session = $this->repository->findByEntityId($basket->getId())) { + $session = $this->createNewSession($basket); + } + + if (!$command->isRefresh() && null !== $key = $session->getBindingApiKey()) { + return new BasketBindingKey($session->getBasketId(), $key); + } + + if (BasketSession::isFinalized($session)) { + throw new \DomainException(sprintf('Basket "%s" was already finalized.', $basket->getId())); + } + + $key = $this->client->initializeBasketBinding($session->getBasketId())->getBindingKey(); + + $session->setBindingApiKey($key); + $this->repository->persist($session); + + return new BasketBindingKey($session->getBasketId(), $key); + } + + private function createNewSession(BasketInterface $basket): BasketSessionInterface + { + $session = $this->repository->createNewSession($basket); + $session->setShopId((int) $this->context->shop->id); + $this->repository->persist($session); + + return $session; + } +} diff --git a/modules/inpostizi/src/Handler/GetBasketBindingKeyHandlerInterface.php b/modules/inpostizi/src/Handler/GetBasketBindingKeyHandlerInterface.php new file mode 100644 index 00000000..061c82ea --- /dev/null +++ b/modules/inpostizi/src/Handler/GetBasketBindingKeyHandlerInterface.php @@ -0,0 +1,13 @@ +repository = $repository; + $this->contextUpdater = $contextUpdater; + } + + public function __invoke(GetOrderConfirmationUrlCommand $command): string + { + if (null === $session = $this->repository->findByBasketId($command->getBasketId())) { + throw new \DomainException(sprintf('Basket "%s" does not exist.', $command->getBasketId())); + } + + if (null === $orderId = $session->getOrderId()) { + throw new \DomainException(sprintf('Basket "%s" was not finalized.', $session->getBasketId())); + } + + $session->redirect(); + $this->repository->persist($session); + + $this->contextUpdater->updateCustomer((int) $orderId); + + return $session->getOrderConfirmationUrl(); + } +} diff --git a/modules/inpostizi/src/Handler/GetOrderConfirmationUrlHandlerInterface.php b/modules/inpostizi/src/Handler/GetOrderConfirmationUrlHandlerInterface.php new file mode 100644 index 00000000..fa1d1774 --- /dev/null +++ b/modules/inpostizi/src/Handler/GetOrderConfirmationUrlHandlerInterface.php @@ -0,0 +1,12 @@ +basketId = $basketId; + $this->bindingKey = $bindingKey; + } + + public function getBasketId(): string + { + return $this->basketId; + } + + public function getBindingKey(): string + { + return $this->bindingKey; + } +} diff --git a/modules/inpostizi/src/Handler/UnbindBasketHandler.php b/modules/inpostizi/src/Handler/UnbindBasketHandler.php new file mode 100644 index 00000000..ef2dec19 --- /dev/null +++ b/modules/inpostizi/src/Handler/UnbindBasketHandler.php @@ -0,0 +1,56 @@ +repository = $repository; + $this->client = $client; + } + + public static function getHandledCommandClass(): string + { + return UnbindBasketCommand::class; + } + + public function __invoke(UnbindBasketCommand $command) + { + if (null === $session = $this->repository->findByEntityId($command->getBasketId())) { + return; + } + + try { + $this->client->deleteBasketBinding($session->getBasketId(), $command->isOrderCompleted()); + } catch (BasketNotFoundException|BasketNotBoundException|BasketExpiredException $e) { + // binding does not exist or will be deleted shortly + } + + if (null === $session->getBindingConfirmation()) { + return; + } + + $session->unbind(); + $this->repository->persist($session); + } +} diff --git a/modules/inpostizi/src/Handler/UnbindBasketHandlerInterface.php b/modules/inpostizi/src/Handler/UnbindBasketHandlerInterface.php new file mode 100644 index 00000000..dbd2c5b3 --- /dev/null +++ b/modules/inpostizi/src/Handler/UnbindBasketHandlerInterface.php @@ -0,0 +1,12 @@ +sessionRepository = $sessionRepository; + $this->builderFactory = $builderFactory; + $this->client = $client; + $this->logger = $logger; + } + + public function __invoke(UpdateBasketCommand $command) + { + $session = $this->sessionRepository->findByEntityId($cartId = $command->getBasketId()); + + if (null === $session || !$session->isBasketBound() || BasketSession::isFinalized($session)) { + return; + } + + $basket = $this->builderFactory + ->createRequestBuilder($session->getBasket(), $session->getShopId()) + ->build(); + + try { + $this->client->updateBasket($session->getBasketId(), $basket); + } catch (BasketNotFoundException|BasketNotBoundException|BasketExpiredException $e) { + $this->logger->warning('API error "{code}" for cart #{cartId} update, resetting binding status', [ + 'code' => $e->getError()->getCode(), + 'cartId' => $cartId, + ]); + + $session->unbind(); + $this->sessionRepository->persist($session); + } + } +} diff --git a/modules/inpostizi/src/Handler/UpdateBasketHandlerInterface.php b/modules/inpostizi/src/Handler/UpdateBasketHandlerInterface.php new file mode 100644 index 00000000..679cb276 --- /dev/null +++ b/modules/inpostizi/src/Handler/UpdateBasketHandlerInterface.php @@ -0,0 +1,12 @@ +sessionRepository = $sessionRepository; + $this->eventBuilderFactory = $eventBuilderFactory; + $this->client = $client; + $this->addressDataMapper = $addressDataMapper; + $this->objectManager = $objectManager; + } + + public function __invoke(UpdateOrderAddressDeliveryCommand $command): void + { + if (null === $this->sessionRepository->findByOrderId($orderId = $command->getOrderId())) { + return; + } + + $eventTime = $command->getEventTime(); + $eventId = $this->generateEventId($command->getOrderId(), $eventTime); + + if (null === $order = $this->objectManager->find(\Order::class, (int) $orderId)) { + throw new \DomainException(sprintf('Order "%s" does not exist.', $orderId)); + } + + $delivery = $this->getDeliveryData((int) $order->id_address_delivery); + + $event = $this->eventBuilderFactory + ->create((int) $command->getOrderId()) + ->setEventId($eventId) + ->setEventTime($eventTime) + ->setDeliveryData($delivery) + ->build(); + + $this->client->updateOrder($command->getOrderId(), $event); + } + + private function generateEventId(string $orderId, \DateTimeImmutable $eventTime): string + { + return sprintf('DA_%s_%d', $orderId, $eventTime->getTimestamp()); + } + + private function getDeliveryData(int $deliveryAddressId): Delivery + { + $address = $this->objectManager->find(\Address::class, $deliveryAddressId); + + if (null === $address) { + throw new \DomainException(sprintf('Address "%d" does not exist.', $deliveryAddressId)); + } + + $deliveryAddress = $this->addressDataMapper->mapDeliveryAddress($address); + $phoneNumber = $this->addressDataMapper->mapPhoneNumber($address); + + return new Delivery( + null, + null, + $phoneNumber, + null, + $deliveryAddress + ); + } +} diff --git a/modules/inpostizi/src/Handler/UpdateOrderAddressDeliveryHandlerInterface.php b/modules/inpostizi/src/Handler/UpdateOrderAddressDeliveryHandlerInterface.php new file mode 100644 index 00000000..694c4943 --- /dev/null +++ b/modules/inpostizi/src/Handler/UpdateOrderAddressDeliveryHandlerInterface.php @@ -0,0 +1,12 @@ +sessionRepository = $sessionRepository; + $this->eventBuilderFactory = $eventBuilderFactory; + $this->client = $client; + } + + public function __invoke(UpdateOrderStatusCommand $command): void + { + if (null === $this->sessionRepository->findByOrderId($orderId = $command->getOrderId())) { + return; + } + + $eventTime = $command->getEventTime(); + $eventId = $this->generateEventId($orderId, $eventTime); + + $event = $this->eventBuilderFactory + ->create((int) $orderId) + ->setEventId($eventId) + ->setEventTime($eventTime) + ->setOrderStatus($command->getStatus()) + ->build(); + + $this->client->updateOrder($orderId, $event); + } + + private function generateEventId(string $orderId, \DateTimeImmutable $eventTime): string + { + return sprintf('OS_%s_%d', $orderId, $eventTime->getTimestamp()); + } +} diff --git a/modules/inpostizi/src/Handler/UpdateOrderStatusHandlerInterface.php b/modules/inpostizi/src/Handler/UpdateOrderStatusHandlerInterface.php new file mode 100644 index 00000000..ecce795e --- /dev/null +++ b/modules/inpostizi/src/Handler/UpdateOrderStatusHandlerInterface.php @@ -0,0 +1,12 @@ + $trackingNumberProviders + */ + public function __construct(BasketSessionRepositoryInterface $sessionRepository, OrderEventBuilderFactoryInterface $eventBuilderFactory, OrdersApiClientInterface $client, iterable $trackingNumberProviders) + { + $this->sessionRepository = $sessionRepository; + $this->eventBuilderFactory = $eventBuilderFactory; + $this->client = $client; + $this->trackingNumberProviders = $trackingNumberProviders; + } + + public static function getHandledCommandClass(): string + { + return UpdateOrderTrackingNumbersCommand::class; + } + + public function __invoke(UpdateOrderTrackingNumbersCommand $command): void + { + if (null === $this->sessionRepository->findByOrderId($orderId = $command->getOrderId())) { + return; + } + + $trackingNumbers = $this->getTrackingNumbers((int) $orderId); + + $eventTime = $command->getEventTime(); + $eventId = $this->generateEventId($orderId, $eventTime); + + $event = $this->eventBuilderFactory + ->create((int) $orderId) + ->setEventId($eventId) + ->setEventTime($eventTime) + ->setTrackingNumbers($trackingNumbers) + ->build(); + + $this->client->updateOrder($orderId, $event); + } + + private function getTrackingNumbers(int $orderId): array + { + $trackingNumbers = []; + + foreach ($this->trackingNumberProviders as $provider) { + $trackingNumbers[] = $provider->getTrackingNumbers($orderId); + } + + return array_unique(array_merge(...$trackingNumbers)); + } + + private function generateEventId(string $orderId, \DateTimeImmutable $eventTime): string + { + return sprintf('TN_%s_%d', $orderId, $eventTime->getTimestamp()); + } +} diff --git a/modules/inpostizi/src/Handler/UpdateOrderTrackingNumbersHandlerInterface.php b/modules/inpostizi/src/Handler/UpdateOrderTrackingNumbersHandlerInterface.php new file mode 100644 index 00000000..985728ad --- /dev/null +++ b/modules/inpostizi/src/Handler/UpdateOrderTrackingNumbersHandlerInterface.php @@ -0,0 +1,12 @@ +formFactory = $formFactory; + $this->bus = $bus; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + /** + * @param array{return: false|\CartRule, controller: \AdminCartRulesControllerCore, request?: Request} $parameters + */ + public function execute(array $parameters): void + { + $request = $parameters['request'] ?? null; + + if (!$request instanceof Request) { + return; + } + + if (false === $cartRule = $parameters['return'] ?? null) { + return; + } + + if (!$cartRule instanceof \CartRule) { + throw new \InvalidArgumentException(sprintf('Expected parameter "return" to be false or an instance of %s, "%s" given.', \CartRule::class, get_debug_type($cartRule))); + } + + $form = $this->formFactory->create(CartRuleOptionsType::class, new UpdateCartRuleOptionsCommand((int) $cartRule->id), [ + 'csrf_protection' => false, + ]); + $form->handleRequest($request); + + if (!$form->isSubmitted()) { + return; + } + + if ($form->isValid()) { + $this->bus->handle($form->getData()); + + return; + } + + $controller = $parameters['controller'] ?? null; + + if (!$controller instanceof \AdminControllerCore) { + throw new \InvalidArgumentException(sprintf('Expected parameter "controller" to be an instance of %s, "%s" given.', \AdminControllerCore::class, get_debug_type($controller))); + } + + $this->setControllerErrors($controller, $form); + } + + /** + * @see \AdminControllerCore::postProcess() + * + * @todo: store errors in session and redirect back to the cart rule edit page? + */ + private function setControllerErrors(\AdminControllerCore $controller, FormInterface $form): void + { + $errors = $this->getFormErrors($form); + $error = array_pop($errors); + $controller->errors = array_merge($controller->errors, $errors); + $controller->setRedirectAfter(null); + + throw new PrestaShopModuleErrorException($error); + } + + /** + * @return string[] + */ + private function getFormErrors(FormInterface $form): array + { + $errors = []; + + foreach ($form->getErrors(true) as $error) { + if (null === $origin = $error->getOrigin()) { + $errors[] = $error->getMessage(); + } else { + $label = $origin->getConfig()->getOption('label') ?? $origin->getName(); + $errors[] = sprintf('%s: %s', $label, $error->getMessage()); + } + } + + return $errors; + } +} diff --git a/modules/inpostizi/src/Hook/Admin/ActionAdminControllerSetMedia.php b/modules/inpostizi/src/Hook/Admin/ActionAdminControllerSetMedia.php new file mode 100644 index 00000000..cd1fed4f --- /dev/null +++ b/modules/inpostizi/src/Hook/Admin/ActionAdminControllerSetMedia.php @@ -0,0 +1,44 @@ + + */ + private $assetsProviders; + + /** + * @param AssetManagerInterface $assetManager + * @param iterable $assetsProviders + */ + public function __construct(AssetManagerInterface $assetManager, iterable $assetsProviders) + { + $this->assetManager = $assetManager; + $this->assetsProviders = $assetsProviders; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + public function execute(array $parameters): void + { + foreach ($this->assetsProviders as $assetsProvider) { + $this->registerAssets($assetsProvider); + } + } +} diff --git a/modules/inpostizi/src/Hook/Admin/ActionAdminInPostConfirmedShipmentsControllerAfter.php b/modules/inpostizi/src/Hook/Admin/ActionAdminInPostConfirmedShipmentsControllerAfter.php new file mode 100644 index 00000000..59c9c3ec --- /dev/null +++ b/modules/inpostizi/src/Hook/Admin/ActionAdminInPostConfirmedShipmentsControllerAfter.php @@ -0,0 +1,38 @@ +dispatcher = $dispatcher; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + public function execute(array $parameters) + { + $request = $parameters['request'] ?? null; + + if (null === $request || 'createShipment' !== $request->query->get('action')) { + return; + } + + $this->dispatcher->dispatch(new CreateShipmentRequestProcessedEvent($parameters['controller']), CreateShipmentRequestProcessedEvent::class); + } +} diff --git a/modules/inpostizi/src/Hook/Admin/ActionAdminInPostConfirmedShipmentsControllerBefore.php b/modules/inpostizi/src/Hook/Admin/ActionAdminInPostConfirmedShipmentsControllerBefore.php new file mode 100644 index 00000000..431f6301 --- /dev/null +++ b/modules/inpostizi/src/Hook/Admin/ActionAdminInPostConfirmedShipmentsControllerBefore.php @@ -0,0 +1,38 @@ +dispatcher = $dispatcher; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + public function execute(array $parameters) + { + $request = $parameters['request'] ?? null; + + if (null === $request || 'createShipment' !== $request->query->get('action')) { + return; + } + + $this->dispatcher->dispatch(new CreateShipmentRequestEvent($parameters['request'], $parameters['controller']), CreateShipmentRequestEvent::class); + } +} diff --git a/modules/inpostizi/src/Hook/Admin/DisplayAdminOrderLeft.php b/modules/inpostizi/src/Hook/Admin/DisplayAdminOrderLeft.php new file mode 100644 index 00000000..7d06346e --- /dev/null +++ b/modules/inpostizi/src/Hook/Admin/DisplayAdminOrderLeft.php @@ -0,0 +1,73 @@ +renderer = $renderer; + $this->repository = $repository; + $this->translator = $translator ?? new LegacyTranslator('inpostizi'); + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + public static function getVersionRange(): VersionRange + { + return new VersionRange(null, '1.7.7'); + } + + /** + * @param array{id_order?: int} $parameters + */ + public function execute(array $parameters): string + { + $orderId = $parameters['id_order'] ?? null; + + if (!is_int($orderId)) { + throw new \InvalidArgumentException(sprintf('Expected parameter "id_order" to be an integer, "%s" given.', get_debug_type($orderId))); + } + + if (null === $data = $this->repository->getOrderData((string) $orderId)) { + return ''; + } + + $deliveryType = $data->getDelivery()->getType(); + + return $this->renderer->render('module:inpostizi/views/templates/hook/legacy/admin/order_details.tpl', [ + 'delivery' => $deliveryType->trans($this->translator), + 'apm' => DeliveryType::Apm() === $deliveryType ? $data->getDelivery()->getPoint() : '', + 'issue_invoice' => null !== $data->getInvoiceDetails(), + ]); + } +} diff --git a/modules/inpostizi/src/Hook/Admin/DisplayAdminOrderSide.php b/modules/inpostizi/src/Hook/Admin/DisplayAdminOrderSide.php new file mode 100644 index 00000000..40f644eb --- /dev/null +++ b/modules/inpostizi/src/Hook/Admin/DisplayAdminOrderSide.php @@ -0,0 +1,67 @@ +renderer = $renderer; + $this->repository = $repository; + $this->translator = $translator ?? new LegacyTranslator('inpostizi'); + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + public static function getVersionRange(): VersionRange + { + return new VersionRange('1.7.7'); + } + + /** + * @param array{id_order: int} $parameters + */ + public function execute(array $parameters): string + { + if (null === $data = $this->repository->getOrderData((string) $parameters['id_order'])) { + return ''; + } + + $deliveryType = $data->getDelivery()->getType(); + + return $this->renderer->render('module:inpostizi/views/templates/hook/admin/order_details.tpl', [ + 'delivery' => $deliveryType->trans($this->translator), + 'apm' => DeliveryType::Apm() === $deliveryType ? $data->getDelivery()->getPoint() : '', + 'issue_invoice' => null !== $data->getInvoiceDetails(), + ]); + } +} diff --git a/modules/inpostizi/src/Hook/Admin/DisplayBackOfficeHeader.php b/modules/inpostizi/src/Hook/Admin/DisplayBackOfficeHeader.php new file mode 100644 index 00000000..34494784 --- /dev/null +++ b/modules/inpostizi/src/Hook/Admin/DisplayBackOfficeHeader.php @@ -0,0 +1,127 @@ +context = $context; + $this->formFactory = $formFactory; + $this->renderer = $renderer; + + if (!$repository instanceof CartRuleOptionsRepositoryInterface) { + @trigger_error(sprintf('Passing a $repository that does not implement "%s" to "%s()" is deprecated since 2.1.0.', CartRuleOptionsRepositoryInterface::class, __METHOD__), E_USER_DEPRECATED); + + $this->repository = CartRuleOptionsRepository::create(); + $this->originalRepository = $repository; + } else { + $this->repository = $repository; + } + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + /** + * @param array{request?: Request} $parameters + */ + public function execute(array $parameters): string + { + if (!$this->context->controller instanceof \AdminCartRulesControllerCore) { + return ''; + } + + $request = $parameters['request'] ?? null; + + if (!$request instanceof Request) { + return ''; + } + + if (!$request->query->has('addcart_rule') && !$request->query->has('updatecart_rule')) { + return ''; + } + + $form = $this->formFactory->create(CartRuleOptionsType::class, $this->getInitialFormData($request), [ + 'csrf_protection' => false, + ]); + $form->handleRequest($request); + + return $this->renderer->render('module:inpostizi/views/templates/hook/admin/cart_rule_form.tpl', [ + 'form' => $form->createView(), + ]); + } + + /** + * @return array|UpdateCartRuleOptionsCommand + */ + private function getInitialFormData(Request $request) + { + if (0 >= $cartRuleId = (int) $request->get('id_cart_rule')) { + return [ + 'omnibus' => false, + ]; + } + + $command = $this->createUpdateOptionsCommand($cartRuleId); + + if (null !== $this->originalRepository) { + $command->setOmnibus($this->originalRepository->isOmnibus($cartRuleId)); + } + + return $command; + } + + private function createUpdateOptionsCommand(int $cartRuleId): UpdateCartRuleOptionsCommand + { + if (null === $options = $this->repository->find($cartRuleId)) { + return new UpdateCartRuleOptionsCommand($cartRuleId); + } + + return UpdateCartRuleOptionsCommand::for($options); + } +} diff --git a/modules/inpostizi/src/Hook/AliasedHookInterface.php b/modules/inpostizi/src/Hook/AliasedHookInterface.php new file mode 100644 index 00000000..4729ae2e --- /dev/null +++ b/modules/inpostizi/src/Hook/AliasedHookInterface.php @@ -0,0 +1,13 @@ + + */ + public static function getAliases(): array; +} diff --git a/modules/inpostizi/src/Hook/AssetRegistryUpdaterTrait.php b/modules/inpostizi/src/Hook/AssetRegistryUpdaterTrait.php new file mode 100644 index 00000000..19d4293b --- /dev/null +++ b/modules/inpostizi/src/Hook/AssetRegistryUpdaterTrait.php @@ -0,0 +1,35 @@ +getAssets()) { + return; + } + + foreach ($assets->getJavaScripts() as $path => $options) { + $this->assetManager->registerJavaScript($path, $options); + } + + foreach ($assets->getStyleSheets() as $path => $options) { + $this->assetManager->registerStyleSheet($path, $options); + } + + if ([] !== $jsVars = $assets->getJavaScriptVariables()) { + $this->assetManager->registerJavaScriptVariables($jsVars); + } + } +} diff --git a/modules/inpostizi/src/Hook/Common/ActionCartDeleteBefore.php b/modules/inpostizi/src/Hook/Common/ActionCartDeleteBefore.php new file mode 100644 index 00000000..f14bb438 --- /dev/null +++ b/modules/inpostizi/src/Hook/Common/ActionCartDeleteBefore.php @@ -0,0 +1,49 @@ +bus = $bus; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + /** + * @param array{object?: \Cart} $parameters + */ + public function execute(array $parameters): void + { + $cart = $parameters['object'] ?? null; + + if (!$cart instanceof \Cart) { + throw new \InvalidArgumentException(sprintf('Expected parameter "object" to be an instance of "%s", "%s" given.', \Cart::class, get_debug_type($cart))); + } + + if (0 >= $cartId = (int) $cart->id) { + return; + } + + $command = new UnbindBasketCommand($cartId); + + $this->bus->handle($command); + } +} diff --git a/modules/inpostizi/src/Hook/Common/ActionCartUpdateAfter.php b/modules/inpostizi/src/Hook/Common/ActionCartUpdateAfter.php new file mode 100644 index 00000000..efdb6881 --- /dev/null +++ b/modules/inpostizi/src/Hook/Common/ActionCartUpdateAfter.php @@ -0,0 +1,59 @@ +module = $module; + $this->context = $context; + $this->dispatcher = $dispatcher; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + /** + * @param array{object?: \Cart} $parameters + */ + public function execute(array $parameters): void + { + $cart = $parameters['object'] ?? null; + + if (!$cart instanceof \Cart) { + throw new \InvalidArgumentException(sprintf('Expected parameter "cart" to be an instance of "%s", "%s" given.', \Cart::class, get_debug_type($cart))); + } + + if ($this->context->controller instanceof \ModuleFrontControllerCore && $this->module === $this->context->controller->module) { + return; + } + + $this->dispatcher->dispatch(new CartUpdatedEvent($cart)); + } +} diff --git a/modules/inpostizi/src/Hook/Common/ActionEmailSendBefore.php b/modules/inpostizi/src/Hook/Common/ActionEmailSendBefore.php new file mode 100644 index 00000000..ffc90758 --- /dev/null +++ b/modules/inpostizi/src/Hook/Common/ActionEmailSendBefore.php @@ -0,0 +1,65 @@ +dispatcher = $dispatcher; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + public function execute(array $parameters): bool + { + $event = $this->createEvent($parameters); + $this->dispatcher->dispatch($event); + + if ($event->isPropagationStopped()) { + return false; + } + + $parameters['to'] = $event->getRecipients(); + $parameters['bcc'] = $event->getBcc(); + + return true; + } + + private function createEvent(array $parameters): SendEmailEvent + { + $event = new SendEmailEvent($parameters['template'], $parameters['templateVars']); + + if (is_array($parameters['to'])) { + $event->setRecipients(...$parameters['to']); + } else { + $event->setRecipients($parameters['to']); + } + + if (null === $parameters['bcc']) { + return $event; + } + + if (is_array($parameters['bcc'])) { + $event->setBcc(...$parameters['bcc']); + } else { + $event->setBcc($parameters['bcc']); + } + + return $event; + } +} diff --git a/modules/inpostizi/src/Hook/Common/ActionObjectOrderUpdateAfter.php b/modules/inpostizi/src/Hook/Common/ActionObjectOrderUpdateAfter.php new file mode 100644 index 00000000..7f5d9a28 --- /dev/null +++ b/modules/inpostizi/src/Hook/Common/ActionObjectOrderUpdateAfter.php @@ -0,0 +1,51 @@ +dispatcher = $dispatcher; + $this->context = $context ?? \Context::getContext(); + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + public function execute(array $parameters): void + { + $order = $parameters['object'] ?? null; + + if (!$order instanceof \Order) { + throw new \InvalidArgumentException(sprintf('Expected parameter "object" to be an instance of "%s", "%s" given.', \Order::class, get_debug_type($order))); + } + + $controller = $this->context->controller; + if ($controller instanceof \ModuleFrontControllerCore && 'inpostizi' === $controller->module->name) { + return; + } + + $this->dispatcher->dispatch(new OrderEvent($order), OrderEvent::UPDATED); + } +} diff --git a/modules/inpostizi/src/Hook/Common/ActionObjectOrderUpdateBefore.php b/modules/inpostizi/src/Hook/Common/ActionObjectOrderUpdateBefore.php new file mode 100644 index 00000000..c25098ff --- /dev/null +++ b/modules/inpostizi/src/Hook/Common/ActionObjectOrderUpdateBefore.php @@ -0,0 +1,51 @@ +dispatcher = $dispatcher; + $this->context = $context ?? \Context::getContext(); + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + public function execute(array $parameters): void + { + $order = $parameters['object'] ?? null; + + if (!$order instanceof \Order) { + throw new \InvalidArgumentException(sprintf('Expected parameter "object" to be an instance of "%s", "%s" given.', \Order::class, get_debug_type($order))); + } + + $controller = $this->context->controller; + if ($controller instanceof \ModuleFrontControllerCore && 'inpostizi' === $controller->module->name) { + return; + } + + $this->dispatcher->dispatch(new OrderEvent($order), OrderEvent::BEFORE_UPDATE); + } +} diff --git a/modules/inpostizi/src/Hook/Common/ActionOrderStatusPostUpdate.php b/modules/inpostizi/src/Hook/Common/ActionOrderStatusPostUpdate.php new file mode 100644 index 00000000..8effb8ee --- /dev/null +++ b/modules/inpostizi/src/Hook/Common/ActionOrderStatusPostUpdate.php @@ -0,0 +1,65 @@ +module = $module; + $this->context = $context; + $this->dispatcher = $dispatcher; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + /** + * @param array{id_order?: int, newOrderStatus?: \OrderState, oldOrderStatus?: \OrderState} $parameters + */ + public function execute(array $parameters): void + { + $orderId = $parameters['id_order'] ?? null; + + if (!is_int($orderId)) { + throw new \InvalidArgumentException(sprintf('Expected parameter "id_order" to be an integer, "%s" given.', get_debug_type($orderId))); + } + + $newOrderStatus = $parameters['newOrderStatus'] ?? null; + + if (!$newOrderStatus instanceof \OrderState) { + throw new \InvalidArgumentException(sprintf('Expected parameter "newOrderStatus" to be an instance of "%s", "%s" given.', \OrderState::class, get_debug_type($newOrderStatus))); + } + + if ($this->context->controller instanceof \ModuleFrontControllerCore && $this->module === $this->context->controller->module) { + return; + } + + $this->dispatcher->dispatch(new OrderStatusUpdatedEvent($orderId, $newOrderStatus)); + } +} diff --git a/modules/inpostizi/src/Hook/Common/ActionShipmentAddAfter.php b/modules/inpostizi/src/Hook/Common/ActionShipmentAddAfter.php new file mode 100644 index 00000000..5d2d7e06 --- /dev/null +++ b/modules/inpostizi/src/Hook/Common/ActionShipmentAddAfter.php @@ -0,0 +1,43 @@ +dispatcher = $dispatcher; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + /** + * @param array{object?: \InPostShipmentModel} $parameters + */ + public function execute(array $parameters): void + { + $shipment = $parameters['object'] ?? null; + + if (!$shipment instanceof \InPostShipmentModel) { + throw new \InvalidArgumentException(sprintf('Expected parameter "object" to be an instance of "%s", "%s" given.', \InPostShipmentModel::class, get_debug_type($shipment))); + } + + $this->dispatcher->dispatch(new ShipmentEvent($shipment), ShipmentEvent::CREATED); + } +} diff --git a/modules/inpostizi/src/Hook/Common/ActionShipmentUpdateAfter.php b/modules/inpostizi/src/Hook/Common/ActionShipmentUpdateAfter.php new file mode 100644 index 00000000..e12500c0 --- /dev/null +++ b/modules/inpostizi/src/Hook/Common/ActionShipmentUpdateAfter.php @@ -0,0 +1,43 @@ +dispatcher = $dispatcher; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + /** + * @param array{object?: \InPostShipmentModel} $parameters + */ + public function execute(array $parameters): void + { + $shipment = $parameters['object'] ?? null; + + if (!$shipment instanceof \InPostShipmentModel) { + throw new \InvalidArgumentException(sprintf('Expected parameter "object" to be an instance of "%s", "%s" given.', \InPostShipmentModel::class, get_debug_type($shipment))); + } + + $this->dispatcher->dispatch(new ShipmentEvent($shipment), ShipmentEvent::UPDATED); + } +} diff --git a/modules/inpostizi/src/Hook/Common/ActionShipmentUpdateBefore.php b/modules/inpostizi/src/Hook/Common/ActionShipmentUpdateBefore.php new file mode 100644 index 00000000..1151cea2 --- /dev/null +++ b/modules/inpostizi/src/Hook/Common/ActionShipmentUpdateBefore.php @@ -0,0 +1,43 @@ +dispatcher = $dispatcher; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + /** + * @param array{object?: \InPostShipmentModel} $parameters + */ + public function execute(array $parameters): void + { + $shipment = $parameters['object'] ?? null; + + if (!$shipment instanceof \InPostShipmentModel) { + throw new \InvalidArgumentException(sprintf('Expected parameter "object" to be an instance of "%s", "%s" given.', \InPostShipmentModel::class, get_debug_type($shipment))); + } + + $this->dispatcher->dispatch(new ShipmentEvent($shipment), ShipmentEvent::BEFORE_UPDATE); + } +} diff --git a/modules/inpostizi/src/Hook/Common/ActionValidateOrder.php b/modules/inpostizi/src/Hook/Common/ActionValidateOrder.php new file mode 100644 index 00000000..c78a92fe --- /dev/null +++ b/modules/inpostizi/src/Hook/Common/ActionValidateOrder.php @@ -0,0 +1,68 @@ +paymentModule = $paymentModule; + $this->bus = $bus; + $this->dispatcher = $dispatcher ?? $paymentModule->get(EventDispatcherInterface::class); + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + /** + * @param array{order?: \Order} $parameters + */ + public function execute(array $parameters): void + { + $order = $parameters['order'] ?? null; + + if (!$order instanceof \Order) { + throw new \InvalidArgumentException(sprintf('Expected parameter "order" to be an instance of "%s", "%s" given.', \Order::class, get_debug_type($order))); + } + + $this->dispatcher->dispatch(new ValidateOrderEvent($order), ValidateOrderEvent::class); + + if ($this->paymentModule->name === $order->module) { + return; + } + + $command = new UnbindBasketCommand((int) $order->id_cart, true); + + $this->bus->handle($command); + } +} diff --git a/modules/inpostizi/src/Hook/Common/Product/ActionCombinationDeleteAfter.php b/modules/inpostizi/src/Hook/Common/Product/ActionCombinationDeleteAfter.php new file mode 100644 index 00000000..099105e5 --- /dev/null +++ b/modules/inpostizi/src/Hook/Common/Product/ActionCombinationDeleteAfter.php @@ -0,0 +1,43 @@ +dispatcher = $dispatcher; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + /** + * @param array{object: \Combination} $parameters + */ + public function execute(array $parameters): void + { + $combination = $parameters['object'] ?? null; + + if (!$combination instanceof \Combination) { + throw new \InvalidArgumentException(sprintf('Expected parameter "object" to be an instance of "%s", "%s" given.', \Combination::class, get_debug_type($combination))); + } + + $this->dispatcher->dispatch(new CombinationEvent($combination), CombinationEvent::DELETED); + } +} diff --git a/modules/inpostizi/src/Hook/Common/Product/ActionCombinationDeleteBefore.php b/modules/inpostizi/src/Hook/Common/Product/ActionCombinationDeleteBefore.php new file mode 100644 index 00000000..ffddc58d --- /dev/null +++ b/modules/inpostizi/src/Hook/Common/Product/ActionCombinationDeleteBefore.php @@ -0,0 +1,43 @@ +dispatcher = $dispatcher; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + /** + * @param array{object: \Combination} $parameters + */ + public function execute(array $parameters): void + { + $combination = $parameters['object'] ?? null; + + if (!$combination instanceof \Combination) { + throw new \InvalidArgumentException(sprintf('Expected parameter "object" to be an instance of "%s", "%s" given.', \Combination::class, get_debug_type($combination))); + } + + $this->dispatcher->dispatch(new CombinationEvent($combination), CombinationEvent::DELETION); + } +} diff --git a/modules/inpostizi/src/Hook/Common/Product/ActionCombinationUpdateAfter.php b/modules/inpostizi/src/Hook/Common/Product/ActionCombinationUpdateAfter.php new file mode 100644 index 00000000..7989c75b --- /dev/null +++ b/modules/inpostizi/src/Hook/Common/Product/ActionCombinationUpdateAfter.php @@ -0,0 +1,43 @@ +dispatcher = $dispatcher; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + /** + * @param array{object: \Combination} $parameters + */ + public function execute(array $parameters): void + { + $combination = $parameters['object'] ?? null; + + if (!$combination instanceof \Combination) { + throw new \InvalidArgumentException(sprintf('Expected parameter "object" to be an instance of "%s", "%s" given.', \Combination::class, get_debug_type($combination))); + } + + $this->dispatcher->dispatch(new CombinationEvent($combination), CombinationEvent::UPDATED); + } +} diff --git a/modules/inpostizi/src/Hook/Common/Product/ActionImageAddAfter.php b/modules/inpostizi/src/Hook/Common/Product/ActionImageAddAfter.php new file mode 100644 index 00000000..78d4b1c7 --- /dev/null +++ b/modules/inpostizi/src/Hook/Common/Product/ActionImageAddAfter.php @@ -0,0 +1,43 @@ +dispatcher = $dispatcher; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + /** + * @param array{object: \Image} $parameters + */ + public function execute(array $parameters): void + { + $image = $parameters['object'] ?? null; + + if (!$image instanceof \Image) { + throw new \InvalidArgumentException(sprintf('Expected parameter "object" to be an instance of "%s", "%s" given.', \Image::class, get_debug_type($image))); + } + + $this->dispatcher->dispatch(new ImageEvent($image), ImageEvent::CREATED); + } +} diff --git a/modules/inpostizi/src/Hook/Common/Product/ActionImageDeleteAfter.php b/modules/inpostizi/src/Hook/Common/Product/ActionImageDeleteAfter.php new file mode 100644 index 00000000..78dee9c8 --- /dev/null +++ b/modules/inpostizi/src/Hook/Common/Product/ActionImageDeleteAfter.php @@ -0,0 +1,43 @@ +dispatcher = $dispatcher; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + /** + * @param array{object: \Image} $parameters + */ + public function execute(array $parameters): void + { + $image = $parameters['object'] ?? null; + + if (!$image instanceof \Image) { + throw new \InvalidArgumentException(sprintf('Expected parameter "object" to be an instance of "%s", "%s" given.', \Image::class, get_debug_type($image))); + } + + $this->dispatcher->dispatch(new ImageEvent($image), ImageEvent::DELETED); + } +} diff --git a/modules/inpostizi/src/Hook/Common/Product/ActionProductDeleteAfter.php b/modules/inpostizi/src/Hook/Common/Product/ActionProductDeleteAfter.php new file mode 100644 index 00000000..f265441b --- /dev/null +++ b/modules/inpostizi/src/Hook/Common/Product/ActionProductDeleteAfter.php @@ -0,0 +1,43 @@ +dispatcher = $dispatcher; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + /** + * @param array{object: \Product} $parameters + */ + public function execute(array $parameters): void + { + $product = $parameters['object'] ?? null; + + if (!$product instanceof \Product) { + throw new \InvalidArgumentException(sprintf('Expected parameter "object" to be an instance of "%s", "%s" given.', \Product::class, get_debug_type($product))); + } + + $this->dispatcher->dispatch(new ProductEvent($product), ProductEvent::DELETED); + } +} diff --git a/modules/inpostizi/src/Hook/Common/Product/ActionProductDeleteBefore.php b/modules/inpostizi/src/Hook/Common/Product/ActionProductDeleteBefore.php new file mode 100644 index 00000000..fcb99e88 --- /dev/null +++ b/modules/inpostizi/src/Hook/Common/Product/ActionProductDeleteBefore.php @@ -0,0 +1,43 @@ +dispatcher = $dispatcher; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + /** + * @param array{object: \Product} $parameters + */ + public function execute(array $parameters): void + { + $product = $parameters['object'] ?? null; + + if (!$product instanceof \Product) { + throw new \InvalidArgumentException(sprintf('Expected parameter "object" to be an instance of "%s", "%s" given.', \Product::class, get_debug_type($product))); + } + + $this->dispatcher->dispatch(new ProductEvent($product), ProductEvent::DELETION); + } +} diff --git a/modules/inpostizi/src/Hook/Common/Product/ActionProductUpdateAfter.php b/modules/inpostizi/src/Hook/Common/Product/ActionProductUpdateAfter.php new file mode 100644 index 00000000..d82809f5 --- /dev/null +++ b/modules/inpostizi/src/Hook/Common/Product/ActionProductUpdateAfter.php @@ -0,0 +1,43 @@ +dispatcher = $dispatcher; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + /** + * @param array{object: \Product} $parameters + */ + public function execute(array $parameters): void + { + $product = $parameters['object'] ?? null; + + if (!$product instanceof \Product) { + throw new \InvalidArgumentException(sprintf('Expected parameter "object" to be an instance of "%s", "%s" given.', \Product::class, get_debug_type($product))); + } + + $this->dispatcher->dispatch(new ProductEvent($product), ProductEvent::UPDATED); + } +} diff --git a/modules/inpostizi/src/Hook/Common/Product/ActionSpecificPriceAddAfter.php b/modules/inpostizi/src/Hook/Common/Product/ActionSpecificPriceAddAfter.php new file mode 100644 index 00000000..de2086f3 --- /dev/null +++ b/modules/inpostizi/src/Hook/Common/Product/ActionSpecificPriceAddAfter.php @@ -0,0 +1,43 @@ +dispatcher = $dispatcher; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + /** + * @param array{object: \SpecificPrice} $parameters + */ + public function execute(array $parameters): void + { + $price = $parameters['object'] ?? null; + + if (!$price instanceof \SpecificPrice) { + throw new \InvalidArgumentException(sprintf('Expected parameter "object" to be an instance of "%s", "%s" given.', \SpecificPrice::class, get_debug_type($price))); + } + + $this->dispatcher->dispatch(new SpecificPriceEvent($price), SpecificPriceEvent::CREATED); + } +} diff --git a/modules/inpostizi/src/Hook/Common/Product/ActionSpecificPriceDeleteAfter.php b/modules/inpostizi/src/Hook/Common/Product/ActionSpecificPriceDeleteAfter.php new file mode 100644 index 00000000..4a8e6bf4 --- /dev/null +++ b/modules/inpostizi/src/Hook/Common/Product/ActionSpecificPriceDeleteAfter.php @@ -0,0 +1,43 @@ +dispatcher = $dispatcher; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + /** + * @param array{object: \SpecificPrice} $parameters + */ + public function execute(array $parameters): void + { + $price = $parameters['object'] ?? null; + + if (!$price instanceof \SpecificPrice) { + throw new \InvalidArgumentException(sprintf('Expected parameter "object" to be an instance of "%s", "%s" given.', \SpecificPrice::class, get_debug_type($price))); + } + + $this->dispatcher->dispatch(new SpecificPriceEvent($price), SpecificPriceEvent::DELETED); + } +} diff --git a/modules/inpostizi/src/Hook/Common/Product/ActionSpecificPriceUpdateAfter.php b/modules/inpostizi/src/Hook/Common/Product/ActionSpecificPriceUpdateAfter.php new file mode 100644 index 00000000..e7f8ea17 --- /dev/null +++ b/modules/inpostizi/src/Hook/Common/Product/ActionSpecificPriceUpdateAfter.php @@ -0,0 +1,43 @@ +dispatcher = $dispatcher; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + /** + * @param array{object: \SpecificPrice} $parameters + */ + public function execute(array $parameters): void + { + $price = $parameters['object'] ?? null; + + if (!$price instanceof \SpecificPrice) { + throw new \InvalidArgumentException(sprintf('Expected parameter "object" to be an instance of "%s", "%s" given.', \SpecificPrice::class, get_debug_type($price))); + } + + $this->dispatcher->dispatch(new SpecificPriceEvent($price), SpecificPriceEvent::UPDATED); + } +} diff --git a/modules/inpostizi/src/Hook/Common/Product/ActionUpdateQuantity.php b/modules/inpostizi/src/Hook/Common/Product/ActionUpdateQuantity.php new file mode 100644 index 00000000..428735c5 --- /dev/null +++ b/modules/inpostizi/src/Hook/Common/Product/ActionUpdateQuantity.php @@ -0,0 +1,63 @@ +dispatcher = $dispatcher; + $this->context = $context; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + /** + * @param array{ + * id_product: int, + * id_product_attribute: int, + * id_shop: int, + * quantity: int, + * delta_quantity?: int + * } $parameters + */ + public function execute(array $parameters): void + { + $productId = (int) ($parameters['id_product'] ?? 0); + $combinationId = (int) ($parameters['id_product_attribute'] ?? 0); + $shopId = (int) ($parameters['id_shop'] ?? $this->context->shop->id); + $quantity = (int) ($parameters['quantity'] ?? 0); + $deltaQuantity = isset($parameters['delta_quantity']) ? (int) $parameters['delta_quantity'] : null; + + if ($productId <= 0) { + throw new \InvalidArgumentException('Invalid product ID'); + } + + if ($shopId <= 0) { + throw new \InvalidArgumentException('Invalid shop ID'); + } + + $this->dispatcher->dispatch(new StockQuantityUpdatedEvent($productId, $combinationId, $shopId, $quantity, $deltaQuantity)); + } +} diff --git a/modules/inpostizi/src/Hook/Exception/HookExceptionInterface.php b/modules/inpostizi/src/Hook/Exception/HookExceptionInterface.php new file mode 100644 index 00000000..2a88ff72 --- /dev/null +++ b/modules/inpostizi/src/Hook/Exception/HookExceptionInterface.php @@ -0,0 +1,10 @@ +hookName = $hookName; + } + + public function getHookName(): string + { + return $this->hookName; + } +} diff --git a/modules/inpostizi/src/Hook/Exception/HookNotFoundException.php b/modules/inpostizi/src/Hook/Exception/HookNotFoundException.php new file mode 100644 index 00000000..9c97e396 --- /dev/null +++ b/modules/inpostizi/src/Hook/Exception/HookNotFoundException.php @@ -0,0 +1,15 @@ +context = $context; + $this->dispatcher = $dispatcher; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + /** + * @param array{request?: Request, value?: string} $parameters + */ + public function execute(array $parameters): void + { + if (!$this->context->cart instanceof \Cart) { + return; + } + + $request = $parameters['request'] ?? null; + + if (!$request instanceof Request) { + return; + } + + if (null === $request->get('addDiscount') && null === $request->get('deleteDiscount')) { + return; + } + + $this->dispatcher->dispatch(new CartUpdatedEvent($this->context->cart)); + } +} diff --git a/modules/inpostizi/src/Hook/Front/ActionFrontControllerSetMedia.php b/modules/inpostizi/src/Hook/Front/ActionFrontControllerSetMedia.php new file mode 100644 index 00000000..f184a25e --- /dev/null +++ b/modules/inpostizi/src/Hook/Front/ActionFrontControllerSetMedia.php @@ -0,0 +1,87 @@ + + */ + private $assetsProviders; + + /** + * @var ApiConfigurationInterface + */ + private $configuration; + + /** + * @param iterable $assetsProviders + */ + public function __construct( + AssetManagerInterface $assetManager, + AuthorizationCheckerInterface $authorizationChecker, + iterable $assetsProviders, + ApiConfigurationInterface $configuration + ) { + $this->assetManager = $assetManager; + $this->authorizationChecker = $authorizationChecker; + $this->assetsProviders = $assetsProviders; + $this->configuration = $configuration; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + /** + * @param array{request?: Request} $parameters + */ + public function execute(array $parameters): void + { + $request = $parameters['request'] ?? null; + + if ($request instanceof Request && $request->isXmlHttpRequest()) { + return; + } + + if (!$this->hasRequiredConfiguration()) { + return; + } + + if (!$this->authorizationChecker->isGranted(BindingWidgetVoter::VIEW, $request)) { + return; + } + + foreach ($this->assetsProviders as $assetsProvider) { + $this->registerAssets($assetsProvider); + } + + // $this->module->l('Something went wrong. Please try again later.', self::HOOK_NAME) kept for \AdminTranslationsController translatable message discovery + } + + private function hasRequiredConfiguration(): bool + { + return null !== $this->configuration->getClientCredentials() && null !== $this->configuration->getMerchantClientId(); + } +} diff --git a/modules/inpostizi/src/Hook/Front/ActionGetPaymentOptions.php b/modules/inpostizi/src/Hook/Front/ActionGetPaymentOptions.php new file mode 100644 index 00000000..26a7fb3e --- /dev/null +++ b/modules/inpostizi/src/Hook/Front/ActionGetPaymentOptions.php @@ -0,0 +1,101 @@ +paymentModule = $paymentModule; + $this->currencyChecker = $currencyChecker; + $this->configuration = $configuration; + $this->renderer = $renderer; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + /** + * @param array{cart?: \Cart, request?: Request} $parameters + * + * @return PaymentOption[] + */ + public function execute(array $parameters): array + { + $cart = $parameters['cart'] ?? null; + + if (!$cart instanceof \Cart) { + throw new \InvalidArgumentException(sprintf('Expected parameter "cart" to be an instance of "%s", "%s" given.', \Cart::class, get_debug_type($cart))); + } + + if (0 >= (int) $cart->id || !$this->currencyChecker->check($this->paymentModule, (int) $cart->id_currency)) { + return []; + } + + $widget = $this->paymentModule->renderWidget(self::HOOK_NAME, [ + 'config' => $this->configuration->getDisplayConfiguration(BindingPlace::OrderCreate()), + 'request' => $parameters['request'] ?? null, + ]); + + if ('' === $widget) { + return []; + } + + $paymentOption = $this->createPaymentOption($widget); + + return [$paymentOption]; + } + + private function createPaymentOption(string $widget): PaymentOption + { + $additionalInfo = $this->renderer->render('module:inpostizi/views/templates/front/buttonWidget.tpl', [ + 'widget' => $widget, + 'styles' => [], + ]); + + return (new PaymentOption()) + ->setModuleName($this->paymentModule->name) + ->setCallToActionText($this->paymentModule->l('Pay with InPost Pay', self::TRANSLATION_SOURCE)) + ->setBinary(true) // does it do anything? + ->setAdditionalInformation($additionalInfo); + } +} diff --git a/modules/inpostizi/src/Hook/Front/ButtonWidgetRendererTrait.php b/modules/inpostizi/src/Hook/Front/ButtonWidgetRendererTrait.php new file mode 100644 index 00000000..e35bd1cf --- /dev/null +++ b/modules/inpostizi/src/Hook/Front/ButtonWidgetRendererTrait.php @@ -0,0 +1,49 @@ +configuration->getDisplayConfiguration($bindingPlace); + + if (!$configuration->isDisplayed()) { + return ''; + } + + return $this->module->renderWidget($hookName, [ + 'config' => $configuration->getWidgetConfiguration(), + 'request' => $parameters['request'] ?? null, + 'cart' => $parameters['cart'] ?? null, + ]); + } + + private function getHtmlStyles(BindingPlace $bindingPlace): array + { + $configuration = $this->configuration->getDisplayConfiguration($bindingPlace); + $styles = $configuration->getHtmlStyles(); + + if ($styles instanceof \Traversable) { + $styles = iterator_to_array($styles); + } + + return $styles; + } +} diff --git a/modules/inpostizi/src/Hook/Front/DisplayCheckoutSummaryTop.php b/modules/inpostizi/src/Hook/Front/DisplayCheckoutSummaryTop.php new file mode 100644 index 00000000..a1145c98 --- /dev/null +++ b/modules/inpostizi/src/Hook/Front/DisplayCheckoutSummaryTop.php @@ -0,0 +1,63 @@ +generalConfiguration = $generalConfiguration; + $this->configuration = $configuration; + $this->module = $module; + $this->renderer = $renderer; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + /** + * @param array{request?: Request} $parameters + */ + public function execute(array $parameters): string + { + $binding = BindingPlace::CheckoutPage(); + + if ( + self::HOOK_NAME !== $this->generalConfiguration->getCheckoutButtonDisplayHook() + || '' === $widget = $this->renderWidget($binding, $parameters, self::HOOK_NAME) + ) { + return ''; + } + + return $this->renderer->render('module:inpostizi/views/templates/front/buttonWidget.tpl', [ + 'widget' => $widget, + 'styles' => $this->getHtmlStyles($binding), + ]); + } +} diff --git a/modules/inpostizi/src/Hook/Front/DisplayCustomerAccountFormTop.php b/modules/inpostizi/src/Hook/Front/DisplayCustomerAccountFormTop.php new file mode 100644 index 00000000..c8a21823 --- /dev/null +++ b/modules/inpostizi/src/Hook/Front/DisplayCustomerAccountFormTop.php @@ -0,0 +1,53 @@ +configuration = $configuration; + $this->module = $module; + $this->renderer = $renderer; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + /** + * @param array{request?: Request} $parameters + */ + public function execute(array $parameters): string + { + $binding = BindingPlace::RegisterFormPage(); + + if ('' === $widget = $this->renderWidget($binding, $parameters, self::HOOK_NAME)) { + return ''; + } + + return $this->renderer->render('module:inpostizi/views/templates/front/buttonWidget.tpl', [ + 'widget' => $widget, + 'styles' => $this->getHtmlStyles($binding), + ]); + } +} diff --git a/modules/inpostizi/src/Hook/Front/DisplayCustomerLoginFormAfter.php b/modules/inpostizi/src/Hook/Front/DisplayCustomerLoginFormAfter.php new file mode 100644 index 00000000..a85fde6c --- /dev/null +++ b/modules/inpostizi/src/Hook/Front/DisplayCustomerLoginFormAfter.php @@ -0,0 +1,53 @@ +configuration = $configuration; + $this->module = $module; + $this->renderer = $renderer; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + /** + * @param array{request?: Request} $parameters + */ + public function execute(array $parameters): string + { + $binding = BindingPlace::LoginPage(); + + if ('' === $widget = $this->renderWidget($binding, $parameters, self::HOOK_NAME)) { + return ''; + } + + return $this->renderer->render('module:inpostizi/views/templates/front/buttonWidget.tpl', [ + 'widget' => $widget, + 'styles' => $this->getHtmlStyles($binding), + ]); + } +} diff --git a/modules/inpostizi/src/Hook/Front/DisplayExpressCheckout.php b/modules/inpostizi/src/Hook/Front/DisplayExpressCheckout.php new file mode 100644 index 00000000..1fb1985c --- /dev/null +++ b/modules/inpostizi/src/Hook/Front/DisplayExpressCheckout.php @@ -0,0 +1,53 @@ +configuration = $configuration; + $this->module = $module; + $this->renderer = $renderer; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + /** + * @param array{request?: Request} $parameters + */ + public function execute(array $parameters): string + { + $binding = BindingPlace::BasketSummary(); + + if ('' === $widget = $this->renderWidget($binding, $parameters, self::HOOK_NAME)) { + return ''; + } + + return $this->renderer->render('module:inpostizi/views/templates/front/buttonWidget.tpl', [ + 'widget' => $widget, + 'styles' => $this->getHtmlStyles($binding), + ]); + } +} diff --git a/modules/inpostizi/src/Hook/Front/DisplayHeader.php b/modules/inpostizi/src/Hook/Front/DisplayHeader.php new file mode 100644 index 00000000..7344d5e7 --- /dev/null +++ b/modules/inpostizi/src/Hook/Front/DisplayHeader.php @@ -0,0 +1,120 @@ +basketSessionRepository = $basketSessionRepository; + $this->context = $context; + $this->eventDispatcher = $eventDispatcher; + $this->generalConfiguration = $generalConfiguration; + $this->apiConfiguration = $apiConfiguration; + $this->authorizationChecker = $authorizationChecker; + $this->analyticsCookiePersister = $analyticsCookiePersister; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + private function shouldRunUpdateContext(): bool + { + if (!isset($this->context->cart->id) || !$this->context->cookie->exists() || !$this->context->shop->getGroup()->share_order) { + return false; + } + + return true; + } + + /** + * @param array{request?: Request} $parameters + */ + public function execute(array $parameters): void + { + if ($this->shouldRunUpdateContext()) { + $session = $this->basketSessionRepository->findByEntityId($this->context->cart->id); + + if (null === $session || $session->getShopId() === (int) $this->context->shop->id) { + return; + } + + $session->setShopId($this->context->shop->id); + $this->basketSessionRepository->persist($session); + + $this->eventDispatcher->dispatch(new CartUpdatedEvent($this->context->cart)); + } + + $request = $parameters['request'] ?? null; + + if (($request instanceof Request && $request->isXmlHttpRequest()) || !$this->hasRequiredConfiguration() || !$this->authorizationChecker->isGranted(BindingWidgetVoter::VIEW, $request)) { + return; + } + + $this->analyticsCookiePersister->persist($request); + } + + private function hasRequiredConfiguration(): bool + { + return $this->generalConfiguration->isSendAnalyticsData() && null !== $this->apiConfiguration->getClientCredentials() && null !== $this->apiConfiguration->getMerchantClientId(); + } +} diff --git a/modules/inpostizi/src/Hook/Front/DisplayIziCartPreviewButton.php b/modules/inpostizi/src/Hook/Front/DisplayIziCartPreviewButton.php new file mode 100644 index 00000000..3ed45667 --- /dev/null +++ b/modules/inpostizi/src/Hook/Front/DisplayIziCartPreviewButton.php @@ -0,0 +1,53 @@ +configuration = $configuration; + $this->module = $module; + $this->renderer = $renderer; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + /** + * @param array{request?: Request} $parameters + */ + public function execute(array $parameters): string + { + $binding = BindingPlace::MiniCartPage(); + + if ('' === $widget = $this->renderWidget($binding, $parameters, self::HOOK_NAME)) { + return ''; + } + + return $this->renderer->render('module:inpostizi/views/templates/front/buttonWidget.tpl', [ + 'widget' => $widget, + 'styles' => $this->getHtmlStyles($binding), + ]); + } +} diff --git a/modules/inpostizi/src/Hook/Front/DisplayIziCheckoutButton.php b/modules/inpostizi/src/Hook/Front/DisplayIziCheckoutButton.php new file mode 100644 index 00000000..c994a553 --- /dev/null +++ b/modules/inpostizi/src/Hook/Front/DisplayIziCheckoutButton.php @@ -0,0 +1,63 @@ +configuration = $configuration; + $this->generalConfiguration = $generalConfiguration; + $this->module = $module; + $this->renderer = $renderer; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + /** + * @param array{request?: Request} $parameters + */ + public function execute(array $parameters): string + { + $binding = BindingPlace::CheckoutPage(); + + if ( + self::HOOK_NAME !== $this->generalConfiguration->getCheckoutButtonDisplayHook() + || '' === $widget = $this->renderWidget($binding, $parameters, self::HOOK_NAME) + ) { + return ''; + } + + return $this->renderer->render('module:inpostizi/views/templates/front/buttonWidget.tpl', [ + 'widget' => $widget, + 'styles' => $this->getHtmlStyles($binding), + ]); + } +} diff --git a/modules/inpostizi/src/Hook/Front/DisplayIziThankYou.php b/modules/inpostizi/src/Hook/Front/DisplayIziThankYou.php new file mode 100644 index 00000000..7c77cef5 --- /dev/null +++ b/modules/inpostizi/src/Hook/Front/DisplayIziThankYou.php @@ -0,0 +1,77 @@ +paymentModule = $paymentModule; + $this->configuration = $configuration; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + /** + * @param array{order?: OrderLazyArray} $parameters + * + * @return string + */ + public function execute(array $parameters): string + { + $order = $parameters['order'] ?? null; + + $orderObj = $this->getOrderObject($order); + + if (null === $orderObj) { + throw new \InvalidArgumentException('Order object is required.'); + } + + if (!\Validate::isLoadedObject($orderObj)) { + throw new \InvalidArgumentException(sprintf('Order with id "%s" not found.', $orderObj->id)); + } + + if ($this->shouldBeRendered(self::HOOK_NAME, $orderObj)) { + return $this->renderWidgetBlock(); + } + + return ''; + } + + /** + * @param $presentedOrder OrderLazyArray|array + * + * @return \Order|null + */ + private function getOrderObject($presentedOrder): ?\Order + { + $orderId = null; + + if ($presentedOrder instanceof OrderLazyArray) { + $orderDetails = $presentedOrder->getDetails(); + + $orderId = $orderDetails->getId(); + } elseif (!empty($presentedOrder['details']['id'])) { + $orderId = $presentedOrder['details']['id']; + } + + if (null === $orderId) { + return null; + } + + return new \Order($orderId); + } +} diff --git a/modules/inpostizi/src/Hook/Front/DisplayOrderConfirmation.php b/modules/inpostizi/src/Hook/Front/DisplayOrderConfirmation.php new file mode 100644 index 00000000..ddb4a72b --- /dev/null +++ b/modules/inpostizi/src/Hook/Front/DisplayOrderConfirmation.php @@ -0,0 +1,75 @@ +repository = $repository; + $this->context = $context; + $this->paymentModule = $paymentModule; + $this->configuration = $configuration; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + /** + * @param \Order $order + * + * @return void + */ + private function removeSavedBasketId(\Order $order): void + { + if (null === $session = $this->repository->findByEntityId((int) $order->id_cart)) { + return; + } + + if ($session->getBasketId() === $this->context->cookie->inpostizi_basket_id) { + unset($this->context->cookie->inpostizi_basket_id); + } + } + + /** + * @param array{order?: \Order} $parameters + */ + public function execute(array $parameters): string + { + $order = $parameters['order'] ?? null; + + if (!$order instanceof \Order) { + throw new \InvalidArgumentException(sprintf('Expected parameter "order" to be an instance of "%s", "%s" given.', \Order::class, get_debug_type($order))); + } + + $this->removeSavedBasketId($order); + + if ($this->shouldBeRendered(self::HOOK_NAME, $order)) { + return $this->renderWidgetBlock(); + } + + return ''; + } +} diff --git a/modules/inpostizi/src/Hook/Front/DisplayPaymentReturn.php b/modules/inpostizi/src/Hook/Front/DisplayPaymentReturn.php new file mode 100644 index 00000000..e109c01d --- /dev/null +++ b/modules/inpostizi/src/Hook/Front/DisplayPaymentReturn.php @@ -0,0 +1,44 @@ +paymentModule = $paymentModule; + $this->configuration = $configuration; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + /** + * @param array{order?: \Order} $parameters + */ + public function execute(array $parameters): string + { + $order = $parameters['order'] ?? null; + + if (!$order instanceof \Order) { + throw new \InvalidArgumentException(sprintf('Expected parameter "order" to be an instance of "%s", "%s" given.', \Order::class, get_debug_type($order))); + } + + if ($this->shouldBeRendered(self::HOOK_NAME, $order)) { + return $this->renderWidgetBlock(); + } + + return ''; + } +} diff --git a/modules/inpostizi/src/Hook/Front/DisplayProductActions.php b/modules/inpostizi/src/Hook/Front/DisplayProductActions.php new file mode 100644 index 00000000..f5d6971e --- /dev/null +++ b/modules/inpostizi/src/Hook/Front/DisplayProductActions.php @@ -0,0 +1,70 @@ +configuration = $configuration; + $this->generalConfiguration = $generalConfiguration; + $this->module = $module; + $this->renderer = $renderer; + $this->context = $context; + $this->basketSessionRepository = $basketSessionRepository; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + public static function getVersionRange(): VersionRange + { + return new VersionRange('1.7.6'); + } + + /** + * @param array{product?: ProductLazyArray, request?: Request} $parameters + */ + public function execute(array $parameters): string + { + $product = $parameters['product'] ?? null; + + if (!isset($product['id_product']) || !is_numeric($product['id_product'])) { + throw new \InvalidArgumentException(sprintf('Expected parameter "product" to be an instance of "%s", "%s" given.', ProductLazyArray::class, get_debug_type($product))); + } + + if (self::HOOK_NAME !== $this->generalConfiguration->getProductCardDisplayHook()) { + return ''; + } + + return $this->renderer->render('module:inpostizi/views/templates/front/productButtonWidget.tpl', [ + 'widget' => $this->renderWidget($product, $parameters, self::HOOK_NAME), + 'styles' => $this->getHtmlStyles(), + 'hookName' => self::HOOK_NAME, + 'idProduct' => $product['id_product'], + ]); + } +} diff --git a/modules/inpostizi/src/Hook/Front/DisplayProductAdditionalInfo.php b/modules/inpostizi/src/Hook/Front/DisplayProductAdditionalInfo.php new file mode 100644 index 00000000..534f84d8 --- /dev/null +++ b/modules/inpostizi/src/Hook/Front/DisplayProductAdditionalInfo.php @@ -0,0 +1,70 @@ +configuration = $configuration; + $this->generalConfiguration = $generalConfiguration; + $this->module = $module; + $this->renderer = $renderer; + $this->context = $context; + $this->basketSessionRepository = $basketSessionRepository; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + public static function getVersionRange(): VersionRange + { + return new VersionRange(null, '1.7.6'); + } + + /** + * @param array{product?: ProductLazyArray|array, request?: Request} $parameters + */ + public function execute(array $parameters): string + { + $product = $parameters['product'] ?? null; + + if (!isset($product['id_product']) || !is_numeric($product['id_product'])) { + throw new \InvalidArgumentException(sprintf('Expected parameter "product" to be an array or an instance of "%s", "%s" given.', ProductLazyArray::class, get_debug_type($product))); + } + + if (self::HOOK_NAME !== $this->generalConfiguration->getProductCardDisplayHook()) { + return ''; + } + + return $this->renderer->render('module:inpostizi/views/templates/front/productButtonWidget.tpl', [ + 'widget' => $this->renderWidget($product, $parameters, self::HOOK_NAME), + 'styles' => $this->getHtmlStyles(), + 'hookName' => self::HOOK_NAME, + 'idProduct' => $product['id_product'], + ]); + } +} diff --git a/modules/inpostizi/src/Hook/Front/DisplayProductFooter.php b/modules/inpostizi/src/Hook/Front/DisplayProductFooter.php new file mode 100644 index 00000000..6aa1ecc0 --- /dev/null +++ b/modules/inpostizi/src/Hook/Front/DisplayProductFooter.php @@ -0,0 +1,59 @@ +configuration = $configuration; + $this->generalConfiguration = $generalConfiguration; + $this->module = $module; + $this->renderer = $renderer; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + /** + * @param array{product?: ProductLazyArray, request?: Request} $parameters + */ + public function execute(array $parameters): string + { + $product = $parameters['product'] ?? null; + + if (!isset($product['id_product']) || !is_numeric($product['id_product'])) { + throw new \InvalidArgumentException(sprintf('Parameter "product" expected to be an array or an instance of "%s", "%s" given.', ProductLazyArray::class, get_debug_type($product))); + } + + if ('' === $widget = $this->renderWidget((int) $product['id_product'], $parameters, self::HOOK_NAME)) { + return ''; + } + + return $this->renderer->render('module:inpostizi/views/templates/hook/buttonWidget.tpl', [ + 'widget' => $widget, + 'styles' => [], + ]); + } +} diff --git a/modules/inpostizi/src/Hook/Front/DisplayShoppingCart.php b/modules/inpostizi/src/Hook/Front/DisplayShoppingCart.php new file mode 100644 index 00000000..ded46609 --- /dev/null +++ b/modules/inpostizi/src/Hook/Front/DisplayShoppingCart.php @@ -0,0 +1,53 @@ +configuration = $configuration; + $this->module = $module; + $this->renderer = $renderer; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + /** + * @param array{request?: Request} $parameters + */ + public function execute(array $parameters): string + { + $binding = BindingPlace::BasketSummary(); + + if ('' === $widget = $this->renderWidget($binding, $parameters, self::HOOK_NAME)) { + return ''; + } + + return $this->renderer->render('module:inpostizi/views/templates/front/buttonWidget.tpl', [ + 'widget' => $widget, + 'styles' => $this->getHtmlStyles($binding), + ]); + } +} diff --git a/modules/inpostizi/src/Hook/Front/DisplayShoppingCartFooter.php b/modules/inpostizi/src/Hook/Front/DisplayShoppingCartFooter.php new file mode 100644 index 00000000..ee666c74 --- /dev/null +++ b/modules/inpostizi/src/Hook/Front/DisplayShoppingCartFooter.php @@ -0,0 +1,53 @@ +configuration = $configuration; + $this->module = $module; + $this->renderer = $renderer; + } + + public static function getHookName(): string + { + return self::HOOK_NAME; + } + + /** + * @param array{request?: Request} $parameters + */ + public function execute(array $parameters): string + { + $binding = BindingPlace::BasketSummary(); + + if ('' === $widget = $this->renderWidget($binding, $parameters, self::HOOK_NAME)) { + return ''; + } + + return $this->renderer->render('module:inpostizi/views/templates/front/buttonWidget.tpl', [ + 'widget' => $widget, + 'styles' => $this->getHtmlStyles($binding), + ]); + } +} diff --git a/modules/inpostizi/src/Hook/Front/ProductWidgetRendererTrait.php b/modules/inpostizi/src/Hook/Front/ProductWidgetRendererTrait.php new file mode 100644 index 00000000..13458134 --- /dev/null +++ b/modules/inpostizi/src/Hook/Front/ProductWidgetRendererTrait.php @@ -0,0 +1,115 @@ += $productId = (int) $product['id_product']) { + return ''; + } + + if (!$this->shouldDisplayWidget($hookName, $product)) { + return ''; + } + + $configuration = $this->configuration->getDisplayConfiguration(BindingPlace::ProductCard()); + + if (!$configuration->isDisplayed($product)) { + return ''; + } + + $widgetConfig = $configuration + ->getWidgetConfiguration() + ->setProductId((string) $productId); + + return $this->module->renderWidget($hookName, [ + 'config' => $widgetConfig, + 'request' => $parameters['request'] ?? null, + 'cart' => $parameters['cart'] ?? null, + ]); + } + + /** + * @param ProductLazyArray|array{add_to_cart_url: string|null} $product + */ + private function shouldDisplayWidget(string $hookName, $product): bool + { + return $this->checkProductAvailability($product) || $this->isCartBound(); + } + + /** + * @param ProductLazyArray|array $product + */ + private function checkProductAvailability($product): bool + { + if (!$product['available_for_order']) { + return false; + } + + return $product['allow_oosp'] + || \StockAvailable::getQuantityAvailableByProduct($product['id_product'], $product['id_product_attribute'], $this->context->shop->id) >= $product['minimal_quantity']; + } + + private function isCartBound(): bool + { + $basketSession = $this->basketSessionRepository->findByEntityId($this->context->cart->id); + + if (null === $basketSession) { + return false; + } + + return $basketSession->isBasketBound(); + } + + private function getHtmlStyles(): array + { + $styles = $this->configuration + ->getDisplayConfiguration(BindingPlace::ProductCard()) + ->getHtmlStyles(); + + if ($styles instanceof \Traversable) { + $styles = iterator_to_array($styles); + } + + return $styles; + } +} diff --git a/modules/inpostizi/src/Hook/Front/ThankYouWidgetRendererTrait.php b/modules/inpostizi/src/Hook/Front/ThankYouWidgetRendererTrait.php new file mode 100644 index 00000000..a50f9f14 --- /dev/null +++ b/modules/inpostizi/src/Hook/Front/ThankYouWidgetRendererTrait.php @@ -0,0 +1,45 @@ +paymentModule->name === $order->module + && $this->shouldDisplayHook($hookName); + } + + private function shouldDisplayHook(string $hookName): bool + { + return $this->configuration->getThankYouDisplayHook() === $hookName; + } + + /** + * @return string + */ + private function renderWidgetBlock(): string + { + return ''; + } +} diff --git a/modules/inpostizi/src/Hook/HookDispatcherInterface.php b/modules/inpostizi/src/Hook/HookDispatcherInterface.php new file mode 100644 index 00000000..7212ee90 --- /dev/null +++ b/modules/inpostizi/src/Hook/HookDispatcherInterface.php @@ -0,0 +1,10 @@ +locator = $locator; + $this->widget = $widget; + } + + public static function getSubscribedServices(): array + { + return [ + Admin\ActionAdminControllerSetMedia::HOOK_NAME => '?' . Admin\ActionAdminControllerSetMedia::class, + Admin\ActionAdminCartRuleSaveAfter::HOOK_NAME => '?' . Admin\ActionAdminCartRuleSaveAfter::class, + Admin\DisplayBackOfficeHeader::HOOK_NAME => '?' . Admin\DisplayBackOfficeHeader::class, + Admin\DisplayAdminOrderLeft::HOOK_NAME => '?' . Admin\DisplayAdminOrderLeft::class, + Admin\DisplayAdminOrderSide::HOOK_NAME => '?' . Admin\DisplayAdminOrderSide::class, + Admin\ActionAdminInPostConfirmedShipmentsControllerAfter::HOOK_NAME => '?' . Admin\ActionAdminInPostConfirmedShipmentsControllerAfter::class, + Admin\ActionAdminInPostConfirmedShipmentsControllerBefore::HOOK_NAME => '?' . Admin\ActionAdminInPostConfirmedShipmentsControllerBefore::class, + + Common\ActionCartDeleteBefore::HOOK_NAME => Common\ActionCartDeleteBefore::class, + Common\ActionCartUpdateAfter::HOOK_NAME => Common\ActionCartUpdateAfter::class, + Common\ActionValidateOrder::HOOK_NAME => Common\ActionValidateOrder::class, + Common\ActionOrderStatusPostUpdate::HOOK_NAME => Common\ActionOrderStatusPostUpdate::class, + Common\ActionShipmentAddAfter::HOOK_NAME => Common\ActionShipmentAddAfter::class, + Common\ActionShipmentUpdateBefore::HOOK_NAME => Common\ActionShipmentUpdateBefore::class, + Common\ActionShipmentUpdateAfter::HOOK_NAME => Common\ActionShipmentUpdateAfter::class, + Common\ActionObjectOrderUpdateBefore::HOOK_NAME => Common\ActionObjectOrderUpdateBefore::class, + Common\ActionObjectOrderUpdateAfter::HOOK_NAME => Common\ActionObjectOrderUpdateAfter::class, + Common\ActionEmailSendBefore::HOOK_NAME => Common\ActionEmailSendBefore::class, + + // products + Common\Product\ActionProductDeleteBefore::HOOK_NAME => Common\Product\ActionProductDeleteBefore::class, + Common\Product\ActionProductDeleteAfter::HOOK_NAME => Common\Product\ActionProductDeleteAfter::class, + Common\Product\ActionProductUpdateAfter::HOOK_NAME => Common\Product\ActionProductUpdateAfter::class, + Common\Product\ActionCombinationDeleteBefore::HOOK_NAME => Common\Product\ActionCombinationDeleteBefore::class, + Common\Product\ActionCombinationDeleteAfter::HOOK_NAME => Common\Product\ActionCombinationDeleteAfter::class, + Common\Product\ActionCombinationUpdateAfter::HOOK_NAME => Common\Product\ActionCombinationUpdateAfter::class, + Common\Product\ActionImageAddAfter::HOOK_NAME => Common\Product\ActionImageAddAfter::class, + Common\Product\ActionImageDeleteAfter::HOOK_NAME => Common\Product\ActionImageDeleteAfter::class, + Common\Product\ActionSpecificPriceAddAfter::HOOK_NAME => Common\Product\ActionSpecificPriceAddAfter::class, + Common\Product\ActionSpecificPriceUpdateAfter::HOOK_NAME => Common\Product\ActionSpecificPriceUpdateAfter::class, + Common\Product\ActionSpecificPriceDeleteAfter::HOOK_NAME => Common\Product\ActionSpecificPriceDeleteAfter::class, + Common\Product\ActionUpdateQuantity::HOOK_NAME => Common\Product\ActionUpdateQuantity::class, + + Front\ActionCartControllerAjaxUpdateResponse::HOOK_NAME => '?' . Front\ActionCartControllerAjaxUpdateResponse::class, + Front\ActionFrontControllerSetMedia::HOOK_NAME => '?' . Front\ActionFrontControllerSetMedia::class, + Front\DisplayOrderConfirmation::HOOK_NAME => '?' . Front\DisplayOrderConfirmation::class, + Front\DisplayPaymentReturn::HOOK_NAME => '?' . Front\DisplayPaymentReturn::class, + Front\DisplayIziThankYou::HOOK_NAME => '?' . Front\DisplayIziThankYou::class, + Front\DisplayProductAdditionalInfo::HOOK_NAME => '?' . Front\DisplayProductAdditionalInfo::class, + Front\DisplayProductActions::HOOK_NAME => '?' . Front\DisplayProductActions::class, + Front\DisplayExpressCheckout::HOOK_NAME => '?' . Front\DisplayExpressCheckout::class, + Front\DisplayCustomerLoginFormAfter::HOOK_NAME => '?' . Front\DisplayCustomerLoginFormAfter::class, + Front\DisplayCustomerAccountFormTop::HOOK_NAME => '?' . Front\DisplayCustomerAccountFormTop::class, + Front\DisplayCheckoutSummaryTop::HOOK_NAME => '?' . Front\DisplayCheckoutSummaryTop::class, + Front\DisplayIziCartPreviewButton::HOOK_NAME => '?' . Front\DisplayIziCartPreviewButton::class, + Front\DisplayIziCheckoutButton::HOOK_NAME => '?' . Front\DisplayIziCheckoutButton::class, + Front\DisplayHeader::HOOK_NAME => '?' . Front\DisplayHeader::class, + ]; + } + + public function execute(string $hookName, array $parameters) + { + if (null !== $hook = $this->getHook($hookName)) { + return $hook->execute($parameters); + } + + if (null !== $this->widget && \Hook::isDisplayHookName($hookName)) { + return $this->widget->renderWidget($hookName, $parameters); + } + + throw HookNotImplementedException::create($hookName); + } + + /** + * @return string[] + */ + public static function getHooksToInstall(string $psVersion): array + { + $hookNames = []; + + foreach (self::getSubscribedServices() as $hookName => $serviceName) { + if (in_array($hookName, self::DEPRECATED_HOOKS, true)) { + continue; + } + + $class = '?' === $serviceName[0] ? substr($serviceName, 1) : $serviceName; + + if (is_subclass_of($class, AliasedHookInterface::class)) { + foreach (self::getAliases($class, $psVersion) as $alias) { + $hookNames[] = $alias; + } + } elseif ( + !is_subclass_of($class, PrestaShopVersionAwareHookInterface::class) + || $class::getVersionRange()->contains($psVersion) + ) { + $hookNames[] = $hookName; + } + } + + return $hookNames; + } + + private function getHook(string $name): ?HookInterface + { + if ($this->locator->has($name)) { + return $this->locator->get($name); + } + + static $canonicalNames; + + if (!isset($canonicalNames)) { + $canonicalNames = []; + + foreach (self::getSubscribedServices() as $canonicalName => $ignored) { + $normalizedName = strtolower($canonicalName); + $canonicalNames[$normalizedName] = $canonicalName; + } + } + + $normalizedName = strtolower($name); + + if (!isset($canonicalNames[$normalizedName])) { + return null; + } + + $canonicalName = $canonicalNames[$normalizedName]; + + if (!$this->locator->has($canonicalName)) { + throw HookNotFoundException::create($name); + } + + return $this->locator->get($canonicalName); + } + + /** + * @param class-string $class + * + * @return string[] + */ + private static function getAliases(string $class, string $psVersion): array + { + $aliases = []; + + foreach ($class::getAliases() as $alias => $versionRange) { + if (null === $versionRange || $versionRange->contains($psVersion)) { + $aliases[] = $alias; + } + } + + return $aliases; + } +} diff --git a/modules/inpostizi/src/Hook/HookExecutorInterface.php b/modules/inpostizi/src/Hook/HookExecutorInterface.php new file mode 100644 index 00000000..5567f7d1 --- /dev/null +++ b/modules/inpostizi/src/Hook/HookExecutorInterface.php @@ -0,0 +1,13 @@ +min = $min; + $this->max = $max; + } + + public function getMinVersion(): ?string + { + return $this->min; + } + + public function getMaxVersion(): ?string + { + return $this->max; + } + + public function contains(string $version): bool + { + $minVersion = $this->getMinVersion(); + if (null !== $minVersion && \Tools::version_compare($version, $minVersion)) { + return false; + } + + $maxVersion = $this->getMaxVersion(); + + return null === $maxVersion || \Tools::version_compare($version, $maxVersion); + } +} diff --git a/modules/inpostizi/src/Hook/Widget.php b/modules/inpostizi/src/Hook/Widget.php new file mode 100644 index 00000000..7cb8b22a --- /dev/null +++ b/modules/inpostizi/src/Hook/Widget.php @@ -0,0 +1,66 @@ +renderer = $renderer; + $this->parametersProvider = $parametersProvider; + $this->template = $template; + } + + /** + * @param string|null $hookName + */ + public function renderWidget($hookName, array $configuration): string + { + if ([] === $parameters = $this->getWidgetVariables($hookName, $configuration)) { + return ''; + } + + return $this->renderer->render($this->template, $parameters); + } + + /** + * @param string|null $hookName + */ + public function getWidgetVariables($hookName, array $configuration): array + { + $this->assertIsValidHookName($hookName); + + return $this->parametersProvider->getParameters($hookName, $configuration); + } + + private function assertIsValidHookName($hookName): void + { + if (null === $hookName || is_string($hookName)) { + return; + } + + throw new \InvalidArgumentException(sprintf('Expected hook name to be a string or null, "%s" given', get_debug_type($hookName))); + } +} diff --git a/modules/inpostizi/src/Hook/WidgetParametersProvider.php b/modules/inpostizi/src/Hook/WidgetParametersProvider.php new file mode 100644 index 00000000..259416ad --- /dev/null +++ b/modules/inpostizi/src/Hook/WidgetParametersProvider.php @@ -0,0 +1,103 @@ +configuration = $configuration; + $this->authorizationChecker = $authorizationChecker; + $this->resolver = $resolver; + $this->repository = $repository; + $this->validator = $validator; + } + + public function getParameters(?string $hookName, array $parameters): array + { + if (!$this->hasRequiredConfiguration()) { + return []; + } + + $request = $parameters['request'] ?? null; + + if (!$request instanceof Request) { + throw new \InvalidArgumentException(sprintf('Parameter "request" expected to be an instance of "%s", "%s" given.', Request::class, get_debug_type($request))); + } + + if (!$this->authorizationChecker->isGranted(BindingWidgetVoter::VIEW, $request)) { + return []; + } + + $cart = $parameters['cart'] ?? null; + + if (!$cart instanceof \Cart) { + throw new \InvalidArgumentException(sprintf('Parameter "cart" expected to be an instance of "%s", "%s" given.', \Cart::class, get_debug_type($cart))); + } + + $widgetConfiguration = $this->resolver->resolve($parameters); + + if (!$this->isBindable($cart, $widgetConfiguration->getBindingPlace())) { + return []; + } + + return [ + 'attributes' => $widgetConfiguration, + ]; + } + + private function hasRequiredConfiguration(): bool + { + return null !== $this->configuration->getClientCredentials() && null !== $this->configuration->getMerchantClientId(); + } + + private function isBindable(\Cart $cart, BindingPlace $bindingPlace): bool + { + $session = $this->repository->findByEntityId((int) $cart->id); + + if (null !== $session && $session->isBasketBound()) { + return true; + } + + $violations = $this->validator->validate($cart, new Bindable($bindingPlace)); + + return 0 === count($violations); + } +} diff --git a/modules/inpostizi/src/Hook/WidgetParametersProviderInterface.php b/modules/inpostizi/src/Hook/WidgetParametersProviderInterface.php new file mode 100644 index 00000000..018afcf1 --- /dev/null +++ b/modules/inpostizi/src/Hook/WidgetParametersProviderInterface.php @@ -0,0 +1,10 @@ + hot products by product ID + */ + private $productsMap; + + /** + * @var \Product|null product being deleted + */ + private $product; + + /** + * @var \Combination|null combination being deleted + */ + private $combination; + + private $shutdownRegistered = false; + + /** + * @var array + */ + private $toDelete = []; + + /** + * @var array + */ + private $toUpdate = []; + + public function __construct(Context $context, HotProductRepositoryInterface $repository, PriceCalculatorInterface $calculator, CommandBusInterface $bus, LoggerInterface $logger, ?HotProductValidator $validator = null) + { + $this->context = $context; + $this->repository = $repository; + $this->calculator = $calculator; + $this->bus = $bus; + $this->logger = $logger; + $this->validator = $validator ?? self::createValidator(); + } + + public static function getSubscribedEvents(): array + { + return [ + ProductEvent::DELETION => 'onProductDeletion', + ProductEvent::DELETED => 'onProductDeleted', + ProductEvent::UPDATED => 'onProductUpdated', + CombinationEvent::DELETION => 'onCombinationDeletion', + CombinationEvent::DELETED => 'onCombinationDeleted', + CombinationEvent::UPDATED => 'onCombinationUpdated', + ImageEvent::CREATED => 'onImagesUpdated', + ImageEvent::DELETED => 'onImagesUpdated', + SpecificPriceEvent::CREATED => 'onSpecificPricesUpdated', + SpecificPriceEvent::UPDATED => 'onSpecificPricesUpdated', + SpecificPriceEvent::DELETED => 'onSpecificPricesUpdated', + StockQuantityUpdatedEvent::class => 'onStockQuantityUpdate', + ]; + } + + public function onProductDeletion(ProductEvent $event): void + { + $product = $event->getProduct(); + + if ([] === $this->getHotProducts((int) $product->id)) { + return; + } + + $this->product = $product; // keep an object reference to check after deletion + } + + public function onProductDeleted(ProductEvent $event): void + { + if ($this->product !== $event->getProduct()) { + return; + } + + foreach ($this->getHotProducts((int) $this->product->id) as $hotProduct) { + if (!$this->wasUpdatedInShop($this->product, $hotProduct->getShopId())) { + continue; + } + + $this->scheduleDelete($hotProduct); + } + + $this->product = null; + } + + public function onProductUpdated(ProductEvent $event): void + { + $product = $event->getProduct(); + $updatedFields = $this->getUpdatedFields($product); + + if (null !== $updatedFields && [] === array_intersect(array_keys($updatedFields), self::OBSERVED_PRODUCT_PROPERTIES)) { + return; + } + + if ([] === $hotProducts = $this->getHotProducts((int) $product->id)) { + return; + } + + foreach ($hotProducts as $hotProduct) { + if (array_key_exists($hotProduct->getId(), $this->toDelete)) { + return; + } + + if (!$this->wasUpdatedInShop($product, $hotProduct->getShopId())) { + continue; + } + + $this->scheduleUpdate($hotProduct); + } + } + + public function onCombinationDeletion(CombinationEvent $event): void + { + $combination = $event->getCombination(); + $combinationId = (int) $this->combination->id; + + foreach ($this->getHotProducts((int) $combination->id_product) as $hotProduct) { + if ($hotProduct->getCombinationId() !== $combinationId) { + continue; + } + + $this->combination = $combination; // keep an object reference to check after deletion + + return; + } + } + + public function onCombinationDeleted(CombinationEvent $event): void + { + if ($this->combination !== $event->getCombination()) { + return; + } + + $combinationId = (int) $this->combination->id; + + foreach ($this->getHotProducts((int) $this->combination->id_product) as $hotProduct) { + if ($hotProduct->getCombinationId() !== $combinationId) { + continue; + } + + if (!$this->wasUpdatedInShop($this->combination, $hotProduct->getShopId())) { + continue; + } + + $this->scheduleDelete($hotProduct); + } + + $this->combination = null; + } + + public function onCombinationUpdated(CombinationEvent $event): void + { + $combination = $event->getCombination(); + $updatedFields = $this->getUpdatedFields($combination); + + if (null !== $updatedFields && [] === array_intersect(array_keys($updatedFields), self::OBSERVED_COMBINATION_PROPERTIES)) { + return; + } + + $productId = (int) $combination->id_product; + $combinationId = (int) $combination->id; + + foreach ($this->getHotProducts($productId) as $hotProduct) { + if (array_key_exists($hotProduct->getId(), $this->toDelete)) { + return; + } + + if ($hotProduct->getCombinationId() !== $combinationId) { + continue; + } + + if (!$this->wasUpdatedInShop($combination, $hotProduct->getShopId())) { + continue; + } + + $this->scheduleUpdate($hotProduct); + } + } + + public function onImagesUpdated(ImageEvent $event): void + { + $image = $event->getImage(); + $productId = (int) $image->id_product; + + foreach ($this->getHotProducts($productId) as $hotProduct) { + if (array_key_exists($hotProduct->getId(), $this->toDelete)) { + return; + } + + if (!$this->wasUpdatedInShop($image, $hotProduct->getShopId())) { + continue; + } + + $this->scheduleUpdate($hotProduct); + } + } + + public function onSpecificPricesUpdated(SpecificPriceEvent $event): void + { + $price = $event->getPrice(); + + if ($price->id_cart || $price->id_customer) { + return; + } + + $productId = (int) $price->id_product; + + foreach ($this->getHotProducts($productId) as $hotProduct) { + if (array_key_exists($hotProduct->getId(), $this->toDelete)) { + return; + } + + if (!$this->doesAffectProduct($hotProduct, $price)) { + continue; + } + + $this->scheduleUpdate($hotProduct); + } + } + + public function onStockQuantityUpdate(StockQuantityUpdatedEvent $event): void + { + if (0 === $event->getDeltaQuantity()) { + return; + } + + $combinationId = $event->getCombinationId(); + $updatedShopId = $event->getShopId(); + $shopGroup = is_callable([$this->context, 'getContextShopGroup']) + ? $this->context->getContextShopGroup() + : \Shop::getContextShopGroup(); + + foreach ($this->getHotProducts($event->getProductId()) as $hotProduct) { + if ((int) $hotProduct->getCombinationId() !== $combinationId) { + continue; + } + + $shopId = $hotProduct->getShopId(); + + if (!$shopGroup->share_stock && null !== $shopId && $updatedShopId !== $shopId) { + continue; + } + + $this->scheduleUpdate($hotProduct); + } + } + + private function scheduleDelete(HotProduct $product): void + { + $this->registerShutdownFunction(); + $this->toDelete[$product->getId()] = $product; + unset($this->toUpdate[$product->getId()]); + } + + private function scheduleUpdate(HotProduct $product): void + { + $this->registerShutdownFunction(); + $this->toUpdate[$product->getId()] = $product; + } + + private function registerShutdownFunction(): void + { + if ($this->shutdownRegistered) { + return; + } + + register_shutdown_function(function () { + try { + $this->processUpdates(); + } finally { + $this->toUpdate = $this->toDelete = []; + } + }); + + $this->shutdownRegistered = true; + } + + private function processUpdates(): void + { + foreach ($this->toUpdate as $id => $product) { + try { + $this->validator->validate($product); + } catch (InvalidProductDataException $e) { + $this->toDelete[$id] = $product; + unset($this->toUpdate[$id]); + } catch (\Throwable $e) { + // ignore and attempt to update anyway + } + } + + foreach ($this->toDelete as $product) { + try { + $this->bus->handle(new DeleteRemoteProductCommand((string) $product->getReferenceId())); + } catch (BasketAppException|NetworkExceptionInterface|OAuth2ExceptionInterface $e) { + $this->logger->error('Failed to delete hot product "{id}".', [ + 'id' => (string) $product->getReferenceId(), + ]); + } catch (\Throwable $e) { + $this->logger->error('Failed to delete hot product "{id}". Error: {exception}', [ + 'id' => (string) $product->getReferenceId(), + 'exception' => $e, + ]); + } + } + + foreach ($this->toUpdate as $product) { + try { + $this->bus->handle(new UpdateHotProductCommand($product->getId())); + } catch (ProductNotFoundException $e) { + // ignore + } catch (BasketAppException|NetworkExceptionInterface|OAuth2ExceptionInterface $e) { + $this->logger->error('Failed to update hot product "{id}" data.', [ + 'id' => (string) $product->getReferenceId(), + ]); + } catch (\Throwable $e) { + $this->logger->error('Failed to update hot product "{id}" data. Error: {exception}', [ + 'id' => (string) $product->getReferenceId(), + 'exception' => $e, + ]); + } + } + } + + private function getHotProducts(int $productId): array + { + if (!isset($this->productsMap)) { + $this->loadHotProductData(); + } + + return $this->productsMap[$productId] ?? []; + } + + private function loadHotProductData(): void + { + $this->productsMap = []; + + foreach ($this->repository->findAll() as $product) { + $this->productsMap[$product->getProductId()][] = $product; + } + } + + private function wasUpdatedInShop(\ObjectModel $model, ?int $shopId): bool + { + if (null === $shopId) { + return true; + } + + $shopIds = $model->id_shop_list ?: $this->context->getContextListShopID(); + + return in_array($shopId, $shopIds, false); + } + + private function doesAffectProduct(HotProduct $product, \SpecificPrice $price): bool + { + $combinationId = (int) $price->id_product_attribute; + + if (0 !== $combinationId && $product->getCombinationId() !== $combinationId) { + return false; + } + + $shopId = (int) $price->id_shop; + $currencyId = (int) $price->id_currency; + $countryId = (int) $price->id_country; + $customerGroupId = (int) $price->id_group; + + if (0 === $shopId && 0 === $currencyId && 0 === $countryId && 0 === $customerGroupId) { + return true; + } + + $productShopId = $product->getShopId(); + + if (0 !== $shopId && null !== $productShopId && $productShopId !== $shopId) { + return false; + } + + $parameters = $this->calculator->getCalculationParameters(Currency::getDefault(), $productShopId); + + if (0 !== $currencyId && $currencyId !== $parameters->getCurrencyId()) { + return false; + } + + if (0 !== $countryId && $countryId !== $parameters->getCountryId()) { + return false; + } + + if (0 !== $customerGroupId && $customerGroupId !== $parameters->getCustomerGroupId()) { + return false; + } + + return true; + } + + private function getUpdatedFields(\ObjectModel $model): ?array + { + if (is_callable([$model, 'getFieldsToUpdate'])) { + return $model->getFieldsToUpdate(); + } + + return (\Closure::bind(function () { + return $this->update_fields; + }, $model, \ObjectModel::class))(); + } + + private static function createValidator(): HotProductValidator + { + @trigger_error(sprintf('Not passing a $validator to "%s::__construct()" is deprecated since version 2.2.2.', __CLASS__), E_USER_DEPRECATED); + + /** @var \InPostIzi $module */ + $module = \Module::getInstanceByName('inpostizi'); + $repository = $module->get(ObjectManagerInterface::class)->getRepository(\Product::class); + + return new HotProductValidator($repository); + } +} diff --git a/modules/inpostizi/src/HotProduct/Exception/HotProductExceptionInterface.php b/modules/inpostizi/src/HotProduct/Exception/HotProductExceptionInterface.php new file mode 100644 index 00000000..46bd8fcc --- /dev/null +++ b/modules/inpostizi/src/HotProduct/Exception/HotProductExceptionInterface.php @@ -0,0 +1,9 @@ +context = $context; + $this->translator = $translator; + $this->urlGenerator = $urlGenerator; + } + + public function getParent(): string + { + return UpdateHotProductType::class; + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder->add('productId', ObjectModelAutocompleteType::class, [ + 'class' => \Product::class, + 'input' => 'id', + 'label' => $this->context->getTranslator()->trans('Product', [], 'Admin.Global'), + 'placeholder' => $this->translator->l('Search products by name or reference...', self::TRANSLATION_SOURCE), + 'autocomplete_url' => $this->urlGenerator->generate('admin_inpost_izi_products_autocomplete'), + 'choice_label' => static function (\Product $product): string { + $label = $product->name ?? 'Product #' . $product->id; + + if ($product->reference) { + $label .= ' (ref. ' . $product->reference . ')'; + } + + return $label; + }, + ]); + + $builder->get('productId')->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) { + $product = $event->getForm()->getNormData(); + + if (!$product instanceof \Product || !$product->cache_default_attribute) { + return; + } + + $event->getForm()->getParent()->add('combinationId', CombinationByAttributesChoiceType::class, [ + 'label' => $this->context->getTranslator()->trans('Combination', [], 'Admin.Global'), + 'product_id' => (int) $product->id, + 'input' => 'id', + ]); + }); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => CreateHotProductCommand::class, + ]); + } +} diff --git a/modules/inpostizi/src/HotProduct/Form/UpdateHotProductType.php b/modules/inpostizi/src/HotProduct/Form/UpdateHotProductType.php new file mode 100644 index 00000000..3a6dfcd2 --- /dev/null +++ b/modules/inpostizi/src/HotProduct/Form/UpdateHotProductType.php @@ -0,0 +1,59 @@ +translator = $translator; + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + ->add('availableFrom', DatePickerType::class, [ + 'required' => false, + 'label' => $this->translator->l('Available from', self::TRANSLATION_SOURCE), + 'date_format' => 'YYYY-MM-DD HH:mm:ss', + ]) + ->add('availableTo', DatePickerType::class, [ + 'required' => false, + 'label' => $this->translator->l('Available to', self::TRANSLATION_SOURCE), + 'date_format' => 'YYYY-MM-DD HH:mm:ss', + ]); + + $dateTransformer = new DataTransformerChain([ + class_exists(DateTimeImmutableToDateTimeTransformer::class) ? new DateTimeImmutableToDateTimeTransformer() : new DateTransformerPolyfill(), + new DateTimeToStringTransformer(), + ]); + + foreach (['availableFrom', 'availableTo'] as $name) { + $builder->get($name)->addModelTransformer($dateTransformer); + } + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => UpdateHotProductCommand::class, + ]); + } +} diff --git a/modules/inpostizi/src/HotProduct/HotProduct.php b/modules/inpostizi/src/HotProduct/HotProduct.php new file mode 100644 index 00000000..de5403e3 --- /dev/null +++ b/modules/inpostizi/src/HotProduct/HotProduct.php @@ -0,0 +1,137 @@ +productId = $productId; + $this->combinationId = $combinationId; + $this->shopId = $shopId; + $this->referenceId = $referenceId ?? ReferenceId::create($productId, $combinationId); + $this->setAvailability($availableFrom, $availableTo); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getProductId(): int + { + return $this->productId; + } + + public function getCombinationId(): ?int + { + return $this->combinationId; + } + + public function getShopId(): ?int + { + return $this->shopId; + } + + public function getReferenceId(): ReferenceId + { + return $this->referenceId; + } + + public function getAvailableFrom(): ?\DateTimeInterface + { + return $this->availableFrom; + } + + public function getAvailableTo(): ?\DateTimeInterface + { + return $this->availableTo; + } + + public function setAvailability(?\DateTimeImmutable $from, ?\DateTimeImmutable $to): void + { + if (null !== $from && null !== $to && $from > $to) { + throw new InvalidProductDataException('Start date cannot be later than end date.'); + } + + $this->availableFrom = $from; + $this->availableTo = $to; + } + + public function getCreatedAt(): ?\DateTimeImmutable + { + return $this->createdAt; + } + + public function getUpdatedAt(): ?\DateTimeImmutable + { + return $this->updatedAt; + } + + private static function validateReferenceId(ReferenceId $referenceId, int $productId, ?int $combinationId): bool + { + if ($referenceId->getProductId() !== $productId) { + return false; + } + + if (null === $referenceId->getCombinationId()) { + return true; + } + + return $combinationId === $referenceId->getCombinationId(); + } +} diff --git a/modules/inpostizi/src/HotProduct/HotProductDataMapper.php b/modules/inpostizi/src/HotProduct/HotProductDataMapper.php new file mode 100644 index 00000000..47b3b1fa --- /dev/null +++ b/modules/inpostizi/src/HotProduct/HotProductDataMapper.php @@ -0,0 +1,218 @@ + + */ + private $languageRepository; + + /** + * @var ProductRepository + */ + private $productRepository; + + /** + * @var CombinationRepository + */ + private $combinationRepository; + + /** + * @var PriceCalculatorInterface + */ + private $priceCalculator; + + /** + * @var ImageUrlsProviderInterface + */ + private $imageProvider; + + /** + * @var \Context + */ + private $context; + + /** + * @var array Polish language by shop ID + */ + private $languages = []; + + /** + * @param ObjectRepositoryInterface<\Language> $languageRepository + * @param ProductRepository $productRepository + * @param CombinationRepository $combinationRepository + */ + public function __construct(PrestaShopConfiguration $configuration, ObjectRepositoryInterface $languageRepository, ObjectRepositoryInterface $productRepository, ObjectRepositoryInterface $combinationRepository, PriceCalculatorInterface $priceCalculator, ImageUrlsProviderInterface $imageProvider, ?\Context $context = null) + { + $this->configuration = $configuration; + $this->languageRepository = $languageRepository; + $this->productRepository = $productRepository; + $this->combinationRepository = $combinationRepository; + $this->priceCalculator = $priceCalculator; + $this->imageProvider = $imageProvider; + $this->context = $context ?? \Context::getContext(); + } + + public function map(HotProduct $hotProduct): Product + { + $shopId = $hotProduct->getShopId() ?? $this->configuration->getDefaultShopId(); + + $language = $this->getPolishLanguage($shopId); + $languageId = (int) $language->id; + + $productWithCombination = $this->productRepository->findWithCombination( + $productId = $hotProduct->getProductId(), + $hotProduct->getCombinationId(), + $languageId, + $shopId, + true + ); + + if (null === $productWithCombination) { + throw new \RuntimeException('Product not found.'); + } + + $product = $productWithCombination->getProduct(); + $combination = $productWithCombination->getCombination(); + $combinationId = $combination ? (int) $combination->id : null; + + $imageUrls = $this->imageProvider->getImageUrls($productId, $combinationId, $language, $shopId); + + $currency = Currency::getDefault(); + $price = $this->priceCalculator->calculatePrice(new PriceQuery( + $productId, + $shopId, + $currency, + $combinationId + )); + + if (HotProductValidator::isAvailableForOrder($product)) { + $quantity = $this->getAvailableQuantity($product, $combination, $shopId); + $availability = $this->getAvailability($hotProduct); + } else { + // Should be deleted, but we might have not been able to react to the associated update event (or the hot product was created outside PrestaShop). + // Passing 0 as available quantity and a past end of availability date should cause the product to become inactive on the InPost side. + $quantity = 0; + $availability = new ProductAvailability(null, new \DateTimeImmutable('yesterday')); + } + + $ean = $combination && $combination->ean13 ? $combination->ean13 : $product->ean13; + + return new Product( + \Tools::substr($product->name ?? '', 0, 255), + DescriptionFormatter::formatDescription($product), + (string) $imageUrls->getMainImageUrl(), + $price, + $currency, + Quantity::integer($quantity), + (string) $ean, + $availability, + $imageUrls->getAdditionalImages(), + $this->getAttributes($combination, $languageId), + $this->context->link->getProductLink($product, null, null, null, $languageId, $shopId, $combinationId) + ); + } + + private function getAvailability(HotProduct $product): ?ProductAvailability + { + $from = $product->getAvailableFrom(); + $to = $product->getAvailableTo(); + + if (null === $from && null === $to) { + return null; + } + + return new ProductAvailability($from, $to); + } + + private function getAvailableQuantity(\Product $product, ?\Combination $combination, int $shopId): int + { + $productId = (int) $product->id; + $combinationId = $combination ? (int) $combination->id : null; + + $stockQuantity = $this->productRepository->getAvailableStockQuantity($productId, $combinationId, $shopId); + + if ($this->productRepository->isAvailableOutOfStock($productId, $shopId)) { + return max(9999, $stockQuantity); + } + + $minQuantity = $combination ? (int) $combination->minimal_quantity : (int) $product->minimal_quantity; + + if ($stockQuantity < $minQuantity) { + return 0; + } + + return $stockQuantity; + } + + /** + * @return ProductAttribute[] + */ + private function getAttributes(?\Combination $combination, int $languageId): array + { + if (null === $combination) { + return []; + } + + if ([] === $productAttributes = $this->combinationRepository->getAttributesByCombinationId((int) $combination->id, $languageId)) { + return []; + } + + $result = []; + + foreach ($productAttributes as $attribute) { + $group = trim($attribute->getGroup()->public_name ?: $attribute->getGroup()->name ?? ''); + $name = trim($attribute->getAttribute()->name ?? ''); + + if ('' === $group || '' === $name) { + continue; + } + + $result[] = new ProductAttribute( + \Tools::substr($group, 0, 255), + \Tools::substr($name, 0, 255) + ); + } + + return $result; + } + + private function getPolishLanguage(int $shopId): \Language + { + if (!array_key_exists($shopId, $this->languages)) { + $this->languages[$shopId] = $this->languageRepository->findOneBy([ + 'iso_code' => 'pl', + 'id_shop' => $shopId, + ], ['active' => 'DESC']); + } + + if (null === $language = $this->languages[$shopId]) { + throw new \RuntimeException('Polish language not found.'); + } + + return $language; + } +} diff --git a/modules/inpostizi/src/HotProduct/HotProductDataMapperInterface.php b/modules/inpostizi/src/HotProduct/HotProductDataMapperInterface.php new file mode 100644 index 00000000..0a84121e --- /dev/null +++ b/modules/inpostizi/src/HotProduct/HotProductDataMapperInterface.php @@ -0,0 +1,12 @@ +connection = $connection; + $this->clock = $clock; + $this->reflection = new \ReflectionClass(HotProduct::class); + } + + public function add(HotProduct $product): void + { + if (null !== $product->getId()) { + throw new \DomainException('Hot product already exists.'); + } + + $this->updateTimestamps($product, $now = $this->clock->now()); + + $this->connection->insert(self::TABLE_NAME, [ + 'product_id' => $product->getProductId(), + 'combination_id' => $product->getCombinationId(), + 'shop_id' => $product->getShopId(), + 'reference_id' => (string) $product->getReferenceId(), + 'available_from' => ($from = $product->getAvailableFrom()) ? $from->format('Y-m-d H:i:s') : null, + 'available_to' => ($to = $product->getAvailableTo()) ? $to->format('Y-m-d H:i:s') : null, + 'updated_at' => $now->format('Y-m-d H:i:s'), + 'created_at' => $now->format('Y-m-d H:i:s'), + ]); + + $this->setPropertyValue($product, 'id', (int) $this->connection->getLastInsertId()); + } + + public function find(int $id): ?HotProduct + { + $qb = $this->createQueryBuilder()->where('id = ' . $id); + + return $this->getOneOrNullResult($qb); + } + + /** + * @return HotProduct[] + */ + public function findAll(?int $shopId = null): array + { + $qb = $this + ->createQueryBuilder($shopId) + ->orderBy('product_id, combination_id'); + + $rows = $this->connection->fetchAllAssociative((string) $qb); + + return array_map([$this, 'hydrate'], $rows); + } + + /** + * @param string[]|ReferenceId[] $referenceIds + */ + public function findBy(?int $shopId = null, ?int $limit = null, ?int $offset = null, array $referenceIds = []): array + { + $qb = $this + ->createByReferenceIdsQueryBuilder($shopId, $referenceIds) + ->orderBy('product_id, combination_id'); + + $rows = $this->connection->fetchAllAssociative((string) $qb); + + return array_map([$this, 'hydrate'], $rows); + } + + /** + * @param string[]|ReferenceId[] $referenceIds + */ + public function countBy(?int $shopId = null, array $referenceIds = []): int + { + $qb = $this + ->createByReferenceIdsQueryBuilder($shopId, $referenceIds) + ->select('COUNT(*)'); + + return (int) $this->connection->fetchOne((string) $qb); + } + + public function findOneByReferenceId(string $referenceId, ?int $shopId = null): ?HotProduct + { + $qb = $this + ->createQueryBuilder($shopId) + ->where('reference_id = "' . pSQL($referenceId) . '"'); + + return $this->getOneOrNullResult($qb); + } + + public function findOneByProductId(int $productId, ?int $combinationId = null, ?int $shopId = null): ?HotProduct + { + $qb = $this + ->createQueryBuilder($shopId) + ->where('product_id = ' . $productId); + + if (null !== $combinationId) { + $qb->where('combination_id = ' . $combinationId); + } else { + $qb->where('combination_id IS NULL'); + } + + return $this->getOneOrNullResult($qb); + } + + public function update(HotProduct $product): void + { + if (null === $id = $product->getId()) { + throw new \DomainException('Cannot update a product that has not been persisted.'); + } + + $this->updateTimestamps($product, $now = $this->clock->now()); + + $this->connection->update(self::TABLE_NAME, [ + 'available_from' => ($from = $product->getAvailableFrom()) ? $from->format('Y-m-d H:i:s') : null, + 'available_to' => ($to = $product->getAvailableTo()) ? $to->format('Y-m-d H:i:s') : null, + 'updated_at' => $now->format('Y-m-d H:i:s'), + ], [ + 'id' => $id, + ]); + } + + public function remove(HotProduct $product): void + { + if (null === $id = $product->getId()) { + throw new \DomainException('Cannot remove a product that has not been persisted.'); + } + + $this->connection->delete(self::TABLE_NAME, [ + 'id' => $id, + ]); + } + + protected function createQueryBuilder(?int $shopId = null): \DbQuery + { + $qb = (new \DbQuery())->from(self::TABLE_NAME); + + return null === $shopId ? $qb : $qb->where('shop_id IS NULL OR shop_id = ' . $shopId); + } + + /** + * @param string[]|ReferenceId[] $referenceIds + */ + protected function createByReferenceIdsQueryBuilder(?int $shopId, array $referenceIds): \DbQuery + { + $qb = $this->createQueryBuilder($shopId); + + if ([] === $referenceIds) { + return $qb; + } + + $referenceIds = array_map(static function ($referenceId): string { + return pSQL((string) $referenceId); + }, $referenceIds); + + return $qb->where('reference_id IN ("' . implode('","', $referenceIds) . '")'); + } + + protected function getOneOrNullResult(\DbQuery $qb): ?HotProduct + { + if (false === $row = $this->connection->fetchAssociative((string) $qb)) { + return null; + } + + return $this->hydrate($row); + } + + protected function hydrate(array $row): HotProduct + { + $product = new HotProduct( + (int) $row['product_id'], + $row['combination_id'] ? (int) $row['combination_id'] : null, + $row['shop_id'] ? (int) $row['shop_id'] : null, + $row['available_from'] ? new \DateTimeImmutable($row['available_from']) : null, + $row['available_to'] ? new \DateTimeImmutable($row['available_to']) : null, + ReferenceId::fromString($row['reference_id']) + ); + + $this->setPropertyValue($product, 'id', (int) $row['id']); + $this->setPropertyValue($product, 'createdAt', new \DateTimeImmutable($row['created_at'])); + $this->setPropertyValue($product, 'updatedAt', new \DateTimeImmutable($row['updated_at'])); + + return $product; + } + + private function updateTimestamps(HotProduct $product, \DateTimeImmutable $now): void + { + $this->setPropertyValue($product, 'updatedAt', $now); + if (null === $product->getCreatedAt()) { + $this->setPropertyValue($product, 'createdAt', $now); + } + } + + /** + * @param string $name property name to set + * @param mixed $value property value + */ + private function setPropertyValue(HotProduct $product, string $name, $value): void + { + $property = $this->reflection->getProperty($name); + $property->setAccessible(true); + $property->setValue($product, $value); + } +} diff --git a/modules/inpostizi/src/HotProduct/HotProductRepositoryInterface.php b/modules/inpostizi/src/HotProduct/HotProductRepositoryInterface.php new file mode 100644 index 00000000..9c4a3915 --- /dev/null +++ b/modules/inpostizi/src/HotProduct/HotProductRepositoryInterface.php @@ -0,0 +1,37 @@ +productRepository = $productRepository; + $this->translator = $translator ?? new LegacyTranslator('inpostizi'); + } + + /** + * @internal + */ + public static function isAvailableForOrder(\Product $product): bool + { + if (!$product->active) { + return false; + } + + if (!$product->available_for_order) { + return false; + } + + if (2 === (int) $product->customizable) { + return false; + } + + if (\Product::STATE_SAVED !== (int) $product->state) { + return false; + } + + return true; + } + + /** + * @return ProductWithCombination associated with the hot product + */ + public function validate(HotProduct $hotProduct, bool $allowDefaultCombination = false): ProductWithCombination + { + $shopId = $hotProduct->getShopId(); + + $productWithCombination = $this->productRepository->findWithCombination( + $hotProduct->getProductId(), + $hotProduct->getCombinationId(), + null, + $shopId, + $allowDefaultCombination + ); + + if (null === $productWithCombination) { + throw new InvalidProductDataException($this->translator->l('Product or combination does not exist.', self::TRANSLATION_SOURCE)); + } + + if (!self::isAvailableForOrder($productWithCombination->getProduct())) { + throw new InvalidProductDataException($this->translator->l('Product is inactive or not available for order.', self::TRANSLATION_SOURCE)); + } + + if (!self::hasEan($productWithCombination)) { + throw new InvalidProductDataException($this->translator->l('EAN code is required.', self::TRANSLATION_SOURCE)); + } + + return $productWithCombination; + } + + private static function hasEan(ProductWithCombination $productWithCombination): bool + { + if ('' !== trim((string) $productWithCombination->getProduct()->ean13)) { + return true; + } + + if (null === $combination = $productWithCombination->getCombination()) { + return false; + } + + return '' !== trim((string) $combination->ean13); + } +} diff --git a/modules/inpostizi/src/HotProduct/Message/CreateHotProductCommand.php b/modules/inpostizi/src/HotProduct/Message/CreateHotProductCommand.php new file mode 100644 index 00000000..005b59f2 --- /dev/null +++ b/modules/inpostizi/src/HotProduct/Message/CreateHotProductCommand.php @@ -0,0 +1,120 @@ +shopId = $shopId; + } + + public function getShopId(): ?int + { + return $this->shopId; + } + + public function getProductId(): ?int + { + return $this->productId; + } + + public function setProductId(?int $productId): self + { + $this->productId = $productId; + + return $this; + } + + public function getCombinationId(): ?int + { + return $this->combinationId; + } + + public function setCombinationId(?int $combinationId): self + { + $this->combinationId = $combinationId; + + return $this; + } + + public function getAvailableFrom(): ?\DateTimeImmutable + { + return $this->availableFrom; + } + + public function setAvailableFrom(?\DateTimeImmutable $availableFrom): self + { + $this->availableFrom = $availableFrom; + + return $this; + } + + public function getAvailableTo(): ?\DateTimeImmutable + { + return $this->availableTo; + } + + public function setAvailableTo(?\DateTimeImmutable $availableTo): self + { + $this->availableTo = $availableTo; + + return $this; + } + + /** + * @internal + * + * @Assert\IsTrue(message="Please choose a valid date interval.") + */ + public function hasValidAvailabilityDates(): bool + { + if (null === $this->availableFrom || null === $this->availableTo) { + return true; + } + + return $this->availableFrom < $this->availableTo; + } +} diff --git a/modules/inpostizi/src/HotProduct/Message/DeleteHotProductCommand.php b/modules/inpostizi/src/HotProduct/Message/DeleteHotProductCommand.php new file mode 100644 index 00000000..9a7c11ed --- /dev/null +++ b/modules/inpostizi/src/HotProduct/Message/DeleteHotProductCommand.php @@ -0,0 +1,28 @@ +id = $id; + } + + public function getId(): int + { + return $this->id; + } +} diff --git a/modules/inpostizi/src/HotProduct/Message/DeleteRemoteProductCommand.php b/modules/inpostizi/src/HotProduct/Message/DeleteRemoteProductCommand.php new file mode 100644 index 00000000..b5feeda6 --- /dev/null +++ b/modules/inpostizi/src/HotProduct/Message/DeleteRemoteProductCommand.php @@ -0,0 +1,39 @@ +id = $id; + $this->shopId = $shopId; + } + + public function getId(): string + { + return $this->id; + } + + public function getShopId(): ?int + { + return $this->shopId; + } +} diff --git a/modules/inpostizi/src/HotProduct/Message/ImportHotProductCommand.php b/modules/inpostizi/src/HotProduct/Message/ImportHotProductCommand.php new file mode 100644 index 00000000..11fb1e72 --- /dev/null +++ b/modules/inpostizi/src/HotProduct/Message/ImportHotProductCommand.php @@ -0,0 +1,39 @@ +id = $id; + $this->shopId = $shopId; + } + + public function getId(): string + { + return $this->id; + } + + public function getShopId(): ?int + { + return $this->shopId; + } +} diff --git a/modules/inpostizi/src/HotProduct/Message/UpdateHotProductCommand.php b/modules/inpostizi/src/HotProduct/Message/UpdateHotProductCommand.php new file mode 100644 index 00000000..25a9d1e9 --- /dev/null +++ b/modules/inpostizi/src/HotProduct/Message/UpdateHotProductCommand.php @@ -0,0 +1,124 @@ +id = $id; + } + + public static function for(HotProduct $product): self + { + $command = new self($product->getId()); + $command->availableFrom = $product->getAvailableFrom(); + $command->availableTo = $product->getAvailableTo(); + $command->updateAvailability = true; + + return $command; + } + + public function getId(): int + { + return $this->id; + } + + public function updateAvailability(): bool + { + return $this->updateAvailability; + } + + public function setUpdateAvailability(bool $updateAvailability): self + { + $this->updateAvailability = $updateAvailability; + + return $this; + } + + public function getAvailableFrom(): ?\DateTimeImmutable + { + return $this->availableFrom; + } + + public function setAvailableFrom(?\DateTimeImmutable $availableFrom): self + { + $this->availableFrom = $availableFrom; + + return $this; + } + + public function getAvailableTo(): ?\DateTimeImmutable + { + return $this->availableTo; + } + + public function setAvailableTo(?\DateTimeImmutable $availableTo): self + { + $this->availableTo = $availableTo; + + return $this; + } + + /** + * @internal + * + * @Assert\IsTrue(message="Please choose a valid date interval.") + */ + public function hasValidAvailabilityDates(): bool + { + if (null === $this->availableFrom || null === $this->availableTo) { + return true; + } + + return $this->availableFrom < $this->availableTo; + } + + public function createIfNotFound(): bool + { + return $this->createIfNotFound; + } + + public function setCreateIfNotFound(bool $createIfNotFound): self + { + $this->createIfNotFound = $createIfNotFound; + + return $this; + } +} diff --git a/modules/inpostizi/src/HotProduct/MessageHandler/CreateHotProductHandler.php b/modules/inpostizi/src/HotProduct/MessageHandler/CreateHotProductHandler.php new file mode 100644 index 00000000..34afb923 --- /dev/null +++ b/modules/inpostizi/src/HotProduct/MessageHandler/CreateHotProductHandler.php @@ -0,0 +1,80 @@ +repository = $repository; + $this->validator = $validator; + $this->dataMapper = $dataMapper; + $this->client = $client; + } + + public function __invoke(CreateHotProductCommand $command): HotProduct + { + $product = new HotProduct( + $command->getProductId(), + $command->getCombinationId(), + $command->getShopId(), + $command->getAvailableFrom(), + $command->getAvailableTo() + ); + + $this->validator->validate($product); + + $referenceId = (string) $product->getReferenceId(); + + if (null !== $this->repository->findOneByReferenceId($referenceId, $product->getShopId())) { + throw new HotProductExistsException('Hot product already exists'); + } + + $payload = $this->dataMapper->map($product)->asIdentifiable($referenceId); + + try { + $this->client->createProducts(new CreateProductsRequest([$payload])); + } catch (ProductExistsException $e) { + // ignore and save data to the database + } + + $this->repository->add($product); + + return $product; + } +} diff --git a/modules/inpostizi/src/HotProduct/MessageHandler/CreateHotProductHandlerInterface.php b/modules/inpostizi/src/HotProduct/MessageHandler/CreateHotProductHandlerInterface.php new file mode 100644 index 00000000..db2f4f5f --- /dev/null +++ b/modules/inpostizi/src/HotProduct/MessageHandler/CreateHotProductHandlerInterface.php @@ -0,0 +1,13 @@ +repository = $repository; + $this->client = $client; + } + + public function __invoke(DeleteHotProductCommand $command): void + { + if (null === $product = $this->repository->find($command->getId())) { + return; + } + + try { + $this->client->deleteProduct((string) $product->getReferenceId()); + } catch (ProductNotFoundException $e) { + // ignore and remove data from the database + } + + $this->repository->remove($product); + } +} diff --git a/modules/inpostizi/src/HotProduct/MessageHandler/DeleteHotProductHandlerInterface.php b/modules/inpostizi/src/HotProduct/MessageHandler/DeleteHotProductHandlerInterface.php new file mode 100644 index 00000000..0e597667 --- /dev/null +++ b/modules/inpostizi/src/HotProduct/MessageHandler/DeleteHotProductHandlerInterface.php @@ -0,0 +1,12 @@ +repository = $repository; + $this->client = $client; + } + + public function __invoke(DeleteRemoteProductCommand $command): void + { + $product = $this->repository->findOneByReferenceId($command->getId(), $command->getShopId()); + + try { + $this->client->deleteProduct($command->getId()); + } catch (ProductNotFoundException $e) { + // ignore silently + } + + if (null !== $product) { + $this->repository->remove($product); + } + } +} diff --git a/modules/inpostizi/src/HotProduct/MessageHandler/DeleteRemoteProductHandlerInterface.php b/modules/inpostizi/src/HotProduct/MessageHandler/DeleteRemoteProductHandlerInterface.php new file mode 100644 index 00000000..8c0c695a --- /dev/null +++ b/modules/inpostizi/src/HotProduct/MessageHandler/DeleteRemoteProductHandlerInterface.php @@ -0,0 +1,12 @@ +repository = $repository; + $this->client = $client; + $this->dataMapper = $dataMapper; + $this->validator = new HotProductValidator($productRepository); + } + + public function __invoke(ImportHotProductCommand $command): HotProduct + { + $referenceId = ReferenceId::fromString($command->getId()); + + if (null === $referenceId || $referenceId->hasCustomization()) { + throw new InvalidProductDataException('Invalid product reference ID.'); + } + + $productWithCombination = $this->validator->validate(new HotProduct( + $productId = $referenceId->getProductId(), + $referenceId->getCombinationId(), + $shopId = $command->getShopId() + ), true); + + $combination = $productWithCombination->getCombination(); + $combinationId = $combination ? (int) $combination->id : null; + + if (null !== $this->repository->findOneByProductId($productId, $combinationId, $shopId)) { + throw new HotProductExistsException('Hot product already exists.'); + } + + $apiProduct = $this->fetchProduct((string) $referenceId); + $availability = $apiProduct->getAvailability() ?? new ProductAvailability(); + + $this->repository->add($hotProduct = new HotProduct( + $productId, + $combinationId, + $shopId, + $availability->getStartDate(), + $availability->getEndDate() + )); + + // synchronize product data + $payload = $this->dataMapper->map($hotProduct); + $this->client->updateProduct((string) $referenceId, $payload); + + return $hotProduct; + } + + private function fetchProduct(string $id): Product + { + $products = iterator_to_array($this->client->getProducts([$id])); + + if ([] === $products) { + throw new HotProductNotFoundException('API product not found.'); + } + + return $products[0]; + } +} diff --git a/modules/inpostizi/src/HotProduct/MessageHandler/ImportHotProductHandlerInterface.php b/modules/inpostizi/src/HotProduct/MessageHandler/ImportHotProductHandlerInterface.php new file mode 100644 index 00000000..c3c88f06 --- /dev/null +++ b/modules/inpostizi/src/HotProduct/MessageHandler/ImportHotProductHandlerInterface.php @@ -0,0 +1,13 @@ +repository = $repository; + $this->dataMapper = $dataMapper; + $this->client = $client; + } + + public function __invoke(UpdateHotProductCommand $command): Status + { + if (null === $product = $this->repository->find($command->getId())) { + throw new HotProductNotFoundException('Hot product not found.'); + } + + if ($command->updateAvailability()) { + $product->setAvailability($command->getAvailableFrom(), $command->getAvailableTo()); + } + + $referenceId = (string) $product->getReferenceId(); + $payload = $this->dataMapper->map($product); + + try { + $status = $this->client->updateProduct($referenceId, $payload)->getStatus(); + } catch (ProductNotFoundException $e) { + if (!$command->createIfNotFound()) { + throw $e; + } + + $payload = $payload->asIdentifiable($referenceId); + $this->client->createProducts(new CreateProductsRequest([$payload])); + + $status = Status::Inactive(); + } + + if ($command->updateAvailability()) { + $this->repository->update($product); + } + + return $status; + } +} diff --git a/modules/inpostizi/src/HotProduct/MessageHandler/UpdateHotProductHandlerInterface.php b/modules/inpostizi/src/HotProduct/MessageHandler/UpdateHotProductHandlerInterface.php new file mode 100644 index 00000000..8f49636c --- /dev/null +++ b/modules/inpostizi/src/HotProduct/MessageHandler/UpdateHotProductHandlerInterface.php @@ -0,0 +1,13 @@ + + */ +final class HotProductListView implements \IteratorAggregate +{ + /** + * @var HotProductView[] + */ + private $products; + + /** + * @var bool + */ + private $statusAvailable; + + /** + * @param HotProductView[] $products + */ + public function __construct(array $products, bool $statusAvailable) + { + $this->products = $products; + $this->statusAvailable = $statusAvailable; + } + + /** + * @return \Iterator + */ + public function getIterator(): \Iterator + { + return new \ArrayIterator($this->products); + } + + /** + * @return HotProductView[] + */ + public function getProducts(): array + { + return $this->products; + } + + public function isStatusAvailable(): bool + { + return $this->statusAvailable; + } +} diff --git a/modules/inpostizi/src/HotProduct/View/HotProductView.php b/modules/inpostizi/src/HotProduct/View/HotProductView.php new file mode 100644 index 00000000..44666924 --- /dev/null +++ b/modules/inpostizi/src/HotProduct/View/HotProductView.php @@ -0,0 +1,162 @@ +referenceId = $referenceId; + $this->name = $name; + } + + public static function local(HotProduct $product, string $name, ?string $shop, ?Product $apiProduct = null): self + { + $view = new self((string) $product->getReferenceId(), $name); + + $view->id = $product->getId(); + $view->name = $name; + $view->availableFrom = $product->getAvailableFrom(); + $view->availableTo = $product->getAvailableTo(); + $view->shop = $shop; + + if (null !== $apiProduct) { + $view->active = Status::Active() === $apiProduct->getStatus(); + } + + return $view; + } + + public static function notFound(HotProduct $product, string $name, ?string $shop): self + { + $view = self::local($product, $name, $shop); + $view->exists = false; + + return $view; + } + + public static function remote(Product $product, bool $importable): self + { + $view = new self($product->getId(), self::getRemoteProductName($product)); + + $view->importable = $importable; + $view->active = Status::Active() === $product->getStatus(); + + if (null !== $availability = $product->getAvailability()) { + $view->availableFrom = $availability->getStartDate(); + $view->availableTo = $availability->getEndDate(); + } + + return $view; + } + + public function getReferenceId(): string + { + return $this->referenceId; + } + + public function getName(): string + { + return $this->name; + } + + public function getId(): ?int + { + return $this->id; + } + + public function getAvailableFrom(): ?\DateTimeInterface + { + return $this->availableFrom; + } + + public function getAvailableTo(): ?\DateTimeInterface + { + return $this->availableTo; + } + + public function getShop(): ?string + { + return $this->shop; + } + + public function exists(): bool + { + return $this->exists; + } + + public function isActive(): ?bool + { + return $this->active; + } + + public function isImportable(): bool + { + return $this->importable; + } + + private static function getRemoteProductName(Product $product): string + { + if ([] === $attributes = $product->getAttributes()) { + return $product->getName(); + } + + $attributes = array_map(static function (ProductAttribute $attribute): string { + return sprintf('%s - %s', $attribute->getName(), $attribute->getValue()); + }, $attributes); + + return sprintf('%s : %s', $product->getName(), implode(', ', $attributes)); + } +} diff --git a/modules/inpostizi/src/HotProduct/View/HotProductViewDataFactory.php b/modules/inpostizi/src/HotProduct/View/HotProductViewDataFactory.php new file mode 100644 index 00000000..b273ea3d --- /dev/null +++ b/modules/inpostizi/src/HotProduct/View/HotProductViewDataFactory.php @@ -0,0 +1,158 @@ + + */ + private $shopRepository; + + /** + * @var ProductsApiClientInterface + */ + private $client; + + /** + * @var HotProductValidator + */ + private $validator; + + /** + * @var array shops by ID + */ + private $shopMap = []; + + /** + * @param ProductRepository $productRepository + * @param ObjectRepositoryInterface<\Shop> $shopRepository + */ + public function __construct(\Context $context, ObjectRepositoryInterface $productRepository, ObjectRepositoryInterface $shopRepository, ProductsApiClientInterface $client) + { + $this->context = $context; + $this->productRepository = $productRepository; + $this->shopRepository = $shopRepository; + $this->client = $client; + $this->validator = new HotProductValidator($productRepository); + } + + /** + * @param HotProduct[] $products + */ + public function createForProducts(array $products): HotProductListView + { + try { + $apiProducts = iterator_to_array($this->client->getProducts()); + } catch (\Exception $e) { + $viewModels = array_map([$this, 'createViewModelForLocalProduct'], $products); + + return new HotProductListView($viewModels, false); + } + + $apiProductMap = []; + foreach ($apiProducts as $apiProduct) { + $apiProductMap[$apiProduct->getId()] = $apiProduct; + } + + $viewModels = array_map(function (HotProduct $product) use (&$apiProductMap) { + $referenceId = (string) $product->getReferenceId(); + + if (!array_key_exists($referenceId, $apiProductMap)) { + return $this->createViewModelForLocalProduct($product, null, false); + } + + $apiProduct = $apiProductMap[$referenceId]; + unset($apiProductMap[$referenceId]); + + return $this->createViewModelForLocalProduct($product, $apiProduct, true); + }, $products); + + return new HotProductListView(array_merge($viewModels, array_map( + [$this, 'createViewModelForRemoteProduct'], + array_values($apiProductMap) + )), true); + } + + private function createViewModelForLocalProduct(HotProduct $product, ?Product $apiProduct = null, ?bool $exists = null): HotProductView + { + $name = $this->productRepository->getProductNameByProductId( + $product->getProductId(), + (int) $this->context->language->id, + $product->getCombinationId() + ); + $shop = $this->getShopName($product); + + return false === $exists + ? HotProductView::notFound($product, $name, $shop) + : HotProductView::local($product, $name, $shop, $apiProduct); + } + + private function createViewModelForRemoteProduct(Product $product): HotProductView + { + $importable = $this->isImportable($product); + + return HotProductView::remote($product, $importable); + } + + private function getShopName(HotProduct $product): ?string + { + if (null === $shopId = $product->getShopId()) { + return null; + } + + if (!array_key_exists($shopId, $this->shopMap)) { + $this->shopMap[$shopId] = $this->shopRepository->find($shopId); + } + + if (null === $shop = $this->shopMap[$shopId]) { + throw new \RuntimeException('Shop not found.'); + } + + return $shop->name ?? 'Shop #' . $shopId; + } + + private function isImportable(Product $apiProduct): bool + { + if (null === $referenceId = ReferenceId::fromString($apiProduct->getId())) { + return false; + } + + if ($referenceId->hasCustomization()) { + return false; + } + + try { + $this->validator->validate(new HotProduct( + $referenceId->getProductId(), + $referenceId->getCombinationId(), + (int) $this->context->shop->id + ), true); + + return true; + } catch (InvalidProductDataException $e) { + return false; + } + } +} diff --git a/modules/inpostizi/src/Http/Client/Adapter/Guzzle5Adapter.php b/modules/inpostizi/src/Http/Client/Adapter/Guzzle5Adapter.php new file mode 100644 index 00000000..c7180399 --- /dev/null +++ b/modules/inpostizi/src/Http/Client/Adapter/Guzzle5Adapter.php @@ -0,0 +1,79 @@ +client = $client ?: new GuzzleClient(); + } + + /** + * {@inheritDoc} + */ + public function sendRequest(RequestInterface $request): ResponseInterface + { + $guzzleRequest = $this->createRequest($request); + + try { + $response = $this->client->send($guzzleRequest); + } catch (GuzzleExceptions\TransferException $exception) { + if (!$exception instanceof GuzzleExceptions\RequestException || !$exception->hasResponse()) { + throw $this->handleException($exception, $request); + } + + $response = $exception->getResponse(); + } + + return $this->createResponse($response); + } + + private function createRequest(RequestInterface $request): GuzzleRequest + { + $body = (string) $request->getBody(); + + return $this->client->createRequest($request->getMethod(), (string) $request->getUri(), [ + 'exceptions' => false, + 'allow_redirects' => false, + 'version' => $request->getProtocolVersion(), + 'headers' => $request->getHeaders(), + 'body' => '' === $body ? null : $body, + ]); + } + + private function createResponse(GuzzleResponse $response): ResponseInterface + { + $body = $response->getBody(); + + return new Response( + $response->getStatusCode(), + $response->getHeaders(), + isset($body) ? $body->detach() : null, + $response->getProtocolVersion(), + $response->getReasonPhrase() + ); + } + + private function handleException(GuzzleExceptions\TransferException $exception, RequestInterface $request): ClientExceptionInterface + { + return $exception instanceof GuzzleExceptions\ConnectException + ? NetworkException::fromGuzzleException($exception, $request) + : RequestException::fromGuzzleException($exception, $request); + } +} diff --git a/modules/inpostizi/src/Http/Client/Adapter/NetworkException.php b/modules/inpostizi/src/Http/Client/Adapter/NetworkException.php new file mode 100644 index 00000000..ecea58da --- /dev/null +++ b/modules/inpostizi/src/Http/Client/Adapter/NetworkException.php @@ -0,0 +1,31 @@ +request = $request; + } + + public function getRequest(): RequestInterface + { + return $this->request; + } + + public static function fromGuzzleException(ConnectException $exception, RequestInterface $request): self + { + return new self($request, $exception->getMessage(), 0, $exception); + } +} diff --git a/modules/inpostizi/src/Http/Client/Adapter/RequestException.php b/modules/inpostizi/src/Http/Client/Adapter/RequestException.php new file mode 100644 index 00000000..55641c12 --- /dev/null +++ b/modules/inpostizi/src/Http/Client/Adapter/RequestException.php @@ -0,0 +1,31 @@ +request = $request; + } + + public function getRequest(): RequestInterface + { + return $this->request; + } + + public static function fromGuzzleException(TransferException $exception, RequestInterface $request): self + { + return new self($request, $exception->getMessage(), 0, $exception); + } +} diff --git a/modules/inpostizi/src/Http/Client/AuthorizingClient.php b/modules/inpostizi/src/Http/Client/AuthorizingClient.php new file mode 100644 index 00000000..2d6cf8cc --- /dev/null +++ b/modules/inpostizi/src/Http/Client/AuthorizingClient.php @@ -0,0 +1,59 @@ +client = $client; + $this->authorizationProvider = $authorizationProvider; + $this->scopes = $scopes; + } + + public function sendRequest(RequestInterface $request): ResponseInterface + { + $request = $this->authorize($request); + $response = $this->client->sendRequest($request); + + if (401 !== $response->getStatusCode()) { + return $response; + } + + $request = $this->authorize($request, true); + + return $this->client->sendRequest($request); + } + + private function authorize(RequestInterface $request, bool $renewAccessToken = false): RequestInterface + { + return $this->authorizationProvider + ->getAccessToken($renewAccessToken, $this->scopes) + ->authorize($request); + } +} diff --git a/modules/inpostizi/src/Http/Client/Factory/ClientFactoryInterface.php b/modules/inpostizi/src/Http/Client/Factory/ClientFactoryInterface.php new file mode 100644 index 00000000..44ea9905 --- /dev/null +++ b/modules/inpostizi/src/Http/Client/Factory/ClientFactoryInterface.php @@ -0,0 +1,12 @@ +timeout = $timeout; + } + + public function create(): ClientInterface + { + if (!class_exists(Client::class)) { + throw new \RuntimeException(sprintf('Class %s does not exist', Client::class)); + } + + if (is_subclass_of(Client::class, ClientInterface::class)) { + return new Client([ + 'connect_timeout' => 3., + 'timeout' => $this->timeout, + ]); + } + + $client = new Client([ + 'defaults' => [ + 'connect_timeout' => 3., + 'timeout' => $this->timeout, + ], + ]); + + return new Guzzle5Adapter($client); + } +} diff --git a/modules/inpostizi/src/Http/Client/LoggingClient.php b/modules/inpostizi/src/Http/Client/LoggingClient.php new file mode 100644 index 00000000..808bcd20 --- /dev/null +++ b/modules/inpostizi/src/Http/Client/LoggingClient.php @@ -0,0 +1,119 @@ +client = $client; + $this->logger = $logger; + $this->options = $options; + } + + public function sendRequest(RequestInterface $request): ResponseInterface + { + $this->logRequest($request); + + try { + $response = $this->client->sendRequest($request); + $this->logResponse($request, $response); + + return $response; + } catch (\Throwable $throwable) { + $this->logError($request, $throwable); + + throw $throwable; + } + } + + private function logRequest(RequestInterface $request): void + { + $this->logger->info('Request: "{method} {uri}"', [ + 'method' => $request->getMethod(), + 'uri' => (string) $request->getUri(), + ]); + + if ('' !== $body = (string) $request->getBody()) { + $this->logger->debug('Request body: "{body}"', ['body' => $body]); + } + } + + private function logResponse(RequestInterface $request, ResponseInterface $response): void + { + $context = [ + 'status_code' => $statusCode = $response->getStatusCode(), + 'uri' => (string) $request->getUri(), + ]; + + if (400 <= $statusCode) { + if ('' !== $body = (string) $response->getBody()) { + $context['body'] = $body; + } + + $this->logger->error('Response: "{status_code} {uri}"', $context); + } elseif (300 <= $statusCode) { + $context['location'] = $response->getHeaderLine('location'); + $this->logger->info('Response: "{status_code} {uri}"', $context); + } else { + $this->logger->info('Response: "{status_code} {uri}"', $context); + $this->logResponseBody($response); + } + } + + private function logError(RequestInterface $request, \Throwable $throwable): void + { + if ($throwable instanceof NetworkExceptionInterface) { + $this->logger->error('Network error for {uri}: "{message}"', [ + 'uri' => (string) $throwable->getRequest()->getUri(), + 'message' => $throwable->getMessage(), + ]); + } else { + $this->logger->critical('Unexpected error for {uri}: {error}', [ + 'uri' => (string) $request->getUri(), + 'error' => $throwable, + ]); + } + } + + private function logResponseBody(ResponseInterface $response): void + { + if ('' === $body = (string) $response->getBody()) { + return; + } + + if (!isset($this->options['max_response_body_size']) || strlen($body) <= $this->options['max_response_body_size']) { + $this->logger->debug('Response body: "{body}"', ['body' => $body]); + + return; + } + + $this->logger->debug('Response body: "{body}"', [ + 'body' => \Tools::substr($body, 0, $this->options['max_response_body_size']) . '...', + 'body_size' => $response->getBody()->getSize(), + ]); + } +} diff --git a/modules/inpostizi/src/Http/Client/ModuleVersionInfoProvidingClient.php b/modules/inpostizi/src/Http/Client/ModuleVersionInfoProvidingClient.php new file mode 100644 index 00000000..98089488 --- /dev/null +++ b/modules/inpostizi/src/Http/Client/ModuleVersionInfoProvidingClient.php @@ -0,0 +1,37 @@ +client = $client; + $this->module = $module; + } + + public function sendRequest(RequestInterface $request): ResponseInterface + { + $request = $request->withHeader(self::HEADER_NAME, $this->module->version); + + return $this->client->sendRequest($request); + } +} diff --git a/modules/inpostizi/src/Http/Exception/ClientException.php b/modules/inpostizi/src/Http/Exception/ClientException.php new file mode 100644 index 00000000..798513bb --- /dev/null +++ b/modules/inpostizi/src/Http/Exception/ClientException.php @@ -0,0 +1,10 @@ +request = $request; + $this->response = $response; + + $statusCode = $response->getStatusCode(); + $message = sprintf('HTTP %d returned for "%s".', $statusCode, $request->getUri()); + + parent::__construct($message, $statusCode); + } + + public function getRequest(): RequestInterface + { + return $this->request; + } + + public function getResponse(): ResponseInterface + { + return $this->response; + } +} diff --git a/modules/inpostizi/src/Http/Exception/RedirectionException.php b/modules/inpostizi/src/Http/Exception/RedirectionException.php new file mode 100644 index 00000000..44b0e603 --- /dev/null +++ b/modules/inpostizi/src/Http/Exception/RedirectionException.php @@ -0,0 +1,10 @@ + 'no', + 'Content-Type' => 'text/event-stream', + 'Cache-Control' => 'no-store', + 'Connection' => 'keep-alive', + 'Access-Control-Expose-Headers' => 'X-Events', + ]); + + parent::__construct($callback, $status, $headers); + } + + public function sendContent(): self + { + if ($this->streamed) { + return $this; + } + + @ini_set('zlib.output_compression', '0'); + @ini_set('implicit_flush', '1'); + + if (function_exists('apache_setenv')) { + @apache_setenv('no-gzip', '1'); + } + + gc_enable(); + ob_implicit_flush(80000 <= PHP_VERSION_ID ? true : 1); + session_write_close(); + + parent::sendContent(); + + return $this; + } +} diff --git a/modules/inpostizi/src/Http/Response/ServerSentEvent.php b/modules/inpostizi/src/Http/Response/ServerSentEvent.php new file mode 100644 index 00000000..cfa3ea2f --- /dev/null +++ b/modules/inpostizi/src/Http/Response/ServerSentEvent.php @@ -0,0 +1,99 @@ += $retry) { + throw new \DomainException('Delay should be greater than 0.'); + } + + $this->id = $id; + $this->event = $event; + $this->data = $data; + $this->retry = $retry; + $this->comment = $comment; + } + + public static function builder(): ServerSentEventBuilder + { + return new ServerSentEventBuilder(); + } + + public function getMessage(): string + { + if (isset($this->message)) { + return $this->message; + } + + $lines = []; + + if (null !== $this->comment) { + $lines[] = ": $this->comment"; + } + + if (null !== $this->id) { + $lines[] = "id: $this->id"; + } + + if (null !== $this->event) { + $lines[] = "event: $this->event"; + } + + if (null !== $this->data) { + $data = (string) $this->data; + $lines[] = "data: $data"; + } + + if (null !== $this->retry) { + $lines[] = "retry: $this->retry"; + } + + return $this->message = implode(PHP_EOL, $lines) . str_repeat(PHP_EOL, 2); + } + + public function __toString(): string + { + return $this->getMessage(); + } +} diff --git a/modules/inpostizi/src/Http/Response/ServerSentEventBuilder.php b/modules/inpostizi/src/Http/Response/ServerSentEventBuilder.php new file mode 100644 index 00000000..eb43040c --- /dev/null +++ b/modules/inpostizi/src/Http/Response/ServerSentEventBuilder.php @@ -0,0 +1,87 @@ +id = $id; + + return $this; + } + + public function setEventName(?string $name): self + { + $this->event = $name; + + return $this; + } + + /** + * @param T|null $data + */ + public function setData($data): self + { + $this->data = $data; + + return $this; + } + + public function setRetry(?int $delayMs): self + { + $this->retry = $delayMs; + + return $this; + } + + public function setComment(?string $comment): self + { + $this->comment = $comment; + + return $this; + } + + /** + * @return ServerSentEvent + */ + public function build(): ServerSentEvent + { + return new ServerSentEvent($this->id, $this->event, $this->data, $this->retry, $this->comment); + } +} diff --git a/modules/inpostizi/src/Http/Util/UriResolver.php b/modules/inpostizi/src/Http/Util/UriResolver.php new file mode 100644 index 00000000..00e8b40d --- /dev/null +++ b/modules/inpostizi/src/Http/Util/UriResolver.php @@ -0,0 +1,43 @@ +container = $container; + } + + public function apply(Request $request, ParamConverter $configuration): bool + { + $value = $this->container->get($configuration->getClass()); + $request->attributes->set($configuration->getName(), $value); + + return true; + } + + public function supports(ParamConverter $configuration): bool + { + if (null === $class = $configuration->getClass()) { + return false; + } + + return $this->container->has($class); + } +} diff --git a/modules/inpostizi/src/Installer/Database/AbstractMigration.php b/modules/inpostizi/src/Installer/Database/AbstractMigration.php new file mode 100644 index 00000000..33c83164 --- /dev/null +++ b/modules/inpostizi/src/Installer/Database/AbstractMigration.php @@ -0,0 +1,165 @@ +connection = $connection ?? new Connection(\Db::getInstance()); + } + + public function down(): void + { + } + + protected function tableExists(string $table): bool + { + return (bool) $this->connection->fetchOne('SELECT EXISTS ( + SELECT * + FROM information_schema.TABLES + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = "' . _DB_PREFIX_ . pSQL($table) . '" + )'); + } + + protected function dropTable(string $table): void + { + $this->connection->executeStatement('DROP TABLE IF EXISTS `' . _DB_PREFIX_ . pSQL($table) . '`'); + } + + protected function dropColumn(string $table, string $column): void + { + if (!$this->columnExists($table, $column)) { + return; + } + + $this->connection->executeStatement(' + ALTER TABLE `' . _DB_PREFIX_ . pSQL($table) . '` + DROP COLUMN `' . pSQL($column) . '` + '); + } + + protected function addColumn(string $table, string $column, string $definition): void + { + if ($this->columnExists($table, $column)) { + return; + } + + $this->connection->executeStatement(' + ALTER TABLE `' . _DB_PREFIX_ . pSQL($table) . '` + ADD `' . pSQL($column) . '` ' . pSQL($definition) + ); + } + + public function changeColumn(string $table, string $column, string $definition, ?string $newName = null): void + { + $column = pSQL($column); + $newName = null !== $newName ? pSQL($newName) : $column; + + $this->connection->executeStatement(' + ALTER TABLE `' . _DB_PREFIX_ . pSQL($table) . '` + CHANGE `' . $column . '` `' . $newName . '` ' . pSQL($definition) + ); + } + + protected function columnExists(string $table, string $column): bool + { + return (bool) $this->connection->fetchOne('SELECT EXISTS ( + SELECT * + FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = "' . _DB_PREFIX_ . pSQL($table) . '" + AND COLUMN_NAME = "' . pSQL($column) . '" + )'); + } + + protected function addForeignKey(string $table, string $foreignTable, array $localColumnNames, array $foreignColumnNames, string $name, array $options = []): void + { + if ('MyISAM' === _MYSQL_ENGINE_) { + return; + } + + if ($this->foreignKeyExists($table, $name)) { + return; + } + + $sql = ' + ALTER TABLE `' . _DB_PREFIX_ . pSQL($table) . '` + ADD CONSTRAINT `' . pSQL($name) . '` + FOREIGN KEY (`' . implode('`,`', array_map('pSQL', $localColumnNames)) . '`) + REFERENCES `' . _DB_PREFIX_ . pSQL($foreignTable) . '` (`' . implode('`,`', array_map('pSQL', $foreignColumnNames)) . '`) + ' . $this->getForeignKeyOptionsSql($options); + + try { + $this->connection->executeStatement($sql); + } catch (\PrestaShopDatabaseException $e) { + if (1215 === $e->getCode()) { + // ignore silently: possible column types mismatch (e.g. due to migration from earlier PS versions) or the referenced table uses MyISAM engine + return; + } + + throw $e; + } + } + + protected function foreignKeyExists(string $table, string $name): bool + { + return (bool) $this->connection->fetchOne('SELECT EXISTS ( + SELECT * + FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS + WHERE TABLE_SCHEMA = DATABASE() + AND CONSTRAINT_TYPE = "FOREIGN KEY" + AND CONSTRAINT_NAME = "' . pSQL($name) . '" + AND TABLE_NAME = "' . _DB_PREFIX_ . pSQL($table) . '" + )'); + } + + protected function addUniqueIndex(string $table, array $columns, string $name): void + { + if ($this->indexExists($table, $name)) { + return; + } + + $this->connection->executeStatement(' + ALTER TABLE `' . _DB_PREFIX_ . pSQL($table) . '` + ADD UNIQUE `' . pSQL($name) . '` (`' . implode('`,`', array_map('pSQL', $columns)) . '`) + '); + } + + protected function indexExists(string $table, string $name): bool + { + $result = $this->connection->fetchAllAssociative(' + SHOW INDEX + FROM `' . _DB_PREFIX_ . pSQL($table) . '` + WHERE Key_name = "' . pSQL($name) . '" + '); + + return [] !== $result; + } + + private function getForeignKeyOptionsSql(array $options): string + { + $sql = ''; + + if (isset($options['onUpdate'])) { + $sql .= ' ON UPDATE ' . $options['onUpdate']; + } + + if (isset($options['onDelete'])) { + $sql .= ' ON DELETE ' . $options['onDelete']; + } + + return $sql; + } +} diff --git a/modules/inpostizi/src/Installer/Database/Version_1_0_0.php b/modules/inpostizi/src/Installer/Database/Version_1_0_0.php new file mode 100644 index 00000000..13e29c9f --- /dev/null +++ b/modules/inpostizi/src/Installer/Database/Version_1_0_0.php @@ -0,0 +1,39 @@ +connection->executeStatement(' + CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . InPostIziBasketSession::TABLE_NAME . '` ( + id MEDIUMINT(9) NOT NULL AUTO_INCREMENT, + session_id TEXT, + confirmation_response TEXT, + cart_id VARCHAR(255), + order_id INTEGER, + order_details TEXT, + redirect_url VARCHAR(255), + basket_cache TEXT, + basket_cached TEXT, + coupons TEXT, + event BIT(1), + redirected SMALLINT(1) DEFAULT 0, + PRIMARY KEY (id) + ) DEFAULT CHARSET=utf8; + '); + } +} diff --git a/modules/inpostizi/src/Installer/Database/Version_1_11_0.php b/modules/inpostizi/src/Installer/Database/Version_1_11_0.php new file mode 100644 index 00000000..03b724be --- /dev/null +++ b/modules/inpostizi/src/Installer/Database/Version_1_11_0.php @@ -0,0 +1,40 @@ +createCartRuleOptionsTable(); + } + + private function createCartRuleOptionsTable(): void + { + $this->connection->executeStatement(' + CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . CartRuleOptionsRepository::TABLE_NAME . '` ( + `id_cart_rule` INT(10) UNSIGNED NOT NULL, + `is_omnibus` TINYINT(1) NOT NULL DEFAULT 0, + PRIMARY KEY (`id_cart_rule`) + ) + ENGINE = ' . _MYSQL_ENGINE_ . ' + DEFAULT CHARSET = utf8 + COLLATE = utf8_general_ci + '); + + $this->addForeignKey(CartRuleOptionsRepository::TABLE_NAME, 'cart_rule', ['id_cart_rule'], ['id_cart_rule'], self::CART_RULE_ID_FK, [ + 'onDelete' => 'CASCADE', + ]); + } +} diff --git a/modules/inpostizi/src/Installer/Database/Version_1_4_0.php b/modules/inpostizi/src/Installer/Database/Version_1_4_0.php new file mode 100644 index 00000000..821e0270 --- /dev/null +++ b/modules/inpostizi/src/Installer/Database/Version_1_4_0.php @@ -0,0 +1,155 @@ +tableExists(self::SESSIONS_TABLE)) { + $this->createSessionTable(); + + return; + } + + $this->cleanupSessionsTable(); + $this->dropColumn(self::SESSIONS_TABLE, 'basket_cached'); + $this->dropColumn(self::SESSIONS_TABLE, 'event'); + $this->changeColumn(self::SESSIONS_TABLE, 'session_id', 'INT(10) UNSIGNED NOT NULL'); + $this->changeColumn(self::SESSIONS_TABLE, 'cart_id', 'VARCHAR(255) NOT NULL'); + $this->changeColumn(self::SESSIONS_TABLE, 'order_id', 'INT(10) UNSIGNED'); + $this->changeColumn(self::SESSIONS_TABLE, 'redirected', 'TINYINT(1) NOT NULL DEFAULT 0'); + $this->addUniqueIndex(self::SESSIONS_TABLE, ['session_id'], self::CART_ID_IDX); + $this->addUniqueIndex(self::SESSIONS_TABLE, ['cart_id'], self::BASKET_ID_IDX); + $this->addForeignKeys(); + } + + private function createSessionTable(): void + { + $this->connection->executeStatement(' + CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . self::SESSIONS_TABLE . '` ( + id MEDIUMINT(9) NOT NULL AUTO_INCREMENT, + session_id INT(10) UNSIGNED NOT NULL, + cart_id VARCHAR(255) NOT NULL, + confirmation_response TEXT, + order_id INT(10) UNSIGNED, + order_details TEXT, + redirect_url VARCHAR(255), + basket_cache TEXT, + coupons TEXT, + redirected TINYINT(1) NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + CONSTRAINT `' . self::CART_ID_IDX . '` UNIQUE (`session_id`), + CONSTRAINT `' . self::BASKET_ID_IDX . '` UNIQUE (`cart_id`) + ) + ENGINE = ' . _MYSQL_ENGINE_ . ' + CHARSET = utf8 + COLLATE = utf8_general_ci; + '); + + $this->addForeignKeys(); + } + + private function addForeignKeys(): void + { + $this->addForeignKey(self::SESSIONS_TABLE, 'cart', ['session_id'], ['id_cart'], self::CART_ID_FK, [ + 'onDelete' => 'CASCADE', + ]); + $this->addForeignKey(self::SESSIONS_TABLE, 'orders', ['order_id'], ['id_order'], self::ORDER_ID_FK, [ + 'onDelete' => 'CASCADE', + ]); + } + + private function cleanupSessionsTable(): void + { + $this->deleteUnassociatedSessions(); + $this->updateNonExistentOrderIds(); + $this->deleteDuplicatedCartAssociations(); + $this->deleteDuplicatedIdentifiers(); + } + + private function deleteUnassociatedSessions(): void + { + $this->connection->executeStatement(' + DELETE bs + FROM `' . _DB_PREFIX_ . self::SESSIONS_TABLE . '` bs + LEFT JOIN `' . _DB_PREFIX_ . 'cart` c + ON c.id_cart = bs.session_id + WHERE c.id_cart IS NULL + '); + } + + private function updateNonExistentOrderIds(): void + { + $this->connection->executeStatement(' + UPDATE `' . _DB_PREFIX_ . self::SESSIONS_TABLE . '` bs + LEFT JOIN `' . _DB_PREFIX_ . 'orders` o + ON o.id_order = bs.order_id + SET bs.order_id = NULL + WHERE o.id_order IS NULL + '); + } + + private function deleteDuplicatedCartAssociations(): void + { + $sql = (new \DbQuery()) + ->type('DELETE') + ->from(self::SESSIONS_TABLE) + ->where('confirmation_response IS NULL') + ->where('order_id IS NULL'); + + $this->connection->executeStatement((string) $sql); + + $sql = (new \DbQuery()) + ->select('session_id, COUNT(*) - 1 AS `limit`') + ->from(self::SESSIONS_TABLE) + ->groupBy('session_id') + ->having('COUNT(*) > 1'); + + $data = $this->connection->fetchAllAssociative((string) $sql); + + foreach ($data as $row) { + $this->connection->executeStatement(' + DELETE FROM `' . _DB_PREFIX_ . self::SESSIONS_TABLE . '` + WHERE session_id = ' . (int) $row['session_id'] . ' + ORDER BY confirmation_response IS NULL + LIMIT ' . (int) $row['limit'] + ); + } + } + + private function deleteDuplicatedIdentifiers(): void + { + $sql = (new \DbQuery()) + ->select('cart_id, COUNT(*) - 1 AS `limit`') + ->from(self::SESSIONS_TABLE) + ->groupBy('cart_id') + ->having('COUNT(*) > 1'); + + $data = $this->connection->fetchAllAssociative((string) $sql); + + foreach ($data as $row) { + $this->connection->executeStatement(' + DELETE FROM `' . _DB_PREFIX_ . self::SESSIONS_TABLE . '` + WHERE cart_id = "' . $row['cart_id'] . '" + ORDER BY confirmation_response IS NULL + LIMIT ' . (int) $row['limit'] + ); + } + } +} diff --git a/modules/inpostizi/src/Installer/Database/Version_1_9_0.php b/modules/inpostizi/src/Installer/Database/Version_1_9_0.php new file mode 100644 index 00000000..51847ce9 --- /dev/null +++ b/modules/inpostizi/src/Installer/Database/Version_1_9_0.php @@ -0,0 +1,97 @@ +createCategoryRestrictionsTable(); + $this->createManufacturerRestrictionsTable(); + $this->createAttributeGroupRestrictionsTable(); + } + + private function createCategoryRestrictionsTable(): void + { + $this->connection->executeStatement(' + CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . ProductRestrictionsRepository::CATEGORY_RESTRICTIONS_TABLE . '` ( + id_category INT(10) UNSIGNED NOT NULL, + id_shop INT(11), + CONSTRAINT `' . self::CATEGORY_SHOP_ID_IDX . '` UNIQUE (`id_category`, `id_shop`) + ) + ENGINE = ' . _MYSQL_ENGINE_ . ' + CHARSET = utf8 + COLLATE = utf8_general_ci; + '); + + $this->addForeignKey(ProductRestrictionsRepository::CATEGORY_RESTRICTIONS_TABLE, 'category', ['id_category'], ['id_category'], self::CATEGORY_ID_FK, [ + 'onDelete' => 'CASCADE', + ]); + $this->addForeignKey(ProductRestrictionsRepository::CATEGORY_RESTRICTIONS_TABLE, 'shop', ['id_shop'], ['id_shop'], self::CATEGORY_SHOP_ID_FK, [ + 'onDelete' => 'CASCADE', + ]); + } + + private function createManufacturerRestrictionsTable(): void + { + $this->connection->executeStatement(' + CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . ProductRestrictionsRepository::MANUFACTURER_RESTRICTIONS_TABLE . '` ( + id_manufacturer INT(10) UNSIGNED NOT NULL, + id_shop INT(11), + CONSTRAINT `' . self::MANUFACTURER_SHOP_ID_IDX . '` UNIQUE (`id_manufacturer`, `id_shop`) + ) + ENGINE = ' . _MYSQL_ENGINE_ . ' + CHARSET = utf8 + COLLATE = utf8_general_ci; + '); + + $this->addForeignKey(ProductRestrictionsRepository::MANUFACTURER_RESTRICTIONS_TABLE, 'manufacturer', ['id_manufacturer'], ['id_manufacturer'], self::MANUFACTURER_ID_FK, [ + 'onDelete' => 'CASCADE', + ]); + $this->addForeignKey(ProductRestrictionsRepository::MANUFACTURER_RESTRICTIONS_TABLE, 'shop', ['id_shop'], ['id_shop'], self::MANUFACTURER_SHOP_ID_FK, [ + 'onDelete' => 'CASCADE', + ]); + } + + private function createAttributeGroupRestrictionsTable(): void + { + $this->connection->executeStatement(' + CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . ProductRestrictionsRepository::ATTRIBUTE_GROUP_RESTRICTIONS_TABLE . '` ( + id_attribute_group INT(11) NOT NULL, + id_shop INT(11), + CONSTRAINT `' . self::ATTRIBUTE_GROUP_SHOP_ID_IDX . '` UNIQUE (`id_attribute_group`, `id_shop`) + ) + ENGINE = ' . _MYSQL_ENGINE_ . ' + CHARSET = utf8 + COLLATE = utf8_general_ci; + '); + + $this->addForeignKey(ProductRestrictionsRepository::ATTRIBUTE_GROUP_RESTRICTIONS_TABLE, 'attribute_group', ['id_attribute_group'], ['id_attribute_group'], self::ATTRIBUTE_GROUP_ID_FK, [ + 'onDelete' => 'CASCADE', + ]); + $this->addForeignKey(ProductRestrictionsRepository::ATTRIBUTE_GROUP_RESTRICTIONS_TABLE, 'shop', ['id_shop'], ['id_shop'], self::ATTRIBUTE_GROUP_SHOP_ID_FK, [ + 'onDelete' => 'CASCADE', + ]); + } +} diff --git a/modules/inpostizi/src/Installer/Database/Version_2_0_0.php b/modules/inpostizi/src/Installer/Database/Version_2_0_0.php new file mode 100644 index 00000000..6a8eb1cf --- /dev/null +++ b/modules/inpostizi/src/Installer/Database/Version_2_0_0.php @@ -0,0 +1,21 @@ +addColumn(InPostIziBasketSession::TABLE_NAME, 'binding_api_key', 'VARCHAR(255)'); + $this->dropColumn(InPostIziBasketSession::TABLE_NAME, 'basket_cache'); + } +} diff --git a/modules/inpostizi/src/Installer/Database/Version_2_1_0.php b/modules/inpostizi/src/Installer/Database/Version_2_1_0.php new file mode 100644 index 00000000..4253e086 --- /dev/null +++ b/modules/inpostizi/src/Installer/Database/Version_2_1_0.php @@ -0,0 +1,105 @@ +createHotProductsTable(); + $this->updateCartRuleOptionsTable(); + $this->createFeatureRestrictionsTable(); + } + + private function createHotProductsTable(): void + { + if ($this->tableExists(HotProductRepository::TABLE_NAME)) { + return; + } + + $this->connection->executeStatement(' + CREATE TABLE `' . _DB_PREFIX_ . HotProductRepository::TABLE_NAME . '` ( + `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `product_id` INT(11) UNSIGNED NOT NULL, + `combination_id` INT(11) UNSIGNED, + `shop_id` INT(11), + `reference_id` varchar(32) NOT NULL, + `available_from` DATETIME, + `available_to` DATETIME, + `created_at` DATETIME NOT NULL, + `updated_at` DATETIME NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `' . self::HP_REFERENCE_ID_IDX . '` (`reference_id`, `shop_id`), + KEY `' . self::HP_PRODUCT_COMBINATION_ID_IDX . '` (`product_id`, `combination_id`) + ) + ENGINE = ' . _MYSQL_ENGINE_ . ' + CHARSET = utf8 + COLLATE = utf8_general_ci + '); + + $this->addForeignKey(HotProductRepository::TABLE_NAME, 'product', ['product_id'], ['id_product'], self::HP_PRODUCT_ID_FK, [ + 'onDelete' => 'CASCADE', + ]); + + $this->addForeignKey(HotProductRepository::TABLE_NAME, 'product_attribute', ['combination_id'], ['id_product_attribute'], self::HP_COMBINATION_ID_FK, [ + 'onDelete' => 'CASCADE', + ]); + + $this->addForeignKey(HotProductRepository::TABLE_NAME, 'shop', ['shop_id'], ['id_shop'], self::HP_SHOP_ID_FK, [ + 'onDelete' => 'CASCADE', + ]); + } + + private function updateCartRuleOptionsTable(): void + { + $this->addColumn(CartRuleOptionsRepository::TABLE_NAME, 'details_cms_id', 'INT(10) UNSIGNED'); + $this->addForeignKey(CartRuleOptionsRepository::TABLE_NAME, 'cms', ['details_cms_id'], ['id_cms'], self::CRO_CMS_ID_FK, [ + 'onDelete' => 'SET NULL', + ]); + } + + private function createFeatureRestrictionsTable(): void + { + $this->connection->executeStatement(' + CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . ProductRestrictionsRepository::FEATURE_RESTRICTIONS_TABLE . '` ( + `id_feature` INT(10) UNSIGNED NOT NULL, + `id_shop` INT(11), + CONSTRAINT `' . self::FR_FEATURE_SHOP_ID_IDX . '` UNIQUE (`id_feature`, `id_shop`) + ) + ENGINE = ' . _MYSQL_ENGINE_ . ' + CHARSET = utf8 + COLLATE = utf8_general_ci; + '); + + $this->addForeignKey(ProductRestrictionsRepository::FEATURE_RESTRICTIONS_TABLE, 'feature', ['id_feature'], ['id_feature'], self::FR_FEATURE_ID_FK, [ + 'onDelete' => 'CASCADE', + ]); + + $this->addForeignKey(ProductRestrictionsRepository::FEATURE_RESTRICTIONS_TABLE, 'shop', ['id_shop'], ['id_shop'], self::FR_SHOP_ID_FK, [ + 'onDelete' => 'CASCADE', + ]); + } +} diff --git a/modules/inpostizi/src/Installer/Database/Version_2_2_0.php b/modules/inpostizi/src/Installer/Database/Version_2_2_0.php new file mode 100644 index 00000000..f02f555b --- /dev/null +++ b/modules/inpostizi/src/Installer/Database/Version_2_2_0.php @@ -0,0 +1,53 @@ +updateBasketSessionTable(); + $this->createBasketAnalyticsTable(); + } + + private function updateBasketSessionTable(): void + { + $this->addColumn(InPostIziBasketSession::TABLE_NAME, 'id_shop', 'INT(10) DEFAULT NULL'); + $this->addForeignKey(InPostIziBasketSession::TABLE_NAME, 'shop', ['id_shop'], ['id_shop'], self::BS_SHOP_ID_FK, [ + 'onDelete' => 'SET NULL', + ]); + } + + private function createBasketAnalyticsTable(): void + { + $this->connection->executeStatement(' + CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . BasketAnalyticsRepository::TABLE_NAME . '` ( + `cart_id` INT(10) UNSIGNED NOT NULL, + `gclid` VARCHAR(512) DEFAULT NULL, + `fbclid` VARCHAR(512) DEFAULT NULL, + `client_id` VARCHAR(512) DEFAULT NULL, + PRIMARY KEY (`cart_id`) + ) + ENGINE = ' . _MYSQL_ENGINE_ . ' + CHARSET = utf8 + COLLATE = utf8_general_ci + '); + + $this->addForeignKey(BasketAnalyticsRepository::TABLE_NAME, 'cart', ['cart_id'], ['id_cart'], self::BASKET_ANALYTICS_CART_ID_FK, [ + 'onDelete' => 'CASCADE', + ]); + } +} diff --git a/modules/inpostizi/src/Installer/DatabaseInstaller.php b/modules/inpostizi/src/Installer/DatabaseInstaller.php new file mode 100644 index 00000000..5d3730a9 --- /dev/null +++ b/modules/inpostizi/src/Installer/DatabaseInstaller.php @@ -0,0 +1,94 @@ + + */ + private $migrations; + + /** + * @param iterable|null $migrations + */ + public function __construct(?ShopAwareConfigurationInterface $configuration = null, ?iterable $migrations = null) + { + $this->configuration = $configuration ?? new Configuration(); + $this->migrations = $migrations ?? $this->getDefaultMigrations(); + } + + public function install(\Module $module): void + { + $currentVersion = $this->configuration->getGlobal(self::SCHEMA_VERSION_CONFIG_KEY) ?: '0'; + + foreach ($this->getSortedMigrations() as $migration) { + if (\Tools::version_compare($currentVersion, $version = $migration->getVersion(), '>=')) { + continue; + } + + if (\Tools::version_compare($module->version, $version)) { + return; + } + + $this->migrateUp($migration); + + $currentVersion = $version; + } + } + + private function getSortedMigrations(): array + { + $migrations = $this->migrations; + if ($migrations instanceof \Traversable) { + $migrations = iterator_to_array($migrations); + } + + usort($migrations, static function (DatabaseMigrationInterface $m1, DatabaseMigrationInterface $m2): int { + return version_compare($m1->getVersion(), $m2->getVersion()); + }); + + return $migrations; + } + + private function migrateUp(DatabaseMigrationInterface $migration): void + { + $migration->up(); + $this->updateSchemaVersion($migration->getVersion()); + } + + private function updateSchemaVersion(?string $version): void + { + $this->configuration->setGlobal(self::SCHEMA_VERSION_CONFIG_KEY, $version); + } + + /** + * @return DatabaseMigrationInterface[] + */ + private function getDefaultMigrations(): array + { + $connection = new Connection(\Db::getInstance()); + + return [ + new Database\Version_1_4_0($connection), + new Database\Version_1_9_0($connection), + new Database\Version_1_11_0($connection), + new Database\Version_2_0_0($connection), + new Database\Version_2_1_0($connection), + new Database\Version_2_2_0($connection), + ]; + } +} diff --git a/modules/inpostizi/src/Installer/DatabaseMigrationInterface.php b/modules/inpostizi/src/Installer/DatabaseMigrationInterface.php new file mode 100644 index 00000000..bba1a91b --- /dev/null +++ b/modules/inpostizi/src/Installer/DatabaseMigrationInterface.php @@ -0,0 +1,14 @@ +environment = $environment; + } + + public function create(array $options): HandlerInterface + { + $options = $this->getOptionsResolver()->resolve($options); + + return $this->doCreate($options); + } + + protected function createOptionsResolver(): OptionsResolver + { + return (new OptionsResolver()) + ->setDefaults([ + 'level' => 'prod' === $this->environment ? Logger::INFO : Logger::DEBUG, + 'bubble' => true, + ]) + ->setNormalizer('level', static function (Options $options, $value) { + if (null === $value || is_numeric($value)) { + return $value; + } + + $upper = strtoupper($value); + $constName = sprintf('%s::%s', Logger::class, $upper); + + if (defined($constName)) { + return constant($constName); + } + + throw new \InvalidArgumentException(sprintf('Could not match "%s" to a log level.', $value)); + }); + } + + abstract protected function doCreate(array $options): HandlerInterface; + + private function getOptionsResolver(): OptionsResolver + { + return $this->optionsResolver ?? ($this->optionsResolver = $this->createOptionsResolver()); + } +} diff --git a/modules/inpostizi/src/Log/Handler/HandlerFactoryInterface.php b/modules/inpostizi/src/Log/Handler/HandlerFactoryInterface.php new file mode 100644 index 00000000..8ab2f299 --- /dev/null +++ b/modules/inpostizi/src/Log/Handler/HandlerFactoryInterface.php @@ -0,0 +1,14 @@ +setFilenameFormat($options['filename_format'], $options['date_format']); + + return $handler; + } + + protected function createOptionsResolver(): OptionsResolver + { + return parent::createOptionsResolver() + ->setRequired('path') + ->setDefaults([ + 'max_files' => 0, + 'file_permission' => null, + 'use_locking' => false, + 'filename_format' => '{filename}-{date}', + 'date_format' => 'Y-m-d', + ]) + ->setNormalizer('file_permission', static function (Options $options, $value) { + if (!is_string($value)) { + return $value; + } + + if (0 === strpos($value, '0')) { + return octdec($value); + } + + return (int) $value; + }); + } +} diff --git a/modules/inpostizi/src/Log/LoggerFactoryInterface.php b/modules/inpostizi/src/Log/LoggerFactoryInterface.php new file mode 100644 index 00000000..0c5a4962 --- /dev/null +++ b/modules/inpostizi/src/Log/LoggerFactoryInterface.php @@ -0,0 +1,12 @@ + $factories + */ + private $factories; + + /** + * @var array + */ + private $handlers = []; + + /** + * @param iterable $factories + */ + public function __construct(iterable $factories) + { + $this->factories = $factories; + } + + public function create(string $name, array $options): LoggerInterface + { + $handler = $this->getHandler($name, $options); + + return new Logger($name, [$handler]); + } + + private function getHandler(string $name, array $options): HandlerInterface + { + return $this->handlers[$name] ?? ($this->handlers[$name] = $this->createHandler($options)); + } + + private function createHandler(array $options): HandlerInterface + { + if (!isset($options['type']) || !is_string($options['type'])) { + throw new \InvalidArgumentException('A required "type" option is missing.'); + } + + $handler = $this + ->getHandlerFactory($options['type']) + ->create($this->filterHandlerOptions($options)); + + if (is_array($options['channels'] ?? null)) { + foreach ($options['channels'] as $channel) { + $this->handlers[$channel] = $handler; + } + } + + if (null === ($options['process_psr_3_messages']['enabled'] ?? null)) { + $options['process_psr_3_messages']['enabled'] = !isset($options['handler']) && empty($options['members']); + } + + if ($options['process_psr_3_messages']['enabled']) { + $this->processPsrMessages($handler, $options['process_psr_3_messages']); + } + + if ($options['include_stacktraces'] ?? false) { + $this->includeStacktraces($handler); + } + + return $handler; + } + + private function filterHandlerOptions(array $options): array + { + unset($options['type'], $options['channels'], $options['include_stacktraces'], $options['process_psr_3_messages']); + + return $options; + } + + private function getHandlerFactory(string $type): HandlerFactoryInterface + { + foreach ($this->factories as $factory) { + if ($factory->supports($type)) { + return $factory; + } + } + + throw new \RuntimeException(sprintf('No log handler factory registered for type "%s".', $type)); + } + + private function includeStacktraces(HandlerInterface $handler): void + { + $formatter = $handler->getFormatter(); + if ($formatter instanceof LineFormatter || $formatter instanceof JsonFormatter) { + $formatter->includeStacktraces(); + } + } + + private function processPsrMessages(HandlerInterface $handler, array $options): void + { + $removeContextFields = $options['remove_used_context_fields'] ?? false; + $processor = new PsrLogMessageProcessor($options['date_format'] ?? null, $removeContextFields); + + if ($removeContextFields && self::shouldDecoratePsrProcessor()) { + $processor = new UsedContextFieldsRemovingProcessor($processor); + } + + $handler->pushProcessor($processor); + } + + private static function shouldDecoratePsrProcessor(): bool + { + if (isset(self::$decoratePsrProcessor)) { + return self::$decoratePsrProcessor; + } + + $class = new \ReflectionClass(PsrLogMessageProcessor::class); + + if (null === $constructor = $class->getConstructor()) { + return self::$decoratePsrProcessor = true; + } + + return self::$decoratePsrProcessor = 2 > count($constructor->getParameters()); + } +} + +/** + * @internal + */ +final class UsedContextFieldsRemovingProcessor +{ + /** + * @var callable + */ + private $processor; + + public function __construct(callable $processor) + { + $this->processor = $processor; + } + + public function __invoke(array $record): array + { + if (false === strpos($record['message'], '{')) { + return ($this->processor)($record); + } + + $toRemove = []; + + foreach ($record['context'] as $key => $val) { + $placeholder = '{' . $key . '}'; + if (false === strpos($record['message'], $placeholder)) { + continue; + } + + $toRemove[] = $key; + } + + $record = ($this->processor)($record); + + foreach ($toRemove as $key) { + unset($record['context'][$key]); + } + + return $record; + } +} diff --git a/modules/inpostizi/src/Mail/Dto/MailRecipient.php b/modules/inpostizi/src/Mail/Dto/MailRecipient.php new file mode 100644 index 00000000..7db2cbab --- /dev/null +++ b/modules/inpostizi/src/Mail/Dto/MailRecipient.php @@ -0,0 +1,42 @@ +bcc = $bcc; + $this->emails = $email; + } + + /** + * @return string[] + */ + public function getBcc(): array + { + return $this->bcc; + } + + /** + * @return string[] + */ + public function getEmails(): array + { + return $this->emails; + } +} diff --git a/modules/inpostizi/src/Mail/Event/SendEmailEvent.php b/modules/inpostizi/src/Mail/Event/SendEmailEvent.php new file mode 100644 index 00000000..77c299f8 --- /dev/null +++ b/modules/inpostizi/src/Mail/Event/SendEmailEvent.php @@ -0,0 +1,119 @@ + + */ + private $parameters; + + /** + * @var string[] + */ + private $recipients = []; + + /** + * @var string[]|null + */ + private $bcc; + + /** + * @param array $parameters + */ + public function __construct(string $template, array $parameters) + { + $this->template = $template; + $this->parameters = $parameters; + } + + public function getTemplate(): string + { + return $this->template; + } + + /** + * @return array + */ + public function getParameters(): array + { + return $this->parameters; + } + + public function hasParameter(string $name): bool + { + return array_key_exists($name, $this->parameters); + } + + /** + * @return mixed + */ + public function getParameter(string $name) + { + return $this->parameters[$name] ?? null; + } + + /** + * @return string[] + */ + public function getRecipients(): array + { + return $this->recipients; + } + + public function hasRecipient(string $email): bool + { + return in_array($email, $this->recipients, true); + } + + public function setRecipients(string $email, string ...$emails): void + { + $this->recipients = array_merge([$email], $emails); + } + + public function addRecipient(string $email): void + { + $this->recipients[] = $email; + } + + /** + * @return string[]|null + */ + public function getBcc(): ?array + { + return $this->bcc; + } + + public function hasBcc(string $email): bool + { + if (null === $this->bcc) { + return false; + } + + return in_array($email, $this->bcc, true); + } + + public function setBcc(string ...$emails): void + { + $this->bcc = $emails; + } + + public function addBcc(string $email): void + { + if (null === $this->bcc) { + $this->bcc = []; + } + + $this->bcc[] = $email; + } +} diff --git a/modules/inpostizi/src/Mail/EventListener/ReplaceOrderNotificationRecipientListener.php b/modules/inpostizi/src/Mail/EventListener/ReplaceOrderNotificationRecipientListener.php new file mode 100644 index 00000000..948995b3 --- /dev/null +++ b/modules/inpostizi/src/Mail/EventListener/ReplaceOrderNotificationRecipientListener.php @@ -0,0 +1,45 @@ +recipientResolver = $recipientResolver; + } + + public static function getSubscribedEvents(): array + { + return [ + SendEmailEvent::class => 'onSendEmail', + ]; + } + + public function onSendEmail(SendEmailEvent $event): void + { + if (!$event->hasParameter('{id_order}')) { + return; + } + + $recipient = $this->recipientResolver->resolve( + (string) $event->getParameter('{id_order}'), + $event->getRecipients(), + $event->getBcc() ?? [] + ); + + $event->setRecipients(...$recipient->getEmails()); + $event->setBcc(...$recipient->getBcc()); + } +} diff --git a/modules/inpostizi/src/Mail/Resolver/OrderMailRecipientResolver.php b/modules/inpostizi/src/Mail/Resolver/OrderMailRecipientResolver.php new file mode 100644 index 00000000..afc75f90 --- /dev/null +++ b/modules/inpostizi/src/Mail/Resolver/OrderMailRecipientResolver.php @@ -0,0 +1,106 @@ + + */ + private $orderRepository; + + /** + * @var string[] + */ + private $emailList; + + /** + * @var CreateOrderRequest|null + */ + private $orderData; + + /** + * @var string[] + */ + private $bccList; + + /** + * @var \Customer + */ + private $customer; + + /** + * @param ObjectRepositoryInterface<\Order> $orderRepository + */ + public function __construct( + OrderDataRepositoryInterface $orderDataRepository, + ObjectRepositoryInterface $orderRepository + ) { + $this->orderDataRepository = $orderDataRepository; + $this->orderRepository = $orderRepository; + } + + public function resolve(string $idOrder, array $emails, array $bcc): MailRecipient + { + $this->emailList = $emails; + $this->bccList = $bcc; + $this->createCustomer($idOrder); + if (!$this->resolveIsInpostOrder($idOrder) || !$this->resolveIsCustomerMail()) { + return new MailRecipient($this->emailList, $this->bccList); + } + + return $this->resolveRecipientMail(); + } + + private function resolveIsInpostOrder(string $idOrder): bool + { + $this->orderData = $this->orderDataRepository->getOrderData($idOrder); + + return !empty($this->orderData); + } + + private function resolveRecipientMail(): MailRecipient + { + if (null === $this->orderData->getDelivery()->getEmail()) { + return new MailRecipient($this->emailList, $this->bccList); + } + $this->addDeliveryMailToSend(); + + return new MailRecipient($this->emailList, $this->bccList); + } + + private function createCustomer(string $idOrder): void + { + if (null === $order = $this->orderRepository->find((int) $idOrder)) { + throw new \RuntimeException('Order does not exist.'); + } + $this->customer = $order->getCustomer(); + } + + private function resolveIsCustomerMail(): bool + { + return in_array($this->customer->email, $this->emailList); + } + + private function addDeliveryMailToSend() + { + if ($this->customer->email === $this->orderData->getAccountInfo()->getEmail()) { + $key = array_search($this->customer->email, $this->emailList); + if (false !== $key) { + $this->emailList[$key] = $this->orderData->getDelivery()->getEmail(); + } + } else { + $this->bccList[] = $this->orderData->getDelivery()->getEmail(); + } + } +} diff --git a/modules/inpostizi/src/MerchantApi/Command/AddProductToBasketCommand.php b/modules/inpostizi/src/MerchantApi/Command/AddProductToBasketCommand.php new file mode 100644 index 00000000..7ec89ba2 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Command/AddProductToBasketCommand.php @@ -0,0 +1,42 @@ +productId = $productId; + $this->request = $request; + } + + public function getProductId(): string + { + return $this->productId; + } + + public function getRequest(): BasketId + { + return $this->request; + } +} diff --git a/modules/inpostizi/src/MerchantApi/Command/Basket/AddProductToCartCommand.php b/modules/inpostizi/src/MerchantApi/Command/Basket/AddProductToCartCommand.php new file mode 100644 index 00000000..8313f53e --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Command/Basket/AddProductToCartCommand.php @@ -0,0 +1,67 @@ +cart = $cart; + $this->productId = $productId; + $this->combinationId = $combinationId; + $this->quantity = $quantity; + } + + public function getCart(): \Cart + { + return $this->cart; + } + + public function getProductId(): int + { + return $this->productId; + } + + public function getCombinationId(): ?int + { + return $this->combinationId; + } + + public function getQuantity(): ?int + { + return $this->quantity; + } +} diff --git a/modules/inpostizi/src/MerchantApi/Command/Basket/CreateCartCommand.php b/modules/inpostizi/src/MerchantApi/Command/Basket/CreateCartCommand.php new file mode 100644 index 00000000..230d50d1 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Command/Basket/CreateCartCommand.php @@ -0,0 +1,23 @@ +shopId = $shopId; + } + + public function getShopId(): ?int + { + return $this->shopId; + } +} diff --git a/modules/inpostizi/src/MerchantApi/Command/Basket/IncrementCartQuantityCommand.php b/modules/inpostizi/src/MerchantApi/Command/Basket/IncrementCartQuantityCommand.php new file mode 100644 index 00000000..c06f6702 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Command/Basket/IncrementCartQuantityCommand.php @@ -0,0 +1,72 @@ +cart = $cart; + $this->productId = $productId; + $this->combinationId = $combinationId; + $this->customizationId = $customizationId; + $this->quantity = $quantity; + } + + public function getCart(): \Cart + { + return $this->cart; + } + + public function getProductId(): int + { + return $this->productId; + } + + public function getCombinationId(): int + { + return $this->combinationId; + } + + public function getCustomizationId(): int + { + return $this->customizationId; + } + + public function getQuantity(): int + { + return $this->quantity; + } +} diff --git a/modules/inpostizi/src/MerchantApi/Command/ConfirmBasketBindingCommand.php b/modules/inpostizi/src/MerchantApi/Command/ConfirmBasketBindingCommand.php new file mode 100644 index 00000000..ac68a48f --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Command/ConfirmBasketBindingCommand.php @@ -0,0 +1,36 @@ +basketId = $basketId; + $this->confirmation = $confirmation; + } + + public function getBasketId(): string + { + return $this->basketId; + } + + public function getConfirmation(): BindingConfirmation + { + return $this->confirmation; + } +} diff --git a/modules/inpostizi/src/MerchantApi/Command/CreateOrderCommand.php b/modules/inpostizi/src/MerchantApi/Command/CreateOrderCommand.php new file mode 100644 index 00000000..f7261636 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Command/CreateOrderCommand.php @@ -0,0 +1,29 @@ +request = $request; + } + + public function getRequest(): CreateOrderRequest + { + return $this->request; + } +} diff --git a/modules/inpostizi/src/MerchantApi/Command/DeleteBasketBindingCommand.php b/modules/inpostizi/src/MerchantApi/Command/DeleteBasketBindingCommand.php new file mode 100644 index 00000000..c1f11d94 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Command/DeleteBasketBindingCommand.php @@ -0,0 +1,23 @@ +basketId = $basketId; + } + + public function getBasketId(): string + { + return $this->basketId; + } +} diff --git a/modules/inpostizi/src/MerchantApi/Command/GetBasketCommand.php b/modules/inpostizi/src/MerchantApi/Command/GetBasketCommand.php new file mode 100644 index 00000000..d3380374 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Command/GetBasketCommand.php @@ -0,0 +1,23 @@ +basketId = $basketId; + } + + public function getBasketId(): string + { + return $this->basketId; + } +} diff --git a/modules/inpostizi/src/MerchantApi/Command/GetOrderCommand.php b/modules/inpostizi/src/MerchantApi/Command/GetOrderCommand.php new file mode 100644 index 00000000..b7686638 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Command/GetOrderCommand.php @@ -0,0 +1,28 @@ +orderId = $orderId; + } + + public function getOrderId(): string + { + return $this->orderId; + } +} diff --git a/modules/inpostizi/src/MerchantApi/Command/GetProductsCommand.php b/modules/inpostizi/src/MerchantApi/Command/GetProductsCommand.php new file mode 100644 index 00000000..87c9e79f --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Command/GetProductsCommand.php @@ -0,0 +1,86 @@ +shopId = $shopId; + $this->pageIndex = $pageIndex; + $this->pageSize = $pageSize; + $this->productIds = $productIds; + } + + public function getShopId(): int + { + return $this->shopId; + } + + public function getPageIndex(): ?int + { + return $this->pageIndex; + } + + public function getPageSize(): ?int + { + return $this->pageSize; + } + + /** + * @return string[]|null + */ + public function getProductIds(): ?array + { + return $this->productIds; + } +} diff --git a/modules/inpostizi/src/MerchantApi/Command/Order/UpdateCartMessageCommand.php b/modules/inpostizi/src/MerchantApi/Command/Order/UpdateCartMessageCommand.php new file mode 100644 index 00000000..c1f62e66 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Command/Order/UpdateCartMessageCommand.php @@ -0,0 +1,40 @@ +cart = $cart; + $this->request = $request; + } + + public function getCart(): \Cart + { + return $this->cart; + } + + public function getRequest(): CreateOrderRequest + { + return $this->request; + } +} diff --git a/modules/inpostizi/src/MerchantApi/Command/UpdateBasketCommand.php b/modules/inpostizi/src/MerchantApi/Command/UpdateBasketCommand.php new file mode 100644 index 00000000..b9fdd5ff --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Command/UpdateBasketCommand.php @@ -0,0 +1,36 @@ +basketId = $basketId; + $this->event = $event; + } + + public function getBasketId(): string + { + return $this->basketId; + } + + public function getEvent(): BasketEvent + { + return $this->event; + } +} diff --git a/modules/inpostizi/src/MerchantApi/Command/UpdateOrderCommand.php b/modules/inpostizi/src/MerchantApi/Command/UpdateOrderCommand.php new file mode 100644 index 00000000..375c1bc3 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Command/UpdateOrderCommand.php @@ -0,0 +1,40 @@ +orderId = $orderId; + $this->event = $event; + } + + public function getOrderId(): string + { + return $this->orderId; + } + + public function getEvent(): OrderEvent + { + return $this->event; + } +} diff --git a/modules/inpostizi/src/MerchantApi/Event/CartUpdatedEvent.php b/modules/inpostizi/src/MerchantApi/Event/CartUpdatedEvent.php new file mode 100644 index 00000000..9dc35cb5 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Event/CartUpdatedEvent.php @@ -0,0 +1,25 @@ +cart = $cart; + } + + public function getCart(): \Cart + { + return $this->cart; + } +} diff --git a/modules/inpostizi/src/MerchantApi/EventListener/UpdateCartRulesListener.php b/modules/inpostizi/src/MerchantApi/EventListener/UpdateCartRulesListener.php new file mode 100644 index 00000000..849fc12e --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/EventListener/UpdateCartRulesListener.php @@ -0,0 +1,37 @@ +context = $context; + } + + public static function getSubscribedEvents(): array + { + return [ + CartUpdatedEvent::class => 'onCartUpdated', + ]; + } + + public function onCartUpdated(CartUpdatedEvent $event): void + { + $context = clone $this->context; + $context->cart = $event->getCart(); + + \CartRule::autoAddToCart($context); + \CartRule::autoRemoveFromCart($context); + } +} diff --git a/modules/inpostizi/src/MerchantApi/Exception/ApiException.php b/modules/inpostizi/src/MerchantApi/Exception/ApiException.php new file mode 100644 index 00000000..618e4662 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Exception/ApiException.php @@ -0,0 +1,12 @@ +availableQuantity = $availableQuantity; + } + + public function getErrorCode(): string + { + return self::ERROR_CODE; + } + + public function getStatusCode(): int + { + return 409; + } + + public function getAvailableQuantity(): int + { + return $this->availableQuantity; + } + + public static function create(int $availableQuantity): self + { + return new self($availableQuantity, 'Product is out of stock.'); + } +} diff --git a/modules/inpostizi/src/MerchantApi/Exception/ServiceUnavailableException.php b/modules/inpostizi/src/MerchantApi/Exception/ServiceUnavailableException.php new file mode 100644 index 00000000..42266962 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Exception/ServiceUnavailableException.php @@ -0,0 +1,25 @@ +signingKeysService = $signingKeysService; + $this->clock = $clock; + } + + public function authenticate(Request $request): void + { + $publicKeyHash = $this->getHeader($request, 'x-public-key-hash'); + $publicKeyVersion = $this->getHeader($request, 'x-public-key-ver'); + $signature = $this->getHeader($request, 'x-signature'); + $signatureTimestamp = $this->getHeader($request, 'x-signature-timestamp'); + + $signingKey = $this->getSigningKey($publicKeyVersion, $publicKeyHash); + $publicKey = $this->extractPublicKey($signingKey->getPublicKey()); + + $data = $this->generateData($signingKey, $request->getContent(), $signatureTimestamp); + $this->verifySignature($data, $signature, $publicKey); + + $this->checkTimestamp($signatureTimestamp); + } + + private function getHeader(Request $request, string $name): string + { + if (!$request->headers->has($name)) { + throw InvalidSignatureException::missingHeader($name); + } + + return $request->headers->get($name); + } + + private function getSigningKey(string $version, string $hash): SigningKey + { + $key = $this->signingKeysService->getSigningKey($version); + + if (null === $key) { + throw new InvalidSignatureException('Could not find public key with given version.'); + } + + if ($hash !== $key->getPublicKey()->getHash()) { + throw new InvalidSignatureException('Public key hash mismatch.'); + } + + return $key; + } + + private function generateData(SigningKey $key, string $body, string $signatureTimestamp): string + { + $digest = base64_encode(hash('sha256', $body, true)); + + return base64_encode(sprintf( + '%s,%s,%s,%s', + $digest, + $key->getMerchantId(), + $key->getPublicKey()->getVersion(), + $signatureTimestamp + )); + } + + private function extractPublicKey(PublicKey $key) + { + $publicKey = openssl_pkey_get_public($key->getPemFormatted()); + + if (false === $publicKey) { + throw new \RuntimeException(sprintf('Could not extract public key: %s.', openssl_error_string())); + } + + return $publicKey; + } + + /** + * @param resource $publicKey + */ + private function verifySignature(string $data, string $signature, $publicKey): void + { + $result = openssl_verify($data, base64_decode($signature), $publicKey, OPENSSL_ALGO_SHA256); + + switch ($result) { + case -1: + throw new \RuntimeException(sprintf('Could not verify signature: %s.', openssl_error_string())); + case 0: + throw new InvalidSignatureException('Signature mismatch.'); + default: + break; + } + } + + private function checkTimestamp(string $signatureTimestamp): void + { + if (false === $signatureTime = \DateTimeImmutable::createFromFormat('Y-m-d\TH:i:s.uP', $signatureTimestamp)) { + throw new InvalidSignatureException('Malformed timestamp.'); + } + + if ($signatureTime < $this->clock->now()->sub(new \DateInterval('PT240S'))) { + throw new InvalidSignatureException('Signature expired.'); + } + } +} diff --git a/modules/inpostizi/src/MerchantApi/Firewall/SigningKeysService.php b/modules/inpostizi/src/MerchantApi/Firewall/SigningKeysService.php new file mode 100644 index 00000000..82e2dcdf --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Firewall/SigningKeysService.php @@ -0,0 +1,81 @@ +client = $client; + $this->cache = $cache; + } + + public function getSigningKey(string $version): ?SigningKey + { + $cachedKeys = $this->getCachedKeys(); + + if (null !== $cachedKeys && $publicKey = $cachedKeys->getPublicKey($version)) { + return new SigningKey($cachedKeys->getMerchantId(), $publicKey); + } + + $newKeys = $this->refreshKeysCache(); + + if (null === $publicKey = $newKeys->getPublicKey($version)) { + return null; + } + + return new SigningKey($newKeys->getMerchantId(), $publicKey); + } + + private function getCachedKeys(): ?SigningKeys + { + if (isset($this->keys)) { + return $this->keys; + } + + $keys = $this->cache->get(self::CACHE_KEY); + + if (!$keys instanceof SigningKeys) { + return null; + } + + return $this->keys = $keys; + } + + private function refreshKeysCache(): SigningKeys + { + $keys = $this->client->getSigningKeys(); + + foreach ($keys as $key) { + $key->getHash(); + } + + $this->cache->set(self::CACHE_KEY, $keys, new \DateInterval('P1D')); + + return $this->keys = $keys; + } +} diff --git a/modules/inpostizi/src/MerchantApi/Firewall/SigningKeysServiceInterface.php b/modules/inpostizi/src/MerchantApi/Firewall/SigningKeysServiceInterface.php new file mode 100644 index 00000000..c47d31d2 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Firewall/SigningKeysServiceInterface.php @@ -0,0 +1,12 @@ +repository = $repository; + $this->contextManager = $contextManager; + $this->bus = $bus; + $this->dispatcher = $dispatcher; + $this->basketBuilderFactory = $basketBuilderFactory; + } + + public function __invoke(AddProductToBasketCommand $command): IdentifiableBasket + { + $basketId = $command->getRequest()->getBasketId(); + $session = $this->findOrCreateSession($basketId); + + $referenceId = ReferenceId::fromString($command->getProductId()); + + if (null === $referenceId) { + throw ProductNotFoundException::create(); + } + + if ($referenceId->hasCustomization()) { + throw new MalformedRequestException('Adding customizable products is not supported.'); + } + + $productId = $referenceId->getProductId(); + $combinationId = $referenceId->getCombinationId(); + + $cart = $session->getBasket()->getEntity(); + + if (ProductHelper::isInCart($cart, $productId, (int) $combinationId)) { + $command = new IncrementCartQuantityCommand($cart, $productId, (int) $combinationId); + } else { + $command = new AddProductToCartCommand($cart, $productId, $combinationId); + } + + try { + $this->contextManager->changeContext($cart, [ + 'shop_id' => $session->getShopId(), + ]); + + try { + $this->bus->handle($command); + } catch (ProductAlreadyInCartException $e) { + assert($command instanceof AddProductToCartCommand && null === $combinationId); + $cartProduct = $e->getProduct(); + $this->bus->handle(new IncrementCartQuantityCommand($cart, $productId, (int) $cartProduct['id_product_attribute'])); + } + + $this->dispatcher->dispatch(new CartUpdatedEvent($cart)); + $this->updateSession($session); + + return $this->buildResponse($session); + } finally { + $this->contextManager->restoreContext(); + } + } + + private function findOrCreateSession(?string $basketId): BasketSessionInterface + { + if (null === $basketId) { + return $this->createNewSession(); + } + + if (null === $session = $this->repository->findByBasketId($basketId)) { + throw BasketNotFoundException::create(); + } + + if (BasketSession::isFinalized($session)) { + throw OrderExistsException::create(); + } + + return $session; + } + + private function createNewSession(): BasketSessionInterface + { + /** @var Cart $cart */ + $cart = $this->bus->handle(new CreateCartCommand()); + + $session = $this->repository->createNewSession($cart); + $this->repository->persist($session); + + return $session; + } + + /** + * Needed to trigger Widget v1 view update. + */ + private function updateSession(BasketSessionInterface $session): void + { + $now = new \DateTimeImmutable(); + + $session->updatedBy(new BasketEvent(sprintf('AP_%d', $now->getTimestamp()), $now, EventType::ProductsQuantity())); + $this->repository->persist($session); + } + + private function buildResponse(BasketSessionInterface $session): IdentifiableBasket + { + return $this->basketBuilderFactory + ->createResponseBuilder($session->getBasket(), $session->getShopId()) + ->build() + ->asIdentifiable($session->getBasketId()); + } +} diff --git a/modules/inpostizi/src/MerchantApi/Handler/AddProductToBasketHandlerInterface.php b/modules/inpostizi/src/MerchantApi/Handler/AddProductToBasketHandlerInterface.php new file mode 100644 index 00000000..a4a51424 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Handler/AddProductToBasketHandlerInterface.php @@ -0,0 +1,13 @@ +context = $context; + $this->productRepository = $productRepository; + $this->translator = $translator; + $this->logger = $logger; + } + + public function __invoke(AddProductToCartCommand $command): void + { + $quantity = $command->getQuantity(); + + if (null !== $quantity && 0 >= $quantity) { + throw new CannotAddProductException($this->context->getTranslator()->trans('Null quantity.', [], 'Shop.Notifications.Error')); + } + + if (!\Validate::isLoadedObject($cart = $command->getCart())) { + throw new \DomainException('Cart does not exist.'); + } + + $productId = $command->getProductId(); + $combinationId = $command->getCombinationId(); + + $productWithCombination = $this->productRepository->findWithCombination( + $productId, + $combinationId, + (int) $cart->id_lang, + null, + true + ); + + if (null === $productWithCombination) { + throw ProductNotFoundException::create(); + } + + $product = $productWithCombination->getProduct(); + $combination = $productWithCombination->getCombination(); + $combinationId = $combination ? (int) $combination->id : 0; + + if (!$product->active || !$product->available_for_order || !$product->checkAccess((int) $cart->id_customer)) { + throw CannotAddProductException::create($this->context->getTranslator()->trans('This product (%product%) is no longer available.', ['%product%' => $product->name], 'Shop.Notifications.Error')); + } + + if (2 === (int) $product->customizable) { + throw CannotAddProductException::create($this->translator->l('This product requires customization. Please add it to your cart via the shop page.', RelatedProductsEventHandler::TRANSLATION_SOURCE)); + } + + if ($cartProduct = ProductHelper::findProductInCart($cart, $productId, $combinationId)) { + throw new ProductAlreadyInCartException($cartProduct); + } + + $minimalQuantity = $combination ? (int) $combination->minimal_quantity : (int) $product->minimal_quantity; + $quantity = $quantity ?? $minimalQuantity; + + if ($quantity < $minimalQuantity) { + throw CannotAddProductException::create($this->context->getTranslator()->trans('The minimum purchase order quantity for the product %product% is %quantity%.', ['%product%' => $product->name, '%quantity%' => $minimalQuantity], 'Shop.Notifications.Error')); + } + + $this->assertQuantityIsAvailable($quantity, $productId, $combinationId, $cart); + $this->addToCart($cart, $productId, $combinationId, $quantity); + } + + private function addToCart(\Cart $cart, int $productId, int $combinationId, int $quantity): void + { + try { + $result = $cart->updateQty( + $quantity, + $productId, + $combinationId, + 0, + 'up', + 0, + null, + false + ); + } catch (\Exception $e) { + $result = false; + } + + if (false !== $result) { + return; + } + + $this->logger->critical('Could not add product [{productId}] to cart #{cartId}.', [ + 'productId' => implode('-', [$productId, $combinationId]), + 'cartId' => $cart->id, + ]); + + throw $e ?? new CannotAddProductException($this->translator->l('Could not add the product to your cart.', RelatedProductsEventHandler::TRANSLATION_SOURCE)); + } + + private function assertQuantityIsAvailable(int $quantity, int $productId, int $combinationId, \Cart $cart): void + { + if ($this->productRepository->isAvailableOutOfStock($productId)) { + return; + } + + $availableQuantity = $this->productRepository->getAvailableQuantity($productId, $combinationId, $cart); + + if ($quantity > $availableQuantity) { + throw ProductOutOfStockException::create(max($availableQuantity, 0)); + } + } +} diff --git a/modules/inpostizi/src/MerchantApi/Handler/Basket/AddProductToCartHandlerInterface.php b/modules/inpostizi/src/MerchantApi/Handler/Basket/AddProductToCartHandlerInterface.php new file mode 100644 index 00000000..9f048184 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Handler/Basket/AddProductToCartHandlerInterface.php @@ -0,0 +1,10 @@ +locator = $locator; + + if ($dispatcher instanceof ContextManager) { + $contextManager = $dispatcher; + $dispatcher = $this->createEventDispatcher($contextManager->getContext()); + } + + if (!$contextManager instanceof ContextManager) { + throw new \InvalidArgumentException(sprintf('Expected parameter $contextManager to be an instance of "%s", "%s" given.', ContextManager::class, get_debug_type($contextManager))); + } + + if (!$dispatcher instanceof EventDispatcherInterface) { + throw new \InvalidArgumentException(sprintf('Expected parameter $dispatcher to be an instance of "%s", "%s" given.', EventDispatcherInterface::class, get_debug_type($dispatcher))); + } + + $this->contextManager = $contextManager; + $this->dispatcher = $dispatcher; + } + + public static function getSubscribedServices(): array + { + return [ + EventType::ProductsQuantity()->value => ProductsQuantityEventHandler::class, + EventType::PromoCodes()->value => PromoCodesEventHandler::class, + EventType::RelatedProducts()->value => RelatedProductsEventHandler::class, + ]; + } + + public function handle(BasketInterface $basket, BasketEvent $event, ?int $shopId = null): ?Notice + { + /** @var BasketEventHandlerInterface $handler */ + $handler = $this->locator->get($event->getType()->value); + + try { + $cart = $basket->getEntity(); + $this->contextManager->changeContext($cart, [ + 'shop_id' => $shopId, + ]); + + $notice = $handler->handle($basket, $event); + + if (null === $notice || NoticeType::Error() !== $notice->getType()) { + $this->dispatcher->dispatch(new CartUpdatedEvent($cart)); + } + + return $notice; + } finally { + $this->contextManager->restoreContext(); + } + } + + private function createEventDispatcher(\Context $context): EventDispatcherInterface + { + @trigger_error(sprintf('Passing $context as the second argument for "%s::__construct()" is deprecated.', __CLASS__), E_USER_DEPRECATED); + + $dispatcher = new EventDispatcher(); + $dispatcher->addSubscriber(new UpdateCartRulesListener($context)); + + return new EventDispatcherAdapter($dispatcher); + } +} diff --git a/modules/inpostizi/src/MerchantApi/Handler/Basket/BasketEventHandlerInterface.php b/modules/inpostizi/src/MerchantApi/Handler/Basket/BasketEventHandlerInterface.php new file mode 100644 index 00000000..fec151b9 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Handler/Basket/BasketEventHandlerInterface.php @@ -0,0 +1,14 @@ +context = $context; + $this->configuration = $configuration; + $this->manager = $manager; + } + + public function __invoke(CreateCartCommand $command): Cart + { + if (null === $shopId = $command->getShopId()) { + $shop = $this->context->shop; + } elseif (null === $shop = $this->manager->getRepository(\Shop::class)->find($shopId)) { + throw new \DomainException(sprintf('Shop #%d does not exist.', $shopId)); + } + + $cart = new \Cart(); + $cart->id_shop = (int) $shop->id; + $cart->id_shop_group = (int) $shop->id_shop_group; + $cart->id_lang = $this->configuration->getDefaultLanguageId($shopId); + $cart->id_currency = (int) $this->getCurrency()->id; + + $this->manager->save($cart); + + return new Cart($cart); + } + + private function getCurrency(): \Currency + { + /** @var CurrencyRepository $repository */ + $repository = $this->manager->getRepository(\Currency::class); + $currency = $repository->findOneByIsoCode($isoCode = Currency::getDefault()->value); + + if (null === $currency) { + throw new \RuntimeException(sprintf('Currency "%s" does not exist.', $isoCode)); + } + + return $currency; + } +} diff --git a/modules/inpostizi/src/MerchantApi/Handler/Basket/CreateCartHandlerInterface.php b/modules/inpostizi/src/MerchantApi/Handler/Basket/CreateCartHandlerInterface.php new file mode 100644 index 00000000..e1081471 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Handler/Basket/CreateCartHandlerInterface.php @@ -0,0 +1,11 @@ +productRepository = $productRepository; + $this->translator = $translator; + $this->logger = $logger; + } + + public function __invoke(IncrementCartQuantityCommand $command): int + { + $quantity = $command->getQuantity(); + + if (0 >= $quantity) { + throw new \DomainException('Quantity must be greater than zero.'); + } + + if (!\Validate::isLoadedObject($cart = $command->getCart())) { + throw new \DomainException('Cart does not exist.'); + } + + $productId = $command->getProductId(); + $combinationId = $command->getCombinationId(); + $customizationId = $command->getCustomizationId(); + + if (0 === $currentQuantity = ProductHelper::getCartQuantity($cart, $productId, $combinationId, $customizationId)) { + throw new \DomainException('Product is not in cart.'); + } + + $this->assertQuantityIsAvailable($quantity, $productId, $combinationId, $cart); + $this->updateCartQuantity($cart, $productId, $combinationId, $customizationId, $quantity); + + return $currentQuantity + $quantity; + } + + private function updateCartQuantity(\Cart $cart, int $productId, int $combinationId, int $customizationId, int $quantity): void + { + try { + $result = $cart->updateQty( + $quantity, + $productId, + $combinationId, + $customizationId, + 'up', + 0, + null, + false + ); + } catch (\Exception $e) { + $result = false; + } + + if (false !== $result) { + return; + } + + $this->logger->critical('Could not increment [{productId}] quantity for cart #{cartId}.', [ + 'productId' => implode('-', [$productId, $combinationId, $customizationId]), + 'cartId' => $cart->id, + ]); + + throw $e ?? new CannotAddProductException($this->translator->l('Could not add the product to your cart.', RelatedProductsEventHandler::TRANSLATION_SOURCE)); + } + + private function assertQuantityIsAvailable(int $quantity, int $productId, int $combinationId, \Cart $cart): void + { + if ($this->productRepository->isAvailableOutOfStock($productId)) { + return; + } + + $availableQuantity = $this->productRepository->getAvailableQuantity($productId, $combinationId, $cart); + + if ($quantity > $availableQuantity) { + throw ProductOutOfStockException::create(max($availableQuantity, 0)); + } + } +} diff --git a/modules/inpostizi/src/MerchantApi/Handler/Basket/IncrementCartQuantityHandlerInterface.php b/modules/inpostizi/src/MerchantApi/Handler/Basket/IncrementCartQuantityHandlerInterface.php new file mode 100644 index 00000000..a25550a8 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Handler/Basket/IncrementCartQuantityHandlerInterface.php @@ -0,0 +1,13 @@ +module = $module; + $this->context = $context; + $this->manager = $manager; + $this->logger = $logger; + } + + public static function getHandledEventType(): string + { + return EventType::ProductsQuantity()->value; + } + + public function handle(BasketInterface $basket, BasketEvent $event): ?Notice + { + if (EventType::ProductsQuantity() !== $type = $event->getType()) { + throw new \DomainException(sprintf('Unsupported event type "%s".', $type->value)); + } + + $cart = $basket->getEntity(); + + if (!$cart instanceof \Cart) { + throw new \InvalidArgumentException(sprintf('Expected basket entity to be an instance of "%s", "%s" given.', \Cart::class, get_class($cart))); + } + + foreach ($event->getQuantityEventData() as $data) { + if (null === $referenceId = ReferenceId::fromString($data->getProductId())) { + throw MalformedRequestException::create(); + } + + $productId = $referenceId->getProductId(); + $combinationId = (int) $referenceId->getCombinationId(); + $customizationId = (int) $referenceId->getCustomizationId(); + $quantity = (int) $data->getQuantity()->getQuantity(); + + if (null !== $error = $this->updateCartQuantity($cart, $productId, $combinationId, $customizationId, $quantity)) { + return Notice::error($error); + } + } + + return null; + } + + /* @TODO: refactor to use {@see IncrementCartQuantityHandler} */ + private function updateCartQuantity(\Cart $cart, int $productId, int $combinationId, int $customizationId, int $quantity): ?string + { + $currentQuantity = ProductHelper::getCartQuantity($cart, $productId, $combinationId, $customizationId); + + if (0 === $currentQuantity) { + return 0 >= $quantity ? null : $this->module->l('Product is no longer in your cart.', self::TRANSLATION_SOURCE); + } + + if (0 >= $quantity) { + return $this->deleteProduct($cart, $productId, $combinationId, $customizationId); + } + + if (0 === $deltaQuantity = $quantity - $currentQuantity) { + return null; + } + + if (null !== $error = $this->checkMinimalQuantity($productId, $combinationId, $quantity, (int) $cart->id_lang)) { + return $error; + } + + $availableQuantity = $this->getAvailableQuantity($cart, $productId, $combinationId, $customizationId); + if (null !== $availableQuantity && $deltaQuantity > $availableQuantity) { + return $this->context->getTranslator()->trans('The available purchase order quantity for this product is %quantity%.', [ + '%quantity%' => $availableQuantity + $currentQuantity, + ], 'Shop.Notifications.Error'); + } + + try { + $result = $cart->updateQty( + abs($deltaQuantity), + $productId, + $combinationId, + $customizationId, + $deltaQuantity > 0 ? 'up' : 'down', + 0, + null, + false + ); + } catch (\Exception $e) { + $result = false; + $this->logger->critical('Quantity update error: {error}', [ + 'error' => $e, + ]); + } + + if (false === $result) { + isset($e) || $this->logger->critical('Could not update product [{productId}] quantity in cart #{cartId}.', [ + 'productId' => implode('-', [$productId, $combinationId, $customizationId]), + 'cartId' => $cart->id, + ]); + + return $this->module->l('Could not update product quantity.', self::TRANSLATION_SOURCE); + } + + return null; + } + + private function deleteProduct(\Cart $cart, int $productId, int $combinationId, int $customizationId): ?string + { + try { + $result = $cart->deleteProduct($productId, $combinationId, $customizationId); + } catch (\Exception $e) { + $result = false; + $this->logger->critical('Cart product deletion error: {error}', [ + 'error' => $e, + ]); + } + + if (false === $result) { + isset($e) || $this->logger->critical('Could not delete product [{productId}] from cart #{cartId}.', [ + 'productId' => implode('-', [$productId, $combinationId, $customizationId]), + 'cartId' => $cart->id, + ]); + + return $this->module->l('Could delete the product from your cart.', self::TRANSLATION_SOURCE); + } + + return null; + } + + private function checkMinimalQuantity(int $productId, int $combinationId, int $quantity, int $languageId): ?string + { + $product = $this->manager->getRepository(\Product::class)->find($productId, $languageId); + + if (null === $product) { + throw new \RuntimeException('Product does not exist'); + } + + $combination = 0 !== $combinationId + ? $this->manager->getRepository(\Combination::class)->find($combinationId) + : null; + + $minimalQuantity = null === $combination + ? $product->minimal_quantity + : $combination->minimal_quantity; + + if ($quantity >= $minimalQuantity) { + return null; + } + + return $this->context->getTranslator()->trans('The minimum purchase order quantity for the product %product% is %quantity%.', [ + '%product%' => $product->name, + '%quantity%' => $minimalQuantity, + ], 'Shop.Notifications.Error'); + } + + // TODO refactor static calls + private function getAvailableQuantity(\Cart $cart, int $productId, int $combinationId, int $customizationId): ?int + { + $outOfStock = \StockAvailable::outOfStock($productId); + if (\Product::isAvailableWhenOutOfStock($outOfStock)) { + return null; + } + + return \Product::getQuantity($productId, $combinationId, null, $cart, $customizationId); + } +} diff --git a/modules/inpostizi/src/MerchantApi/Handler/Basket/PromoCodesEventHandler.php b/modules/inpostizi/src/MerchantApi/Handler/Basket/PromoCodesEventHandler.php new file mode 100644 index 00000000..9a9b94e5 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Handler/Basket/PromoCodesEventHandler.php @@ -0,0 +1,147 @@ +module = $module; + $this->context = $context; + $this->manager = $manager; + $this->logger = $logger; + } + + public static function getHandledEventType(): string + { + return EventType::PromoCodes()->value; + } + + public function handle(BasketInterface $basket, BasketEvent $event): ?Notice + { + if (EventType::PromoCodes() !== $type = $event->getType()) { + throw new \DomainException(sprintf('Unsupported event type "%s".', $type->value)); + } + + $cart = $basket->getEntity(); + + if (!$cart instanceof \Cart) { + throw new \InvalidArgumentException(sprintf('Expected basket entity to be an instance of "%s", "%s" given.', \Cart::class, get_class($cart))); + } + + $cartRules = $cart->getCartRules(\CartRule::FILTER_ACTION_ALL, false); + $currentCodes = array_map(static function (array $cartRule): string { + return $cartRule['code'] ?: $cartRule['name']; + }, $cartRules); + + $appliedCodes = []; + + foreach ($event->getPromoCodesEventData() as $promoCode) { + $code = trim($promoCode->getCode()); + + if ( + !in_array($code, $currentCodes, true) + && null !== $error = $this->addCartRule($cart, $code) + ) { + return Notice::error($error); + } + + $appliedCodes[] = $code; + } + + foreach ($cartRules as $cartRule) { + if ('' === $cartRule['code']) { + continue; + } + + if (!in_array($cartRule['code'], $appliedCodes, true)) { + $cart->removeCartRule($cartRule['id_cart_rule']); + } + } + + return Notice::attention($this->module->l('Voucher has been activated.', self::TRANSLATION_SOURCE)); + } + + private function addCartRule(\Cart $cart, string $code): ?string + { + if ('' === $code) { + return $this->context->getTranslator()->trans('You must enter a voucher code.', [], 'Shop.Notifications.Error'); + } + + if (!\Validate::isCleanHtml($code)) { + return $this->context->getTranslator()->trans('The voucher code is invalid.', [], 'Shop.Notifications.Error'); + } + + $cartRule = $this->manager->getRepository(\CartRule::class)->findOneBy([ + 'code' => $code, + 'id_lang' => (int) $cart->id_lang, + ]); + + if (null === $cartRule) { + return $this->context->getTranslator()->trans('This voucher does not exist.', [], 'Shop.Notifications.Error'); + } + + $context = $this->context->cloneContext(); + $context->cart = $cart; + + if ($error = $cartRule->checkValidity($context)) { + return $error; + } + + try { + $result = $cart->addCartRule($cartRule->id); + } catch (\Exception $e) { + $result = false; + $this->logger->critical('Promo code addition error: {error}', [ + 'error' => $e, + ]); + } + + if (!$result) { + isset($e) || $this->logger->critical('Could not add promo code "{code}" to cart #{cartId}.', [ + 'code' => $code, + 'cartId' => $cart->id, + ]); + + return $this->module->l('Could not add the voucher to your cart.', self::TRANSLATION_SOURCE); + } + + $this->logger->info('Applied promo code "{code}" to cart #{cartId}.', [ + 'code' => $code, + 'cartId' => $cart->id, + ]); + + return null; + } +} diff --git a/modules/inpostizi/src/MerchantApi/Handler/Basket/RelatedProductsEventHandler.php b/modules/inpostizi/src/MerchantApi/Handler/Basket/RelatedProductsEventHandler.php new file mode 100644 index 00000000..263c0349 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Handler/Basket/RelatedProductsEventHandler.php @@ -0,0 +1,146 @@ +context = $context; + + if ($bus instanceof \Module) { + $args = func_get_args(); + $bus = $this->createCommandBus($context, $bus, $args[2], $args[3]); + } + + if (!$bus instanceof CommandBusInterface) { + throw new \InvalidArgumentException(sprintf('Expected parameter $bus to be an instance of "%s", "%s" given.', CommandBusInterface::class, get_debug_type($bus))); + } + + $this->bus = $bus; + } + + public static function getHandledEventType(): string + { + return EventType::RelatedProducts()->value; + } + + public function handle(BasketInterface $basket, BasketEvent $event): ?Notice + { + if (EventType::RelatedProducts() !== $type = $event->getType()) { + throw new \DomainException(sprintf('Unsupported event type "%s".', $type->value)); + } + + $cart = $basket->getEntity(); + + if (!$cart instanceof \Cart) { + throw new \InvalidArgumentException(sprintf('Expected basket entity to be an instance of "%s", "%s" given.', \Cart::class, get_class($cart))); + } + + foreach ($event->getRelatedProductsEventData() as $relatedProduct) { + try { + $this->addRelatedProduct($cart, $relatedProduct); + } catch (ProductAlreadyInCartException $e) { + // ignore silently + } catch (ProductNotFoundException $e) { + return Notice::error($this->context->getTranslator()->trans('Product not found', [], 'Shop.Notifications.Error')); + } catch (ProductOutOfStockException $e) { + return Notice::error($this->context->getTranslator()->trans('The available purchase order quantity for this product is %quantity%.', [ + '%quantity%' => $e->getAvailableQuantity(), + ], 'Shop.Notifications.Error')); + } catch (CannotAddProductException $e) { + return Notice::error($e->getMessage()); + } + } + + return null; + } + + private function addRelatedProduct(\Cart $cart, RelatedProductData $relatedProduct): void + { + $referenceId = ReferenceId::fromString($relatedProduct->getProductId()); + + if (null === $referenceId) { + throw ProductNotFoundException::create(); + } + + if ($referenceId->hasCustomization()) { + throw new MalformedRequestException('Adding customizable products is not supported for related products.'); + } + + $productId = $referenceId->getProductId(); + $combinationId = $referenceId->getCombinationId(); + $quantity = (int) $relatedProduct->getQuantity()->getQuantity(); + + $this->bus->handle(new AddProductToCartCommand($cart, $productId, $combinationId, $quantity)); + } + + private function createCommandBus(\Context $context, \Module $module, ObjectManagerInterface $manager, LoggerInterface $logger): CommandBusInterface + { + @trigger_error(sprintf('Passing $module, $manager, and $logger as arguments for "%s::__construct()" is deprecated.', __CLASS__), E_USER_DEPRECATED); + + $handler = new AddProductToCartHandler( + $context, + $manager->getRepository(\Product::class), + new LegacyTranslator($module->name), + $logger + ); + + return new class($handler) implements CommandBusInterface { + /** + * @var callable + */ + private $handler; + + public function __construct(callable $handler) + { + $this->handler = $handler; + } + + public function handle($command) + { + return ($this->handler)($command); + } + }; + } +} diff --git a/modules/inpostizi/src/MerchantApi/Handler/ConfirmBasketBindingHandler.php b/modules/inpostizi/src/MerchantApi/Handler/ConfirmBasketBindingHandler.php new file mode 100644 index 00000000..c60006c0 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Handler/ConfirmBasketBindingHandler.php @@ -0,0 +1,55 @@ +repository = $repository; + $this->builderFactory = $builderFactory; + } + + public static function getHandledCommandClass(): string + { + return ConfirmBasketBindingCommand::class; + } + + public function __invoke(ConfirmBasketBindingCommand $command): Basket + { + if (null === $session = $this->repository->findByBasketId($command->getBasketId())) { + throw BasketNotFoundException::create(); + } + + if (BasketSession::isFinalized($session)) { + throw OrderExistsException::create(); + } + + $session->setBindingConfirmation($command->getConfirmation()); + $this->repository->persist($session); + + return $this->builderFactory + ->createResponseBuilder($session->getBasket(), $session->getShopId()) + ->build(); + } +} diff --git a/modules/inpostizi/src/MerchantApi/Handler/ConfirmBasketBindingHandlerInterface.php b/modules/inpostizi/src/MerchantApi/Handler/ConfirmBasketBindingHandlerInterface.php new file mode 100644 index 00000000..f87c4b6e --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Handler/ConfirmBasketBindingHandlerInterface.php @@ -0,0 +1,13 @@ + + */ + private $repository; + + /** + * @var CommandBusInterface + */ + private $bus; + + /** + * @param BasketSessionRepositoryInterface $repository + */ + public function __construct(BasketSessionRepositoryInterface $repository, CommandBusInterface $bus) + { + $this->repository = $repository; + $this->bus = $bus; + } + + public function __invoke(CreateOrderCommand $command): Order + { + $handler = new Create(null, null, null, null, $this->bus, $this->repository); + $orderId = $handler->handleRequest($command->getRequest()); + + return $this->bus->handle(new GetOrderCommand((string) $orderId)); + } +} diff --git a/modules/inpostizi/src/MerchantApi/Handler/CreateOrderHandlerInterface.php b/modules/inpostizi/src/MerchantApi/Handler/CreateOrderHandlerInterface.php new file mode 100644 index 00000000..9d15bf36 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Handler/CreateOrderHandlerInterface.php @@ -0,0 +1,16 @@ +repository = $repository; + } + + public static function getHandledCommandClass(): string + { + return DeleteBasketBindingCommand::class; + } + + public function __invoke(DeleteBasketBindingCommand $command) + { + if (null === $session = $this->repository->findByBasketId($command->getBasketId())) { + return; + } + + $session->unbind(); + $this->repository->persist($session); + } +} diff --git a/modules/inpostizi/src/MerchantApi/Handler/DeleteBasketBindingHandlerInterface.php b/modules/inpostizi/src/MerchantApi/Handler/DeleteBasketBindingHandlerInterface.php new file mode 100644 index 00000000..87209a32 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Handler/DeleteBasketBindingHandlerInterface.php @@ -0,0 +1,12 @@ +repository = $repository; + $this->builderFactory = $builderFactory; + } + + public static function getHandledCommandClass(): string + { + return GetBasketCommand::class; + } + + public function __invoke(GetBasketCommand $command): Basket + { + if (null === $session = $this->repository->findByBasketId($command->getBasketId())) { + throw BasketNotFoundException::create(); + } + + return $this->builderFactory + ->createResponseBuilder($session->getBasket(), $session->getShopId()) + ->build(); + } +} diff --git a/modules/inpostizi/src/MerchantApi/Handler/GetBasketHandlerInterface.php b/modules/inpostizi/src/MerchantApi/Handler/GetBasketHandlerInterface.php new file mode 100644 index 00000000..63580cea --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Handler/GetBasketHandlerInterface.php @@ -0,0 +1,13 @@ + + */ + private $orderRepository; + + /** + * @var BasketSessionRepositoryInterface + */ + private $sessionRepository; + + /** + * @var BasketAnalyticsRepositoryInterface + */ + private $analyticsRepository; + + /** + * @param ObjectRepositoryInterface<\Order> $orderRepository + * @param BasketSessionRepositoryInterface $repository + * @param BasketAnalyticsRepositoryInterface|null $analyticsRepository + */ + public function __construct( + ObjectRepositoryInterface $orderRepository, + BasketSessionRepositoryInterface $repository, + ?BasketAnalyticsRepositoryInterface $analyticsRepository = null + ) { + $this->orderRepository = $orderRepository; + $this->sessionRepository = $repository; + $this->analyticsRepository = $analyticsRepository ?? new \izi\prestashop\Analytics\BasketAnalyticsRepository(new \izi\prestashop\Database\Connection()); + } + + public function __invoke(GetOrderCommand $command): Order + { + $order = $this->orderRepository->find((int) $command->getOrderId()); + + if (null === $order || 'inpostizi' !== $order->module) { + throw OrderNotFoundException::create(); + } + + if (null === $session = $this->sessionRepository->findByOrderId((string) $order->id)) { + throw new \RuntimeException('Basket session does not exist.'); + } + + \Shop::setContext(\Shop::CONTEXT_SHOP, (int) $order->id_shop); + + return PrestashopOrder::getOrder($order, $session->getBasketId(), $session->getOrderRequest(), $this->analyticsRepository->find((int) $order->id_cart)); + } +} diff --git a/modules/inpostizi/src/MerchantApi/Handler/GetOrderHandlerInterface.php b/modules/inpostizi/src/MerchantApi/Handler/GetOrderHandlerInterface.php new file mode 100644 index 00000000..4a4457c4 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Handler/GetOrderHandlerInterface.php @@ -0,0 +1,13 @@ + API products by ID + */ + private $apiProducts = []; + + /** + * @param ProductRepository $productRepository + */ + public function __construct(HotProductRepositoryInterface $repository, HotProductDataMapperInterface $dataMapper, ProductsApiClientInterface $client, CacheInterface $cache, ObjectRepositoryInterface $productRepository) + { + $this->repository = $repository; + $this->dataMapper = $dataMapper; + $this->client = $client; + $this->cache = $cache; + $this->productRepository = $productRepository; + } + + public function __invoke(GetProductsCommand $command): Products + { + $shopId = $command->getShopId(); + $productIds = $command->getProductIds(); + $pageIndex = $command->getPageIndex() ?? 0; + $pageSize = $command->getPageSize() ?? self::DEFAULT_PAGE_SIZE; + + return $productIds + ? $this->getProductsByIds($productIds, $shopId, $pageIndex, $pageSize) + : $this->getAllProducts($shopId, $pageIndex, $pageSize); + } + + private function getProductsByIds(array $productIds, int $shopId, int $pageIndex, int $pageSize): Products + { + // filter out invalid reference IDs + $referenceIds = array_filter(array_map([ReferenceId::class, 'fromString'], $productIds)); + natsort($referenceIds); + + $offset = $pageIndex * $pageSize; + + $totalCount = $this->repository->countBy($shopId, $referenceIds); + $products = 0 !== $totalCount && $offset < $totalCount + ? $this->repository->findBy($shopId, $pageSize, $offset, $referenceIds) + : []; + + $responseProducts = array_map([$this, 'mapProductData'], $products); + + if ($totalCount === count($referenceIds)) { + return new Products($responseProducts, $totalCount, $pageIndex, $pageSize); + } + + $knownIdsMap = array_flip(array_map(static function (IdentifiableProduct $product): string { + return $product->getId(); + }, $responseProducts)); + + $missingIds = array_filter($referenceIds, static function (ReferenceId $referenceId) use ($knownIdsMap): bool { + return !isset($knownIdsMap[(string) $referenceId]); + }); + + $importableIds = $this->getImportableProductIds($shopId, $missingIds); + + return $this->appendApiProductData($responseProducts, $totalCount, $importableIds, $pageIndex, $pageSize); + } + + private function getAllProducts(int $shopId, int $pageIndex, int $pageSize): Products + { + $offset = $pageIndex * $pageSize; + + $totalCount = $this->repository->countBy($shopId); + $products = 0 !== $totalCount && $offset < $totalCount + ? $this->repository->findBy($shopId, $pageSize, $offset) + : []; + + $responseProducts = array_map([$this->dataMapper, 'map'], $products); + $importableIds = $this->getImportableProductIds($shopId); + + return $this->appendApiProductData($responseProducts, $totalCount, $importableIds, $pageIndex, $pageSize); + } + + private function appendApiProductData(array $responseProducts, int $totalCount, array $importableIds, int $pageIndex, int $pageSize): Products + { + if ([] === $importableIds) { + return new Products($responseProducts, $totalCount, $pageIndex, $pageSize); + } + + $offset = $pageIndex * $pageSize; + $totalCount += count($importableIds); + + $count = count($responseProducts); + + if ($count === $pageSize || $offset > $totalCount) { + return new Products($responseProducts, $totalCount, $pageIndex, $pageSize); + } + + $productIds = array_slice($importableIds, $offset - $count, $pageSize - $count); + $apiProducts = $this->fetchApiProducts($productIds); + + $responseProducts = array_merge( + $responseProducts, + array_map([$this, 'mapApiProductData'], $apiProducts) + ); + + return new Products($responseProducts, $totalCount, $pageIndex, $pageSize); + } + + private function mapProductData(HotProduct $product): IdentifiableProduct + { + return $this->dataMapper + ->map($product) + ->asIdentifiable((string) $product->getReferenceId()); + } + + private function mapApiProductData(Product $apiProduct): IdentifiableProduct + { + $referenceId = ReferenceId::fromString($apiProduct->getId()); + assert(null !== $referenceId); + + $availability = $apiProduct->getAvailability() ?? new ProductAvailability(); + $product = new HotProduct( + $referenceId->getProductId(), + $referenceId->getCombinationId(), + null, + $availability->getStartDate(), + $availability->getEndDate() + ); + + return $this->dataMapper + ->map($product) + ->asIdentifiable($apiProduct->getId()); + } + + /** + * @param string[] $productIds + * + * @return Product[] + */ + private function fetchApiProducts(array $productIds): array + { + $result = []; + + foreach ($productIds as $key => $productId) { + if (!isset($this->apiProducts[$productId])) { + continue; + } + + unset($productIds[$key]); + $result[] = $this->apiProducts[$productId]; + } + + if ([] === $productIds) { + return $result; + } + + $apiProducts = $this->client->getProducts($productIds); + + return array_merge($result, iterator_to_array($apiProducts)); + } + + /** + * @param ReferenceId[] $referenceIds + * + * @return string[] + */ + private function getImportableProductIds(int $shopId, array $referenceIds = []): array + { + $idsMap = $this->cache->get(self::IMPORTABLE_CACHE_KEY); + $referenceIds = array_map(static function (ReferenceId $referenceId): string { + return (string) $referenceId; + }, $referenceIds); + + if (null === $idsMap || [] !== array_diff($referenceIds, $this->getNormalizedIds($idsMap))) { + $idsMap = $this->createImportableProductIdsMap($shopId); + + $this->cache->set(self::IMPORTABLE_CACHE_KEY, $idsMap, self::CACHE_TTL); + } + + $importableIds = array_keys(array_filter($idsMap)); + + if ([] === $referenceIds) { + return $importableIds; + } + + return array_intersect($importableIds, $referenceIds); + } + + private function getNormalizedIds(array $idsMap): array + { + $ids = array_keys($idsMap); + + return array_map([ReferenceId::class, 'fromString'], $ids); + } + + /** + * @return array + */ + private function createImportableProductIdsMap(int $shopId): array + { + $map = []; + + foreach ($this->client->getProducts() as $apiProduct) { + $productId = $apiProduct->getId(); + $map[$productId] = $this->isImportable($productId, $shopId); + $this->apiProducts[$productId] = $apiProduct; + } + + return $map; + } + + private function isImportable(string $productId, int $shopId): bool + { + if (null === $referenceId = ReferenceId::fromString($productId)) { + return false; + } + + if ($referenceId->hasCustomization()) { + return false; + } + + if (null !== $this->repository->findOneByReferenceId($productId)) { + return false; + } + + $product = $this->productRepository->findWithCombination( + $referenceId->getProductId(), + $referenceId->getCombinationId(), + null, + $shopId, + true + ); + + return null !== $product; + } +} diff --git a/modules/inpostizi/src/MerchantApi/Handler/GetProductsHandlerInterface.php b/modules/inpostizi/src/MerchantApi/Handler/GetProductsHandlerInterface.php new file mode 100644 index 00000000..dce904b2 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Handler/GetProductsHandlerInterface.php @@ -0,0 +1,13 @@ +manager = $manager; + $this->configuration = $configuration; + $this->formatter = $formatter; + } + + public function __invoke(UpdateCartMessageCommand $command): void + { + $cart = $command->getCart(); + + if (0 >= $cartId = (int) $cart->id) { + return; + } + + $message = $this->findOrCreateMessage($cartId); + $message->message = $this->formatMessage($command->getRequest()); + + if ('' === $message->message) { + $this->manager->remove($message); + + return; + } + + $this->manager->save($message); + } + + private function formatMessage(CreateOrderRequest $request): string + { + $message = $this->configuration->getMessageFormat(); + + return $this->formatter->format($message, $request); + } + + private function findOrCreateMessage(int $cartId): \Message + { + $message = $this->manager->getRepository(\Message::class)->findOneBy([ + 'id_cart' => $cartId, + ]); + + if (null !== $message) { + return $message; + } + + $message = new \Message(); + $message->id_cart = $cartId; + + return $message; + } +} diff --git a/modules/inpostizi/src/MerchantApi/Handler/Order/UpdateCartMessageHandlerInterface.php b/modules/inpostizi/src/MerchantApi/Handler/Order/UpdateCartMessageHandlerInterface.php new file mode 100644 index 00000000..f86f2973 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Handler/Order/UpdateCartMessageHandlerInterface.php @@ -0,0 +1,12 @@ +repository = $repository; + $this->eventHandler = $eventHandler; + $this->builderFactory = $builderFactory; + } + + public static function getHandledCommandClass(): string + { + return UpdateBasketCommand::class; + } + + public function __invoke(UpdateBasketCommand $command): Basket + { + if (null === $session = $this->repository->findByBasketId($command->getBasketId())) { + throw BasketNotFoundException::create(); + } + + if (BasketSession::isFinalized($session)) { + throw OrderExistsException::create(); + } + + $event = $command->getEvent(); + $notice = $this->eventHandler->handle($session->getBasket(), $event, $session->getShopId()); + + $session->updatedBy($event); + $this->repository->persist($session); + + return $this->builderFactory + ->createResponseBuilder($session->getBasket(), $session->getShopId()) + ->setNotice($notice) + ->build(); + } +} diff --git a/modules/inpostizi/src/MerchantApi/Handler/UpdateBasketHandlerInterface.php b/modules/inpostizi/src/MerchantApi/Handler/UpdateBasketHandlerInterface.php new file mode 100644 index 00000000..79683d50 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Handler/UpdateBasketHandlerInterface.php @@ -0,0 +1,13 @@ + + */ + private $repository; + + /** + * @var OrdersConfigurationInterface + */ + private $configuration; + + /** + * @var OrderStatusDescriptionProvider + */ + private $statusDescriptionProvider; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @param ObjectRepositoryInterface<\Order> $repository + */ + public function __construct(ObjectRepositoryInterface $repository, OrdersConfigurationInterface $configuration, OrderStatusDescriptionProvider $statusDescriptionProvider, LoggerInterface $logger) + { + $this->repository = $repository; + $this->configuration = $configuration; + $this->statusDescriptionProvider = $statusDescriptionProvider; + $this->logger = $logger; + } + + public static function getHandledCommandClass(): string + { + return UpdateOrderCommand::class; + } + + public function __invoke(UpdateOrderCommand $command): OrderStatusData + { + $order = $this->repository->find((int) $command->getOrderId()); + + if (null === $order || 'inpostizi' !== $order->module) { + throw OrderNotFoundException::create(); + } + + $event = $command->getEvent(); + + $this->updateOrderStatus($order, $event); + $this->saveTransactionId($order, $event); + + return new OrderStatusData( + null, + $this->statusDescriptionProvider->getStatus($order) + ); + } + + private function updateOrderStatus(\Order $order, OrderEvent $event): void + { + if (PaymentStatus::Authorized() !== $event->getData()->getPaymentStatus()) { + return; + } + + $statusId = $this->configuration->getPaidStatusId((int) $order->id_shop); + if (0 >= $statusId || $statusId === (int) $order->current_state) { + return; + } + + $order->setCurrentState($statusId); + + $this->logger->info('Updated order #{orderId} status to #{statusId}.', [ + 'orderId' => $order->id, + 'statusId' => $statusId, + ]); + } + + private function saveTransactionId(\Order $order, OrderEvent $event): void + { + if (null === $transactionId = $event->getData()->getPaymentId()) { + return; + } + + /** + * @var \OrderPayment[] $payments + */ + if ([] === $payments = $order->getOrderPayments()) { + $this->createOrderPayment($order, $transactionId); + + return; + } + + foreach ($payments as $payment) { + if ($payment->transaction_id === $transactionId || '' !== (string) $payment->transaction_id) { + continue; + } + + $payment->transaction_id = $transactionId; + + if (!$payment->update()) { + throw new \RuntimeException('Could not update order payment.'); + } + } + } + + private function createOrderPayment(\Order $order, string $transactionId): void + { + // refresh order data in case it has been updated using another Order object in hooks triggered by status change + $order = $this->repository->find((int) $order->id); + + // this also causes Order update! + if (!$order->addOrderPayment($order->total_paid, null, $transactionId)) { + throw new \RuntimeException('Could not create order payment.'); + } + + $this->logger->info('Created payment for order #{orderId}.', [ + 'orderId' => $order->id, + ]); + } +} diff --git a/modules/inpostizi/src/MerchantApi/Handler/UpdateOrderHandlerInterface.php b/modules/inpostizi/src/MerchantApi/Handler/UpdateOrderHandlerInterface.php new file mode 100644 index 00000000..ad045947 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Handler/UpdateOrderHandlerInterface.php @@ -0,0 +1,13 @@ +event_id = $event_id; + $this->event_data_time = $event_data_time; + $this->event_type = $event_type; + $this->phone_number = $phone_number; + $this->quantity_event_data = $quantity_event_data; + $this->promo_codes_event_data = $promo_codes_event_data; + $this->related_products_event_data = $related_products_event_data; + } + + public function getId(): string + { + return $this->event_id; + } + + public function getDateTime(): \DateTimeImmutable + { + return $this->event_data_time; + } + + public function getType(): EventType + { + return $this->event_type; + } + + public function getPhoneNumber(): ?PhoneNumber + { + return $this->phone_number; + } + + /** + * @return QuantityData[] + */ + public function getQuantityEventData(): array + { + return $this->quantity_event_data; + } + + /** + * @return PromoCode[] + */ + public function getPromoCodesEventData(): array + { + return $this->promo_codes_event_data; + } + + /** + * @return RelatedProductData[] + */ + public function getRelatedProductsEventData(): array + { + return $this->related_products_event_data; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/MerchantApi/Model/Basket/Request/BasketId.php b/modules/inpostizi/src/MerchantApi/Model/Basket/Request/BasketId.php new file mode 100644 index 00000000..18d0e251 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Model/Basket/Request/BasketId.php @@ -0,0 +1,41 @@ +basket_id = $basket_id; + $this->phone_number = $phone_number; + } + + public function getBasketId(): ?string + { + return $this->basket_id; + } + + public function getPhoneNumber(): ?PhoneNumber + { + return $this->phone_number; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/MerchantApi/Model/Basket/Request/BindingConfirmation.php b/modules/inpostizi/src/MerchantApi/Model/Basket/Request/BindingConfirmation.php new file mode 100644 index 00000000..51e33409 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Model/Basket/Request/BindingConfirmation.php @@ -0,0 +1,130 @@ +status = $status; + $this->inpost_basket_id = $inpost_basket_id; + $this->phone_number = $phone_number; + $this->browser = $browser; + $this->masked_phone_number = $masked_phone_number; + $this->name = $name; + $this->surname = $surname; + } + + public static function fromLinkedBasketBindingResponse(BasketBindingResponse $binding, ?string $browserId = null): self + { + if (!$binding->isBasketLinked()) { + throw new \DomainException('Basket is not linked.'); + } + + return self::success($binding, $browserId); + } + + public function getStatus(): BindingStatus + { + return $this->status; + } + + public function getInPostBasketId(): ?string + { + return $this->inpost_basket_id; + } + + public function getPhoneNumber(): ?PhoneNumber + { + return $this->phone_number; + } + + public function getBrowser(): ?Browser + { + return $this->browser; + } + + public function getMaskedPhoneNumber(): ?string + { + return $this->masked_phone_number; + } + + public function getName(): ?string + { + return $this->name; + } + + public function getSurname(): ?string + { + return $this->surname; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } + + public function __toString(): string + { + return json_encode($this); + } + + private static function success(BasketBindingResponse $binding, ?string $browserId = null): self + { + if (null === $clientDetails = $binding->getClientDetails()) { + throw new \UnexpectedValueException('Client details are missing in the binding data.'); + } + + $browser = new Browser($binding->isBrowserTrusted(), $browserId); + + return new self( + BindingStatus::Success(), + $binding->getInPostBasketId(), + $clientDetails->getPhoneNumber(), + $browser, + $clientDetails->getMaskedPhoneNumber(), + $clientDetails->getName(), + $clientDetails->getSurname() + ); + } +} diff --git a/modules/inpostizi/src/MerchantApi/Model/Basket/Request/BindingStatus.php b/modules/inpostizi/src/MerchantApi/Model/Basket/Request/BindingStatus.php new file mode 100644 index 00000000..8f69e212 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Model/Basket/Request/BindingStatus.php @@ -0,0 +1,17 @@ +browser_trusted = $browser_trusted; + $this->browser_id = $browser_id; + } + + public function isTrusted(): bool + { + return $this->browser_trusted; + } + + public function getId(): ?string + { + return $this->browser_id; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/MerchantApi/Model/Basket/Request/EventType.php b/modules/inpostizi/src/MerchantApi/Model/Basket/Request/EventType.php new file mode 100644 index 00000000..0f734c80 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Model/Basket/Request/EventType.php @@ -0,0 +1,19 @@ +quantity = $quantity; + } + + /** + * @return int|float + */ + public function getQuantity() + { + return $this->quantity; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/MerchantApi/Model/Basket/Request/QuantityData.php b/modules/inpostizi/src/MerchantApi/Model/Basket/Request/QuantityData.php new file mode 100644 index 00000000..4ea5ee2d --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Model/Basket/Request/QuantityData.php @@ -0,0 +1,39 @@ +product_id = $product_id; + $this->quantity = $quantity; + } + + public function getProductId(): string + { + return $this->product_id; + } + + public function getQuantity(): Quantity + { + return $this->quantity; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/MerchantApi/Model/Basket/Request/RelatedProductData.php b/modules/inpostizi/src/MerchantApi/Model/Basket/Request/RelatedProductData.php new file mode 100644 index 00000000..db1be4e4 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Model/Basket/Request/RelatedProductData.php @@ -0,0 +1,50 @@ +product_id = $product_id; + $this->quantity = $quantity; + $this->ean = $ean; + } + + public function getProductId(): string + { + return $this->product_id; + } + + public function getQuantity(): Quantity + { + return $this->quantity; + } + + public function getEan(): ?string + { + return $this->ean; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/MerchantApi/Model/Basket/Response/Basket.php b/modules/inpostizi/src/MerchantApi/Model/Basket/Response/Basket.php new file mode 100644 index 00000000..97ac1cc9 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Model/Basket/Response/Basket.php @@ -0,0 +1,50 @@ +summary = $summary; + $this->delivery = $delivery; + $this->promo_codes = $promo_codes; + $this->products = $products; + $this->related_products = $related_products; + $this->consents = $consents; + $this->promotions_available = $promotions_available; + } + + public function asIdentifiable(string $id): IdentifiableBasket + { + return new IdentifiableBasket( + $id, + $this->summary, + $this->delivery, + $this->products, + $this->consents, + $this->promo_codes, + $this->related_products, + $this->promotions_available + ); + } +} diff --git a/modules/inpostizi/src/MerchantApi/Model/Basket/Response/BasketTrait.php b/modules/inpostizi/src/MerchantApi/Model/Basket/Response/BasketTrait.php new file mode 100644 index 00000000..e7ba4203 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Model/Basket/Response/BasketTrait.php @@ -0,0 +1,114 @@ +summary; + } + + /** + * @return DeliveryOption[] + */ + public function getDelivery(): array + { + return $this->delivery; + } + + /** + * @return PromoCode[] + */ + public function getPromoCodes(): array + { + return $this->promo_codes; + } + + /** + * @return Product[] + */ + public function getProducts(): array + { + return $this->products; + } + + /** + * @return Product[] + */ + public function getRelatedProducts(): array + { + return $this->related_products; + } + + /** + * @return Consent[] + */ + public function getConsents(): array + { + return $this->consents; + } + + /** + * @return AvailablePromotion[] + */ + public function getAvailablePromotions(): array + { + return $this->promotions_available; + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/MerchantApi/Model/Basket/Response/IdentifiableBasket.php b/modules/inpostizi/src/MerchantApi/Model/Basket/Response/IdentifiableBasket.php new file mode 100644 index 00000000..22713dd7 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Model/Basket/Response/IdentifiableBasket.php @@ -0,0 +1,47 @@ +basket_id = $basket_id; + $this->summary = $summary; + $this->delivery = $delivery; + $this->promo_codes = $promo_codes; + $this->products = $products; + $this->related_products = $related_products; + $this->consents = $consents; + $this->promotions_available = $promotions_available; + } + + public function getId(): string + { + return $this->basket_id; + } +} diff --git a/modules/inpostizi/src/MerchantApi/Model/Order/Request/AccountInfo.php b/modules/inpostizi/src/MerchantApi/Model/Order/Request/AccountInfo.php new file mode 100644 index 00000000..e53ddd4d --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Model/Order/Request/AccountInfo.php @@ -0,0 +1,74 @@ +name = $name; + $this->surname = $surname; + $this->phone_number = $phone_number; + $this->mail = $mail; + $this->client_address = $client_address; + } + + public function getName(): string + { + return $this->name; + } + + public function getSurname(): string + { + return $this->surname; + } + + public function getPhoneNumber(): PhoneNumber + { + return $this->phone_number; + } + + public function getEmail(): string + { + return $this->mail; + } + + public function getAddress(): ClientAddress + { + return $this->client_address; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/MerchantApi/Model/Order/Request/AddressDetails.php b/modules/inpostizi/src/MerchantApi/Model/Order/Request/AddressDetails.php new file mode 100644 index 00000000..dae794ba --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Model/Order/Request/AddressDetails.php @@ -0,0 +1,52 @@ +street = $street; + $this->building = $building; + $this->flat = $flat; + } + + public function getStreet(): ?string + { + return $this->street; + } + + public function getBuilding(): ?string + { + return $this->building; + } + + public function getFlat(): ?string + { + return $this->flat; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/MerchantApi/Model/Order/Request/ClientAddress.php b/modules/inpostizi/src/MerchantApi/Model/Order/Request/ClientAddress.php new file mode 100644 index 00000000..583f7c78 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Model/Order/Request/ClientAddress.php @@ -0,0 +1,72 @@ +country_code = $country_code; + $this->address = $address; + $this->city = $city; + $this->postal_code = $postal_code; + $this->address_details = $address_details; + } + + public function getCountryCode(): string + { + return $this->country_code; + } + + public function getAddress(): string + { + return $this->address; + } + + public function getCity(): string + { + return $this->city; + } + + public function getPostalCode(): string + { + return $this->postal_code; + } + + public function getAddressDetails(): ?AddressDetails + { + return $this->address_details; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/MerchantApi/Model/Order/Request/CreateOrderRequest.php b/modules/inpostizi/src/MerchantApi/Model/Order/Request/CreateOrderRequest.php new file mode 100644 index 00000000..30f2f2e6 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Model/Order/Request/CreateOrderRequest.php @@ -0,0 +1,89 @@ +order_details = $order_details; + $this->account_info = $account_info; + $this->invoice_details = $invoice_details; + $this->delivery = $delivery; + $this->consents = $consents; + } + + public function getOrderDetails(): OrderDetails + { + return $this->order_details; + } + + public function getAccountInfo(): AccountInfo + { + return $this->account_info; + } + + public function getInvoiceDetails(): ?InvoiceDetails + { + return $this->invoice_details; + } + + public function getDelivery(): Delivery + { + return $this->delivery; + } + + public function getConsents(): array + { + return $this->consents; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } + + /** + * Create a new request with delivery emails updated based on the given delivery details + */ + public function withDeliveryEmails(Delivery $delivery): self + { + $request = clone $this; + $request->delivery = $this->delivery->withEmail($delivery->getEmail()); + + return $request; + } +} diff --git a/modules/inpostizi/src/MerchantApi/Model/Order/Request/Delivery.php b/modules/inpostizi/src/MerchantApi/Model/Order/Request/Delivery.php new file mode 100644 index 00000000..2fde4613 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Model/Order/Request/Delivery.php @@ -0,0 +1,112 @@ +delivery_type = $delivery_type; + $this->delivery_codes = $delivery_codes; + $this->mail = $mail; + $this->phone_number = $phone_number; + $this->delivery_point = $delivery_point; + $this->delivery_address = $delivery_address; + $this->courier_note = $courier_note; + } + + public function getType(): DeliveryType + { + return $this->delivery_type; + } + + /** + * @return ServiceCode[] + */ + public function getOptionalServiceCodes(): array + { + return $this->delivery_codes; + } + + public function getEmail(): ?string + { + return $this->mail; + } + + public function withEmail(?string $email): self + { + $delivery = clone $this; + $delivery->mail = $email; + + return $delivery; + } + + public function getPhoneNumber(): ?PhoneNumber + { + return $this->phone_number; + } + + public function getPoint(): ?string + { + return $this->delivery_point; + } + + public function getAddress(): ?DeliveryAddress + { + return $this->delivery_address; + } + + public function getCourierNote(): ?string + { + return $this->courier_note; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/MerchantApi/Model/Order/Request/DeliveryAddress.php b/modules/inpostizi/src/MerchantApi/Model/Order/Request/DeliveryAddress.php new file mode 100644 index 00000000..79afa8a1 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Model/Order/Request/DeliveryAddress.php @@ -0,0 +1,83 @@ +name = $name; + $this->country_code = $country_code; + $this->address = $address; + $this->city = $city; + $this->postal_code = $postal_code; + $this->address_details = $address_details; + } + + public function getName(): string + { + return $this->name; + } + + public function getCountryCode(): string + { + return $this->country_code; + } + + public function getAddress(): string + { + return $this->address; + } + + public function getCity(): string + { + return $this->city; + } + + public function getPostalCode(): string + { + return $this->postal_code; + } + + public function getAddressDetails(): ?AddressDetails + { + return $this->address_details; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/MerchantApi/Model/Order/Request/EventData.php b/modules/inpostizi/src/MerchantApi/Model/Order/Request/EventData.php new file mode 100644 index 00000000..1b9ac41f --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Model/Order/Request/EventData.php @@ -0,0 +1,74 @@ +payment_status = $payment_status; + $this->order_status = $order_status; + $this->payment_id = $payment_id; + $this->payment_reference = $payment_reference; + $this->payment_type = $payment_type; + } + + public function getPaymentStatus(): ?PaymentStatus + { + return $this->payment_status; + } + + public function getOrderStatus(): ?OrderStatus + { + return $this->order_status; + } + + public function getPaymentId(): ?string + { + return $this->payment_id; + } + + public function getPaymentReference(): ?string + { + return $this->payment_reference; + } + + public function getPaymentType(): ?PaymentType + { + return $this->payment_type; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/MerchantApi/Model/Order/Request/OrderDetails.php b/modules/inpostizi/src/MerchantApi/Model/Order/Request/OrderDetails.php new file mode 100644 index 00000000..ba05a3ca --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Model/Order/Request/OrderDetails.php @@ -0,0 +1,76 @@ +order_comments = $order_comments; + $this->basket_id = $basket_id; + $this->currency = $currency; + $this->basket_price = $basket_price; + $this->payment_type = $payment_type; + } + + public function getOrderComments(): ?string + { + return $this->order_comments; + } + + public function getBasketId(): string + { + return $this->basket_id; + } + + public function getCurrency(): Currency + { + return $this->currency; + } + + public function getBasketPrice(): Price + { + return $this->basket_price; + } + + public function getPaymentType(): PaymentType + { + return $this->payment_type; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/MerchantApi/Model/Order/Request/OrderEvent.php b/modules/inpostizi/src/MerchantApi/Model/Order/Request/OrderEvent.php new file mode 100644 index 00000000..49044108 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Model/Order/Request/OrderEvent.php @@ -0,0 +1,63 @@ +event_id = $event_id; + $this->event_data_time = $event_data_time; + $this->phone_number = $phone_number; + $this->event_data = $event_data; + } + + public function getId(): string + { + return $this->event_id; + } + + public function getDateTime(): \DateTimeImmutable + { + return $this->event_data_time; + } + + public function getPhoneNumber(): ?PhoneNumber + { + return $this->phone_number; + } + + public function getData(): EventData + { + return $this->event_data; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/MerchantApi/Model/Order/Request/OrderStatus.php b/modules/inpostizi/src/MerchantApi/Model/Order/Request/OrderStatus.php new file mode 100644 index 00000000..eb1fcc92 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Model/Order/Request/OrderStatus.php @@ -0,0 +1,15 @@ +delivery_type = $delivery_type; + $this->delivery_date = $delivery_date; + $this->delivery_options = $delivery_options; + $this->mail = $mail; + $this->phone_number = $phone_number; + $this->delivery_point = $delivery_point; + $this->delivery_address = $delivery_address; + $this->delivery_price = $delivery_price; + $this->courier_note = $courier_note; + } + + public function getType(): DeliveryType + { + return $this->delivery_type; + } + + public function getDate(): \DateTimeImmutable + { + return $this->delivery_date; + } + + /** + * @return OptionalService[] + */ + public function getOptionalServices(): array + { + return $this->delivery_options; + } + + public function getEmail(): ?string + { + return $this->mail; + } + + public function getPhoneNumber(): ?PhoneNumber + { + return $this->phone_number; + } + + public function getPoint(): ?string + { + return $this->delivery_point; + } + + public function getAddress(): ?DeliveryAddress + { + return $this->delivery_address; + } + + public function getPrice(): Price + { + return $this->delivery_price; + } + + public function getCourierNote(): ?string + { + return $this->courier_note; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/MerchantApi/Model/Order/Response/Order.php b/modules/inpostizi/src/MerchantApi/Model/Order/Response/Order.php new file mode 100644 index 00000000..687691aa --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Model/Order/Response/Order.php @@ -0,0 +1,92 @@ +order_details = $order_details; + $this->account_info = $account_info; + $this->invoice_details = $invoice_details; + $this->delivery = $delivery; + $this->products = $products; + $this->consents = $consents; + } + + public function getOrderDetails(): OrderDetails + { + return $this->order_details; + } + + public function getAccountInfo(): AccountInfo + { + return $this->account_info; + } + + public function getInvoiceDetails(): ?InvoiceDetails + { + return $this->invoice_details; + } + + public function getDelivery(): Delivery + { + return $this->delivery; + } + + public function getProducts(): array + { + return $this->products; + } + + public function getConsents(): array + { + return $this->consents; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/MerchantApi/Model/Order/Response/OrderDetails.php b/modules/inpostizi/src/MerchantApi/Model/Order/Response/OrderDetails.php new file mode 100644 index 00000000..6429b997 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Model/Order/Response/OrderDetails.php @@ -0,0 +1,196 @@ +order_comments = $order_comments; + $this->order_id = $order_id; + $this->pos_id = $pos_id; + $this->order_creation_date = $order_creation_date; + $this->basket_id = $basket_id; + $this->order_merchant_status_description = $order_merchant_status_description; + $this->payment_type = $payment_type; + $this->order_base_price = $order_base_price; + $this->order_final_price = $order_final_price; + $this->currency = $currency; + $this->delivery_references_list = $delivery_references_list; + $this->order_discount = $order_discount; + $this->customer_order_id = $customer_order_id; + $this->order_additional_parameters = $order_additional_parameters; + } + + public function getComments(): ?string + { + return $this->order_comments; + } + + public function getId(): string + { + return $this->order_id; + } + + public function getPosId(): string + { + return $this->pos_id; + } + + public function getCreationDate(): \DateTimeImmutable + { + return $this->order_creation_date; + } + + public function getBasketId(): string + { + return $this->basket_id; + } + + public function getMerchantStatusDescription(): string + { + return $this->order_merchant_status_description; + } + + public function getPaymentType(): PaymentType + { + return $this->payment_type; + } + + public function getBasePrice(): Price + { + return $this->order_base_price; + } + + public function getFinalPrice(): Price + { + return $this->order_final_price; + } + + public function getDiscount(): ?float + { + return $this->order_discount; + } + + public function getCurrency(): Currency + { + return $this->currency; + } + + public function getCustomerOrderId(): ?string + { + return $this->customer_order_id; + } + + /** + * @return string[] + */ + public function getDeliveryReferencesList(): array + { + return $this->delivery_references_list; + } + + public function getOrderAdditionalParameters(): ?OrderAdditionalParameters + { + return $this->order_additional_parameters; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/MerchantApi/Model/Order/Response/OrderStatusData.php b/modules/inpostizi/src/MerchantApi/Model/Order/Response/OrderStatusData.php new file mode 100644 index 00000000..7f2414ac --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Model/Order/Response/OrderStatusData.php @@ -0,0 +1,58 @@ +order_status = $order_status; + $this->order_merchant_status_description = $order_merchant_status_description; + $this->delivery_references_list = $delivery_references_list; + } + + public function getStatus(): ?MerchantOrderStatus + { + return $this->order_status; + } + + public function getStatusDescription(): ?string + { + return $this->order_merchant_status_description; + } + + /** + * @return string[]|null + */ + public function getDeliveryReferencesList(): ?array + { + return $this->delivery_references_list; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/MerchantApi/Model/Product/Response/Products.php b/modules/inpostizi/src/MerchantApi/Model/Product/Response/Products.php new file mode 100644 index 00000000..23b268c2 --- /dev/null +++ b/modules/inpostizi/src/MerchantApi/Model/Product/Response/Products.php @@ -0,0 +1,69 @@ +content = $content; + $this->total_items = $total_items; + $this->page_index = $page_index; + $this->page_size = $page_size; + } + + /** + * @return Product[] + */ + public function getItems(): array + { + return $this->content; + } + + public function getTotalCount(): ?int + { + return $this->total_items; + } + + public function getPageIndex(): ?int + { + return $this->page_index; + } + + public function getPageSize(): ?int + { + return $this->page_size; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); + } +} diff --git a/modules/inpostizi/src/Module/Exception/ModuleErrorInterface.php b/modules/inpostizi/src/Module/Exception/ModuleErrorInterface.php new file mode 100644 index 00000000..0a95de7e --- /dev/null +++ b/modules/inpostizi/src/Module/Exception/ModuleErrorInterface.php @@ -0,0 +1,17 @@ + $name + * + * @return T|null + */ + public function findByName(string $name): ?\Module + { + if (false === $module = \Module::getInstanceByName($name)) { + return null; + } + + return $module; + } +} diff --git a/modules/inpostizi/src/OAuth2/Authentication/AuthenticationMethodInterface.php b/modules/inpostizi/src/OAuth2/Authentication/AuthenticationMethodInterface.php new file mode 100644 index 00000000..786e3981 --- /dev/null +++ b/modules/inpostizi/src/OAuth2/Authentication/AuthenticationMethodInterface.php @@ -0,0 +1,14 @@ +clientId = $clientId; + $this->clientSecret = $clientSecret; + } + + public function getClientId(): string + { + return $this->clientId; + } + + public function getClientSecret(): ?string + { + return $this->clientSecret; + } +} diff --git a/modules/inpostizi/src/OAuth2/Authentication/ClientCredentialsInterface.php b/modules/inpostizi/src/OAuth2/Authentication/ClientCredentialsInterface.php new file mode 100644 index 00000000..7c50bc02 --- /dev/null +++ b/modules/inpostizi/src/OAuth2/Authentication/ClientCredentialsInterface.php @@ -0,0 +1,12 @@ +getClientSecret()) { + throw new \LogicException(sprintf('"%s" authentication method cannot be used if the client was not issued client credentials.', self::IDENTIFIER)); + } + + $payload['client_id'] = $credentials->getClientId(); + $payload['client_secret'] = $clientSecret; + + return $request; + } +} diff --git a/modules/inpostizi/src/OAuth2/Authentication/None.php b/modules/inpostizi/src/OAuth2/Authentication/None.php new file mode 100644 index 00000000..069ce64e --- /dev/null +++ b/modules/inpostizi/src/OAuth2/Authentication/None.php @@ -0,0 +1,24 @@ +getClientId(); + + return $request; + } +} diff --git a/modules/inpostizi/src/OAuth2/AuthorizationProvider.php b/modules/inpostizi/src/OAuth2/AuthorizationProvider.php new file mode 100644 index 00000000..e4a64b34 --- /dev/null +++ b/modules/inpostizi/src/OAuth2/AuthorizationProvider.php @@ -0,0 +1,147 @@ +authServerClient = $authServerClient; + $this->grantType = $grantType; + $this->credentials = $credentials; + $this->tokenRepository = $tokenRepository ?? new InMemoryTokenRepository(); + $this->tokenFactory = $tokenFactory ?? new BearerTokenFactory(); + $this->clock = $clock ?? SystemClock::fromSystemTimezone(); + } + + /** + * {@inheritDoc} + */ + public function authorize(array $scopes = []) + { + $this->grantType->authorize($this->authServerClient, $this->credentials, $scopes); + } + + /** + * {@inheritDoc} + */ + public function processAuthorizationResponse(ServerRequestInterface $request): void + { + $this->grantType->processAuthorizationResponse($request); + } + + /** + * {@inheritDoc} + */ + public function getAccessToken(bool $renew = false, array $scopes = []): AccessTokenInterface + { + if (!$renew && $token = $this->getOrRefreshStoredToken($scopes)) { + return $token; + } + + $data = $this->grantType->getAccessToken($this->authServerClient, $this->credentials, $scopes); + + return $this->createAndSaveToken($data); + } + + private function getOrRefreshStoredToken(array $scopes): ?AccessTokenInterface + { + if (!$token = $this->tokenRepository->getToken()) { + return null; + } + + if (!$this->hasRequestedScopes($token, $scopes)) { + return null; + } + + if (!$this->isExpired($token)) { + return $token; + } + + if (!$refreshToken = $token->getRefreshToken()) { + return null; + } + + return $this->refreshAccessToken($refreshToken); + } + + private function hasRequestedScopes(AccessTokenInterface $token, array $scopes): bool + { + if ([] === $scopes) { + return true; + } + + if (!$tokenScopes = $token->getScopes()) { + return false; + } + + return [] === array_diff($scopes, $tokenScopes); + } + + private function isExpired(AccessTokenInterface $token): bool + { + $expiresAt = $token->getExpiresAt(); + + return null !== $expiresAt && $expiresAt <= $this->clock->now(); + } + + private function refreshAccessToken(string $refreshToken): AccessTokenInterface + { + $grant = new RefreshTokenGrant($refreshToken); + $data = $grant->getAccessToken($this->authServerClient, $this->credentials); + + return $this->createAndSaveToken($data); + } + + private function createAndSaveToken(array $data): AccessTokenInterface + { + $token = $this->tokenFactory->createToken($data); + $this->tokenRepository->saveToken($token); + + return $token; + } +} diff --git a/modules/inpostizi/src/OAuth2/AuthorizationProviderFactoryInterface.php b/modules/inpostizi/src/OAuth2/AuthorizationProviderFactoryInterface.php new file mode 100644 index 00000000..d9bca6ad --- /dev/null +++ b/modules/inpostizi/src/OAuth2/AuthorizationProviderFactoryInterface.php @@ -0,0 +1,13 @@ +client = $client; + $this->requestFactory = $requestFactory; + $this->streamFactory = $streamFactory; + $this->uriCollection = $uriCollection; + $this->authenticationMethod = $authenticationMethod ?? new None(); + } + + /** + * {@inheritDoc} + */ + public function sendAccessTokenRequest(ClientCredentialsInterface $credentials, array $parameters): array + { + $request = $this->createAccessTokenRequest($credentials, $parameters); + try { + $response = $this->client->sendRequest($request); + } catch (NetworkExceptionInterface $e) { + throw new NetworkException($e); + } + + if (200 !== $response->getStatusCode()) { + $this->handleAccessTokenResponseError($response); + } + + return $this->decodeAccessTokenResponse($response); + } + + /** + * {@inheritDoc} + */ + public function redirectToAuthorizationEndpoint(array $parameters) + { + $url = $this->getAuthorizationUrl($parameters); + + header('Location: ' . $url); + exit; + } + + private function createAccessTokenRequest(ClientCredentialsInterface $credentials, array $parameters): RequestInterface + { + $request = $this->requestFactory->createRequest('POST', $this->uriCollection->getTokenEndpointUri()); + $request = $this->authenticationMethod->authenticate($request, $parameters, $credentials); + + $body = $this->streamFactory->createStream(http_build_query($parameters, '', '&')); + + return $request + ->withBody($body) + ->withHeader('Content-Type', 'application/x-www-form-urlencoded'); + } + + private function decodeAccessTokenResponse(ResponseInterface $response): array + { + $data = $this->decodeJsonResponse($response); + + if (!isset($data['token_type']) || !is_string($data['token_type'])) { + throw new UnexpectedValueException('Token type is missing in the response data.'); + } + + if (!isset($data['access_token']) || !is_string($data['access_token'])) { + throw new UnexpectedValueException('Access token is missing in the response data.'); + } + + return $data; + } + + private function handleAccessTokenResponseError(ResponseInterface $response): void + { + try { + $data = $this->decodeJsonResponse($response); + } catch (\Exception $exception) { + // decoding exception + } + + if (isset($data['error'])) { + throw AccessTokenRequestException::create($data, $response); + } + + throw new UnexpectedValueException('Unexpected access token response format.', 0, $exception ?? null); + } + + private function getAuthorizationUrl(array $parameters): string + { + $url = $this->uriCollection->getAuthorizationEndpointUri(); + $query = http_build_query($parameters, '', '&', PHP_QUERY_RFC3986); + + return false === strpos($url, '?') + ? $url . '?' . $query + : $url . '&' . $query; + } + + private function decodeJsonResponse(ResponseInterface $response): array + { + if ('' === $content = (string) $response->getBody()) { + throw new UnexpectedValueException('Response body is empty.'); + } + + $data = json_decode($content, true, 512, JSON_BIGINT_AS_STRING); + + if (null === $data && JSON_ERROR_NONE !== $errorCode = json_last_error()) { + throw new UnexpectedValueException(json_last_error_msg(), $errorCode); + } + + if (!is_array($data)) { + throw new UnexpectedValueException(sprintf('JSON content was expected to decode to an array, "%s" returned".', gettype($data))); + } + + return $data; + } +} diff --git a/modules/inpostizi/src/OAuth2/AuthorizationServerClientInterface.php b/modules/inpostizi/src/OAuth2/AuthorizationServerClientInterface.php new file mode 100644 index 00000000..2c4ee964 --- /dev/null +++ b/modules/inpostizi/src/OAuth2/AuthorizationServerClientInterface.php @@ -0,0 +1,20 @@ +error = $error; + $this->response = $response; + + parent::__construct($message, $code); + } + + public static function create(array $data, ResponseInterface $response): self + { + $error = $data['error']; + $description = $data['error_description'] ?? null; + + $message = sprintf('Access token response error: "%s".', $error); + if (null !== $description) { + $message = sprintf('%s Error description: "%s".', $message, $description); + } + + return new self($error, $response, $message); + } + + public function getError(): string + { + return $this->error; + } + + public function getResponse(): ResponseInterface + { + return $this->response; + } +} diff --git a/modules/inpostizi/src/OAuth2/Exception/NetworkException.php b/modules/inpostizi/src/OAuth2/Exception/NetworkException.php new file mode 100644 index 00000000..03ed9965 --- /dev/null +++ b/modules/inpostizi/src/OAuth2/Exception/NetworkException.php @@ -0,0 +1,25 @@ +request = $previous->getRequest(); + + parent::__construct($previous->getMessage(), $previous->getCode(), $previous); + } + + public function getRequest(): RequestInterface + { + return $this->request; + } +} diff --git a/modules/inpostizi/src/OAuth2/Exception/OAuth2ExceptionInterface.php b/modules/inpostizi/src/OAuth2/Exception/OAuth2ExceptionInterface.php new file mode 100644 index 00000000..c3bfa0ca --- /dev/null +++ b/modules/inpostizi/src/OAuth2/Exception/OAuth2ExceptionInterface.php @@ -0,0 +1,9 @@ +getAccessTokenRequestParameters($credentials, $scopes); + + return $authServerClient->sendAccessTokenRequest($credentials, $params); + } + + protected function getAccessTokenRequestParameters(ClientCredentialsInterface $credentials, array $scopes): array + { + $params = ['grant_type' => $this->getIdentifier()]; + if ([] !== $scopes) { + $params['scope'] = implode(' ', $scopes); + } + + return $params; + } +} diff --git a/modules/inpostizi/src/OAuth2/Grant/ClientCredentialsGrant.php b/modules/inpostizi/src/OAuth2/Grant/ClientCredentialsGrant.php new file mode 100644 index 00000000..564d0d3e --- /dev/null +++ b/modules/inpostizi/src/OAuth2/Grant/ClientCredentialsGrant.php @@ -0,0 +1,15 @@ +refreshToken = $refreshToken; + } + + public function getIdentifier(): string + { + return self::IDENTIFIER; + } + + protected function getAccessTokenRequestParameters(ClientCredentialsInterface $credentials, array $scopes): array + { + $params = parent::getAccessTokenRequestParameters($credentials, $scopes); + $params['refresh_token'] = $this->refreshToken; + + return $params; + } +} diff --git a/modules/inpostizi/src/OAuth2/LazyAuthorizationProvider.php b/modules/inpostizi/src/OAuth2/LazyAuthorizationProvider.php new file mode 100644 index 00000000..514ce2d2 --- /dev/null +++ b/modules/inpostizi/src/OAuth2/LazyAuthorizationProvider.php @@ -0,0 +1,75 @@ +factory = $factory; + $this->uriCollection = $uriCollection; + $this->credentialsRepository = $credentialsRepository; + $this->tokenRepository = $tokenRepository; + } + + public function authorize(array $scopes = []) + { + $this->getAuthProvider()->authorize($scopes); + } + + public function processAuthorizationResponse(ServerRequestInterface $request): void + { + $this->getAuthProvider()->processAuthorizationResponse($request); + } + + public function getAccessToken(bool $renew = false, array $scopes = []): AccessTokenInterface + { + return $this->getAuthProvider()->getAccessToken($renew, $scopes); + } + + private function getAuthProvider(): AuthorizationProviderInterface + { + return $this->authProvider ?? ($this->authProvider = $this->createAuthProvider()); + } + + private function createAuthProvider(): AuthorizationProviderInterface + { + if (null === $credentials = $this->credentialsRepository->getClientCredentials()) { + throw new \RuntimeException('Client credentials are not available.'); + } + + return $this->factory->create($this->uriCollection, $credentials, $this->tokenRepository); + } +} diff --git a/modules/inpostizi/src/OAuth2/Token/AccessTokenFactoryInterface.php b/modules/inpostizi/src/OAuth2/Token/AccessTokenFactoryInterface.php new file mode 100644 index 00000000..8e1b9ba2 --- /dev/null +++ b/modules/inpostizi/src/OAuth2/Token/AccessTokenFactoryInterface.php @@ -0,0 +1,13 @@ +clock = $clock ?? SystemClock::fromSystemTimezone(); + $this->defaultExpirationTime = $defaultExpirationTime; + } + + private function getExpiresAt(array $data): ?\DateTimeImmutable + { + $expiresIn = isset($data['expires_in']) ? (int) $data['expires_in'] : $this->defaultExpirationTime; + + if (null === $expiresIn) { + return null; + } + + if (0 > $expiresIn) { + throw new UnexpectedValueException('Negative access token expiration time.'); + } + + return $this->clock->now()->add(new \DateInterval(sprintf('PT%dS', $expiresIn))); + } + + private function getScopes(array $data): ?array + { + if (!isset($data['scope'])) { + return null; + } + + return explode(' ', $data['scope']); + } +} diff --git a/modules/inpostizi/src/OAuth2/Token/AccessTokenInterface.php b/modules/inpostizi/src/OAuth2/Token/AccessTokenInterface.php new file mode 100644 index 00000000..dc49c25a --- /dev/null +++ b/modules/inpostizi/src/OAuth2/Token/AccessTokenInterface.php @@ -0,0 +1,25 @@ +accessToken; + } + + public function getExpiresAt(): ?\DateTimeImmutable + { + return $this->expiresAt; + } + + public function getRefreshToken(): ?string + { + return $this->refreshToken; + } + + public function getScopes(): ?array + { + return $this->scopes; + } +} diff --git a/modules/inpostizi/src/OAuth2/Token/BearerToken.php b/modules/inpostizi/src/OAuth2/Token/BearerToken.php new file mode 100644 index 00000000..08bc0305 --- /dev/null +++ b/modules/inpostizi/src/OAuth2/Token/BearerToken.php @@ -0,0 +1,35 @@ +accessToken = $accessToken; + $this->expiresAt = $expiresAt; + $this->refreshToken = $refreshToken; + $this->scopes = $scopes; + } + + public function getType(): string + { + return self::TYPE; + } + + public function authorize(RequestInterface $request): RequestInterface + { + return $request->withHeader('Authorization', 'Bearer ' . $this->accessToken); + } +} diff --git a/modules/inpostizi/src/OAuth2/Token/BearerTokenFactory.php b/modules/inpostizi/src/OAuth2/Token/BearerTokenFactory.php new file mode 100644 index 00000000..4771f22d --- /dev/null +++ b/modules/inpostizi/src/OAuth2/Token/BearerTokenFactory.php @@ -0,0 +1,26 @@ +getExpiresAt($data), + $data['refresh_token'] ?? null, + $this->getScopes($data) + ); + } +} diff --git a/modules/inpostizi/src/OAuth2/Token/InMemoryTokenRepository.php b/modules/inpostizi/src/OAuth2/Token/InMemoryTokenRepository.php new file mode 100644 index 00000000..5076d4a5 --- /dev/null +++ b/modules/inpostizi/src/OAuth2/Token/InMemoryTokenRepository.php @@ -0,0 +1,25 @@ +accessToken; + } + + public function saveToken(AccessTokenInterface $accessToken): void + { + $this->accessToken = $accessToken; + } + + public function deleteToken(): void + { + $this->accessToken = null; + } +} diff --git a/modules/inpostizi/src/OAuth2/UriCollection.php b/modules/inpostizi/src/OAuth2/UriCollection.php new file mode 100644 index 00000000..ea7468f5 --- /dev/null +++ b/modules/inpostizi/src/OAuth2/UriCollection.php @@ -0,0 +1,34 @@ +authorizationEndpointUri = $authorizationEndpointUri; + $this->tokenEndpointUri = $tokenEndpointUri; + } + + public function getAuthorizationEndpointUri(): string + { + return $this->authorizationEndpointUri; + } + + public function getTokenEndpointUri(): string + { + return $this->tokenEndpointUri; + } +} diff --git a/modules/inpostizi/src/OAuth2/UriCollectionInterface.php b/modules/inpostizi/src/OAuth2/UriCollectionInterface.php new file mode 100644 index 00000000..d0c69b0a --- /dev/null +++ b/modules/inpostizi/src/OAuth2/UriCollectionInterface.php @@ -0,0 +1,12 @@ +db = $db ?? \Db::getInstance(); + } + + public function getPlatformVersion(): string + { + return $this->db->getVersion(); + } + + public function save(\ObjectModel $model) + { + return $this->execute(function () use ($model) { + return $model->save(); + }); + } + + public function delete(\ObjectModel $model) + { + return $this->execute(function () use ($model) { + return $model->delete(); + }); + } + + public function fetchAllAssociative(string $sql): array + { + return $this->execute(function () use ($sql) { + return $this->db->executeS($sql); + }); + } + + /** + * @template T + * + * @param \Closure(): T $closure + * + * @return T passed function result + * + * @throws \PrestaShopDatabaseException + */ + protected function execute(\Closure $closure) + { + try { + $result = $closure(); + } catch (\PrestaShopDatabaseException $e) { + throw $this->normalizeException($e); + } + + if (false !== $result || !$error = $this->db->getNumberError()) { + return $result; + } + + throw new \PrestaShopDatabaseException($this->db->getMsgError(), $error); + } + + private function normalizeException(\PrestaShopDatabaseException $e): \PrestaShopDatabaseException + { + return new \PrestaShopDatabaseException($e->getMessage(), $this->db->getNumberError()); + } +} diff --git a/modules/inpostizi/src/ObjectModel/Entity/InPostIziBasketSession.php b/modules/inpostizi/src/ObjectModel/Entity/InPostIziBasketSession.php new file mode 100644 index 00000000..8f0898c2 --- /dev/null +++ b/modules/inpostizi/src/ObjectModel/Entity/InPostIziBasketSession.php @@ -0,0 +1,78 @@ + self::TABLE_NAME, + 'primary' => 'id', + 'fields' => [ + 'id_shop' => ['type' => self::TYPE_INT, 'allow_null' => true], + 'session_id' => ['type' => self::TYPE_INT, 'required' => true], + 'cart_id' => ['type' => self::TYPE_STRING, 'required' => true], + 'confirmation_response' => ['type' => self::TYPE_STRING, 'allow_null' => true], + 'order_id' => ['type' => self::TYPE_INT, 'allow_null' => true], + 'order_details' => ['type' => self::TYPE_STRING, 'allow_null' => true], + 'redirect_url' => ['type' => self::TYPE_STRING, 'allow_null' => true], + 'coupons' => ['type' => self::TYPE_BOOL, 'allow_null' => true], + 'redirected' => ['type' => self::TYPE_BOOL], + 'binding_api_key' => ['type' => self::TYPE_STRING, 'allow_null' => true], + ], + ]; +} diff --git a/modules/inpostizi/src/ObjectModel/Exception/InvalidDataException.php b/modules/inpostizi/src/ObjectModel/Exception/InvalidDataException.php new file mode 100644 index 00000000..21644dea --- /dev/null +++ b/modules/inpostizi/src/ObjectModel/Exception/InvalidDataException.php @@ -0,0 +1,9 @@ +hydrateObject($model, $data, $languageId); + } + + $collection = $this->hydrateCollection($data, $class, $languageId); + if (1 !== $count = count($collection)) { + throw new \DomainException(sprintf('Unexpected collection count. Expected: 1, got: %d.', $count)); + } + + return current($collection); + } + + /** + * @template T of \ObjectModel + * + * @param class-string $class + * + * @return T[] + * + * @throws \PrestaShopException + */ + public function hydrateCollection(array $data, string $class, ?int $languageId = null): array + { + return $class::hydrateCollection($class, $data, $languageId); + } + + private function hydrateObject(\ObjectModel $model, array $data, ?int $languageId = null): \ObjectModel + { + $metadata = $model::getDefinition(get_class($model)); + $data = $this->normalizeData($data, $metadata); + + $id = (int) $model->id; + $idColumn = $metadata['primary']; + + if (0 < $id && isset($data[$idColumn]) && $id !== (int) $data[$idColumn]) { + throw new \DomainException('Identifier mismatch.'); + } + + $model->hydrate($data, $languageId); + + return $model; + } + + private function normalizeData(array $data, array $metadata): array + { + $result = []; + + $langFields = empty($metadata['multilang']) ? [] : array_filter($metadata['fields'], static function (array $field) { + return !empty($field['lang']); + }); + + foreach ($data as $row) { + $result = $this->appendDataRow($row, $langFields, $result); + } + + return $result; + } + + private function appendDataRow(array $row, array $langFields, array $result): array + { + foreach ($row as $field => $value) { + if (!isset($langFields[$field])) { + $result[$field] = $value; + } elseif (!isset($row['id_lang'])) { + throw new \DomainException('Language identifier is missing.'); + } else { + $result[$field][(int) $row['id_lang']] = $value; + } + } + + return $result; + } +} diff --git a/modules/inpostizi/src/ObjectModel/HydratorInterface.php b/modules/inpostizi/src/ObjectModel/HydratorInterface.php new file mode 100644 index 00000000..1dc7c6c0 --- /dev/null +++ b/modules/inpostizi/src/ObjectModel/HydratorInterface.php @@ -0,0 +1,26 @@ + $class + * + * @return T + */ + public function hydrate(array $data, string $class, ?\ObjectModel $model = null, ?int $languageId = null): \ObjectModel; + + /** + * @template T of \ObjectModel + * + * @param class-string $class + * + * @return T[] + */ + public function hydrateCollection(array $data, string $class, ?int $languageId = null): array; +} diff --git a/modules/inpostizi/src/ObjectModel/ObjectManager.php b/modules/inpostizi/src/ObjectModel/ObjectManager.php new file mode 100644 index 00000000..a29d2666 --- /dev/null +++ b/modules/inpostizi/src/ObjectModel/ObjectManager.php @@ -0,0 +1,181 @@ + metadata by class + */ + private $metadata = []; + + public function __construct(Connection $connection, ObjectRepositoryFactoryInterface $repositoryFactory, HydratorInterface $hydrator) + { + $this->repositoryFactory = $repositoryFactory; + $this->connection = $connection; + $this->hydrator = $hydrator; + } + + public function getConnection(): Connection + { + return $this->connection; + } + + public function getHydrator(): HydratorInterface + { + return $this->hydrator; + } + + public function save(\ObjectModel $model): void + { + $id = (int) $model->id; + + $this->validateModel($model); + + if (false === $this->connection->execute(\Closure::fromCallable([$model, 'save']))) { + $message = 0 >= $id + ? sprintf('Failed to create a new %s.', get_class($model)) + : sprintf('Failed to update %s ID %d.', get_class($model), $id); + + throw new \RuntimeException($message); + } + } + + public function remove(\ObjectModel $model): void + { + if (0 >= $id = (int) $model->id) { + return; + } + + if (false === $this->connection->execute(\Closure::fromCallable([$model, 'delete']))) { + throw new \RuntimeException(sprintf('Failed to delete %s ID %d.', get_class($model), $id)); + } + } + + /** + * @template T of \ObjectModel + * + * @param class-string $class + * + * @return T|null + */ + public function find(string $class, int $id, ?int $languageId = null, ?int $shopId = null): ?\ObjectModel + { + if (0 >= $id) { + return null; + } + + $args = \Product::class === $class + ? [$id, false, $languageId, $shopId] + : [$id, $languageId, $shopId]; + + $model = new $class(...$args); + + return $id === (int) $model->id ? $model : null; + } + + public function refresh(\ObjectModel $model): void + { + $class = get_class($model); + $metadata = $this->getMetadata($class); + + $languageId = $metadata['multilang'] + ? (\Closure::bind(function (): ?int { + return isset($this->id_lang) ? (int) $this->id_lang : null; + }, $model, \ObjectModel::class))() + : null; + + $shopId = $metadata['multishop'] ? $this->getShopId($model) : null; + + $data = $this + ->getRepository($class) + ->createQueryBuilder('a', $languageId, $shopId) + ->where(sprintf('a.%s = %d', $metadata['primary'], (int) $model->id)) + ->build() + ->getArrayResult(); + + $this->hydrator->hydrate($data, $class, $model, $languageId); + } + + /** + * @template T of \ObjectModel + * + * @param class-string $class + */ + public function getMetadata(string $class): array + { + if (isset($this->metadata[$class])) { + return $this->metadata[$class]; + } + + if (!is_subclass_of($class, \ObjectModel::class)) { + throw new \DomainException(sprintf('%s is not a %s.', $class, \ObjectModel::class)); + } + + $metadata = $class::getDefinition($class); + $metadata['multishop'] = \Shop::isTableAssociated($metadata['table']); + + if (!array_key_exists('multilang', $metadata)) { + $metadata['multilang'] = false; + } + + if (!array_key_exists('multilang_shop', $metadata)) { + $metadata['multilang_shop'] = false; + } + + return $this->metadata[$class] = $metadata; + } + + public function getRepository(string $class): ObjectRepositoryInterface + { + return $this->repositoryFactory->getRepository($this, $class); + } + + public function createQueryBuilder(string $class, ?int $languageId = null): QueryBuilder + { + return new QueryBuilder($this, $class, $languageId); + } + + private function validateModel(\ObjectModel $model): void + { + try { + $model->validateFields(); + $model->validateFieldsLang(); + } catch (\PrestaShopException $e) { + throw new InvalidDataException($e->getMessage(), 0, $e); + } + } + + private function getShopId(\ObjectModel $model): ?int + { + if (is_callable([$model, 'getShopId'])) { + return $model->getShopId(); + } + + return (\Closure::bind(function (): ?int { + return isset($this->id_shop) ? (int) $this->id_shop : null; + }, $model, \ObjectModel::class))(); + } +} diff --git a/modules/inpostizi/src/ObjectModel/ObjectManagerInterface.php b/modules/inpostizi/src/ObjectModel/ObjectManagerInterface.php new file mode 100644 index 00000000..c4472c76 --- /dev/null +++ b/modules/inpostizi/src/ObjectModel/ObjectManagerInterface.php @@ -0,0 +1,57 @@ + $class + * + * @return T|null + */ + public function find(string $class, int $id, ?int $languageId = null, ?int $shopId = null): ?\ObjectModel; + + public function refresh(\ObjectModel $model); + + /** + * @template T of \ObjectModel + * + * @param class-string $class + * + * @return array normalized {@see \ObjectModel::$definition} + */ + public function getMetadata(string $class): array; + + /** + * @template T of \ObjectModel + * + * @param class-string $class + * + * @return ObjectRepositoryInterface + */ + public function getRepository(string $class): ObjectRepositoryInterface; + + /** + * @template T of \ObjectModel + * + * @param class-string $class + * + * @return QueryBuilder + */ + public function createQueryBuilder(string $class, ?int $languageId = null): QueryBuilder; + + public function remove(\ObjectModel $model); +} diff --git a/modules/inpostizi/src/ObjectModel/OrderMaintainingLoaderTrait.php b/modules/inpostizi/src/ObjectModel/OrderMaintainingLoaderTrait.php new file mode 100644 index 00000000..335e5007 --- /dev/null +++ b/modules/inpostizi/src/ObjectModel/OrderMaintainingLoaderTrait.php @@ -0,0 +1,57 @@ + $class + * @param array $ids list of object identifiers + * + * @return array objects indexed by corresponding keys from the $ids array and in the same relative order + */ + private function findByIdsMaintainingOrder(string $class, array $ids, ?int $languageId = null, ?int $shopId = null): array + { + if ([] === $ids) { + return []; + } + + $criteria = ['id' => $ids]; + + if (null !== $languageId) { + $criteria['id_lang'] = $languageId; + } + + if (null !== $shopId) { + $criteria['id_shop'] = $shopId; + } + + if ([] === $objects = $this->manager->getRepository($class)->findBy($criteria)) { + return []; + } + + $objectsById = []; + foreach ($objects as $object) { + $objectsById[$object->id] = $object; + } + + $result = []; + foreach ($ids as $i => $id) { + if (isset($objectsById[$id])) { + $result[$i] = $objectsById[$id]; + } + } + + return $result; + } +} diff --git a/modules/inpostizi/src/ObjectModel/Query.php b/modules/inpostizi/src/ObjectModel/Query.php new file mode 100644 index 00000000..c60b9d54 --- /dev/null +++ b/modules/inpostizi/src/ObjectModel/Query.php @@ -0,0 +1,90 @@ + + */ + private $class; + + /** + * @var string + */ + private $sql; + + /** + * @var int|null + */ + private $languageId; + + /** + * @param class-string $class + */ + public function __construct(ObjectManagerInterface $manager, string $class, string $sql, ?int $languageId = null) + { + $this->manager = $manager; + $this->class = $class; + $this->sql = $sql; + $this->languageId = $languageId; + } + + public function getSql(): string + { + return $this->sql; + } + + /** + * @return T[] + */ + public function getResult(): array + { + $data = $this->getArrayResult(); + + return $this->manager->getHydrator()->hydrateCollection($data, $this->class, $this->languageId); + } + + public function getArrayResult(): array + { + return $this->manager->getConnection()->fetchAllAssociative($this->sql); + } + + /** + * @return T|null + */ + public function getOneOrNullResult(): ?\ObjectModel + { + if ([] === $data = $this->getArrayResult()) { + return null; + } + + return $this->manager->getHydrator()->hydrate($data, $this->class, null, $this->languageId); + } + + /** + * @return mixed + */ + public function getSingleScalarResult() + { + $result = $this->manager->getConnection()->fetchFirstColumn($this->sql); + + if (count($result) > 1) { + throw new \DomainException('More than one result was found for query although one row or none was expected.'); + } + + return array_shift($result); + } +} diff --git a/modules/inpostizi/src/ObjectModel/QueryBuilder.php b/modules/inpostizi/src/ObjectModel/QueryBuilder.php new file mode 100644 index 00000000..39ec277e --- /dev/null +++ b/modules/inpostizi/src/ObjectModel/QueryBuilder.php @@ -0,0 +1,98 @@ + $class + * + * @experimental API may change, extending {@see \DbQuery} is a temporary hack + */ +class QueryBuilder extends \DbQuery +{ + /** + * @var ObjectManagerInterface + */ + private $manager; + + /** + * @var class-string + */ + private $class; + + /** + * @var int|null + */ + private $languageId; + + /** + * @param class-string $class + */ + public function __construct(ObjectManagerInterface $manager, string $class, ?int $languageId = null) + { + $this->manager = $manager; + $this->class = $class; + $this->languageId = $languageId; + } + + /** + * @return $this + */ + public function setLanguageId(?int $languageId): self + { + $this->languageId = $languageId; + + return $this; + } + + /** + * @return Query + */ + public function build(): Query + { + return new Query($this->manager, $this->class, parent::build(), $this->languageId); + } + + /** + * @param string|string[] $select + * + * @return $this + */ + public function setSelect($select): self + { + $this->query['select'] = []; + + if (!is_array($select)) { + $select = [$select]; + } + + foreach ($select as $fields) { + $this->select($fields); + } + + return $this; + } + + /** + * @param string|string[] $orderBy + * + * @return $this + */ + public function setOrderBy($orderBy): self + { + $this->query['order'] = []; + + if (!is_array($orderBy)) { + $orderBy = [$orderBy]; + } + + foreach ($orderBy as $fields) { + $this->orderBy($fields); + } + + return $this; + } +} diff --git a/modules/inpostizi/src/ObjectModel/Repository/CarrierRepository.php b/modules/inpostizi/src/ObjectModel/Repository/CarrierRepository.php new file mode 100644 index 00000000..78889a5d --- /dev/null +++ b/modules/inpostizi/src/ObjectModel/Repository/CarrierRepository.php @@ -0,0 +1,31 @@ + + */ +class CarrierRepository extends ObjectRepository +{ + public function __construct(ObjectManagerInterface $manager) + { + parent::__construct(\Carrier::class, $manager); + } + + public function findOneByReferenceId(int $referenceId, ?int $languageId = null): ?\Carrier + { + if (0 >= $referenceId) { + return null; + } + + if (false === $carrier = \Carrier::getCarrierByReference($referenceId, $languageId)) { + return null; + } + + return $carrier; + } +} diff --git a/modules/inpostizi/src/ObjectModel/Repository/CartRuleRepository.php b/modules/inpostizi/src/ObjectModel/Repository/CartRuleRepository.php new file mode 100644 index 00000000..67fa248c --- /dev/null +++ b/modules/inpostizi/src/ObjectModel/Repository/CartRuleRepository.php @@ -0,0 +1,40 @@ + + */ +class CartRuleRepository extends ObjectRepository +{ + public function __construct(ObjectManagerInterface $manager) + { + parent::__construct(\CartRule::class, $manager); + } + + /** + * @param int $cartRuleId identifier of country restricted cart rule + * + * @return \Country[] + */ + public function getCompatibleCountries(int $cartRuleId): array + { + if (0 >= $cartRuleId) { + return []; + } + + return $this->manager + ->createQueryBuilder(\Country::class) + ->select('cl.*, c.*') + ->from('cart_rule_country', 'crc') + ->innerJoin('country', 'c', 'c.id_country = crc.id_country AND c.active = 1') + ->leftJoin('country_lang', 'cl', 'cl.id_country = c.id_country') + ->where('crc.id_cart_rule = ' . $cartRuleId) + ->build() + ->getResult(); + } +} diff --git a/modules/inpostizi/src/ObjectModel/Repository/CmsPageRepository.php b/modules/inpostizi/src/ObjectModel/Repository/CmsPageRepository.php new file mode 100644 index 00000000..54217ffc --- /dev/null +++ b/modules/inpostizi/src/ObjectModel/Repository/CmsPageRepository.php @@ -0,0 +1,28 @@ + + */ +class CmsPageRepository extends ObjectRepository +{ + public function __construct(ObjectManagerInterface $manager) + { + parent::__construct(\CMS::class, $manager); + } + + /** + * @return \CMS[] + */ + public function findActiveByLanguageAndShopId(int $languageId, int $shopId): array + { + $data = \CMS::getCMSPages($languageId, null, true, $shopId); + + return $this->manager->getHydrator()->hydrateCollection($data, $this->getClassName(), $languageId); + } +} diff --git a/modules/inpostizi/src/ObjectModel/Repository/CombinationRepository.php b/modules/inpostizi/src/ObjectModel/Repository/CombinationRepository.php new file mode 100644 index 00000000..d935e83c --- /dev/null +++ b/modules/inpostizi/src/ObjectModel/Repository/CombinationRepository.php @@ -0,0 +1,176 @@ + + */ +class CombinationRepository extends ObjectRepository +{ + /** + * @var class-string + */ + private $attributeModelClass; + + public function __construct(ObjectManagerInterface $manager) + { + parent::__construct(\Combination::class, $manager); + $this->attributeModelClass = class_exists(\ProductAttribute::class) ? \ProductAttribute::class : \Attribute::class; + } + + /** + * @return class-string + * + * @internal + */ + public function getAttributeModelClass(): string + { + return $this->attributeModelClass; + } + + /** + * @return array>> attributes by attribute group ID + */ + public function getAvailableAttributesByProductId(int $productId, int $languageId): array + { + $data = $this + ->createAttributesWithGroupQueryBuilder($languageId) + ->innerJoin('product_attribute', 'pa', 'pa.id_product_attribute = pac.id_product_attribute') + ->where('pa.id_product = ' . $productId) + ->setOrderBy('pa.id_product_attribute, ag.position, a.position') + ->build() + ->getArrayResult(); + + return $this->hydrateProductAttributes($data, $languageId); + } + + /** + * @return ProductAttribute[] + */ + public function getAttributesByCombinationId(int $combinationId, int $languageId): array + { + $data = $this + ->createAttributesWithGroupQueryBuilder($languageId) + ->where('pac.id_product_attribute = ' . $combinationId) + ->build() + ->getArrayResult(); + + $attributes = $this->hydrateProductAttributes($data, $languageId); + + return array_map(static function (array $attributes): ProductAttribute { + return $attributes[0]; + }, $attributes); + } + + /** + * @return array attributes by group ID + */ + public function getSimpleAttributesByCombinationId(int $combinationId, ?int $languageId = null): array + { + /** @var T[] $attributes */ + $attributes = $this + ->createAttributesQueryBuilder($languageId) + ->where('pac.id_product_attribute = ' . $combinationId) + ->build() + ->getResult(); + + $result = []; + + foreach ($attributes as $attribute) { + $result[$attribute->id_attribute_group] = $attribute; + } + + return $result; + } + + public function findByProductAndAttributeIds(int $productId, int ...$attributeIds): ?\Combination + { + if ([] === $attributeIds) { + return null; + } + + if (method_exists(\Product::class, 'getIdProductAttributeByIdAttributes')) { + try { + $combinationId = (int) \Product::getIdProductAttributeByIdAttributes($productId, $attributeIds); + } catch (\PrestaShopObjectNotFoundException $e) { + return null; + } + } else { + $combinationId = (int) \Product::getIdProductAttributesByIdAttributes($productId, $attributeIds); + } + + if (0 >= $combinationId) { + return null; + } + + return $this->find($combinationId); + } + + public function getAttributeGroupIds(int $productId, int $combinationId): array + { + $attributes = \Product::getAttributesParams($productId, $combinationId); + + return array_column($attributes, 'id_attribute_group'); + } + + private function createAttributesWithGroupQueryBuilder(int $languageId): QueryBuilder + { + return $this + ->createAttributesQueryBuilder($languageId) + ->select('ag.is_color_group, ag.group_type, agl.public_name') + ->select('agl.name AS group_name, ag.position as group_position') + ->innerJoin('attribute_group', 'ag', 'ag.id_attribute_group = a.id_attribute_group') + ->leftJoin('attribute_group_lang', 'agl', 'agl.id_attribute_group = ag.id_attribute_group AND agl.id_lang = ' . $languageId) + ->groupBy('a.id_attribute') + ->orderBy('ag.position, a.position'); + } + + /** + * @return QueryBuilder + */ + private function createAttributesQueryBuilder(?int $languageId = null): QueryBuilder + { + return $this->manager + ->getRepository($this->attributeModelClass) + ->createQueryBuilder('a', $languageId) + ->innerJoin('product_attribute_combination', 'pac', 'pac.id_attribute = a.id_attribute'); + } + + /** + * @return array>> attributes by attribute group ID + */ + private function hydrateProductAttributes(array $data, int $languageId): array + { + if ([] === $data) { + return []; + } + + $groups = $result = []; + + foreach ($data as $row) { + /** @var T $attribute */ + $attribute = $this->manager->getHydrator()->hydrate($row, $this->attributeModelClass, null, $languageId); + $groupId = (int) $attribute->id_attribute_group; + + if (!array_key_exists($groupId, $groups)) { + $result[$groupId] = []; + $groups[$groupId] = $this->manager->getHydrator()->hydrate(array_merge($row, [ + 'name' => $row['group_name'], + 'position' => $row['group_position'], + ]), \AttributeGroup::class, null, $languageId); + } + + $result[$groupId][] = new ProductAttribute($attribute, $groups[$groupId]); + } + + return $result; + } +} diff --git a/modules/inpostizi/src/ObjectModel/Repository/ConfigurationRepository.php b/modules/inpostizi/src/ObjectModel/Repository/ConfigurationRepository.php new file mode 100644 index 00000000..6e75001d --- /dev/null +++ b/modules/inpostizi/src/ObjectModel/Repository/ConfigurationRepository.php @@ -0,0 +1,34 @@ + + */ +class ConfigurationRepository extends ObjectRepository +{ + public function __construct(ObjectManagerInterface $manager) + { + parent::__construct(\Configuration::class, $manager); + } + + /** + * @return \Configuration[] + */ + public function findByNamePrefix(string $prefix): array + { + return $this->manager + ->createQueryBuilder($this->class) + ->select('cl.*, c.*, COALESCE(cl.`value`, c.`value`) AS `value`') + ->from('configuration', 'c') + ->leftJoin('configuration_lang', 'cl', 'cl.id_configuration = c.id_configuration') + ->where(sprintf('c.name LIKE "%s%%"', pSQL($prefix))) + ->orderBy('c.id_configuration') + ->build() + ->getResult(); + } +} diff --git a/modules/inpostizi/src/ObjectModel/Repository/CurrencyRepository.php b/modules/inpostizi/src/ObjectModel/Repository/CurrencyRepository.php new file mode 100644 index 00000000..e4625cbe --- /dev/null +++ b/modules/inpostizi/src/ObjectModel/Repository/CurrencyRepository.php @@ -0,0 +1,27 @@ + + */ +class CurrencyRepository extends ObjectRepository +{ + public function __construct(ObjectManagerInterface $manager) + { + parent::__construct(\Currency::class, $manager); + } + + public function findOneByIsoCode(string $isoCode, ?int $shopId = null): ?\Currency + { + if (0 === $currencyId = (int) \Currency::getIdByIsoCode($isoCode, $shopId)) { + return null; + } + + return \Currency::getCurrencyInstance($currencyId); + } +} diff --git a/modules/inpostizi/src/ObjectModel/Repository/HookRepository.php b/modules/inpostizi/src/ObjectModel/Repository/HookRepository.php new file mode 100644 index 00000000..ba097b51 --- /dev/null +++ b/modules/inpostizi/src/ObjectModel/Repository/HookRepository.php @@ -0,0 +1,38 @@ + + */ +class HookRepository extends ObjectRepository +{ + public function __construct(ObjectManagerInterface $manager) + { + parent::__construct(\Hook::class, $manager); + } + + /** + * @return \Hook[] + */ + public function findByModuleId(int $moduleId): array + { + if (0 >= $moduleId) { + return []; + } + + return $this->manager + ->createQueryBuilder($this->class) + ->select('h.*') + ->from('hook', 'h') + ->innerJoin('hook_module', 'hm', 'hm.id_hook = h.id_hook') + ->where('hm.id_module = ' . $moduleId) + ->orderBy('h.name') + ->build() + ->getResult(); + } +} diff --git a/modules/inpostizi/src/ObjectModel/Repository/ImageTypeRepository.php b/modules/inpostizi/src/ObjectModel/Repository/ImageTypeRepository.php new file mode 100644 index 00000000..d5b616b6 --- /dev/null +++ b/modules/inpostizi/src/ObjectModel/Repository/ImageTypeRepository.php @@ -0,0 +1,28 @@ + + */ +class ImageTypeRepository extends ObjectRepository +{ + public function __construct(ObjectManagerInterface $manager) + { + parent::__construct(\ImageType::class, $manager); + } + + /** + * @return \ImageType[] + */ + public function getProductImageTypes(): array + { + return $this->findBy([ + 'products' => true, + ]); + } +} diff --git a/modules/inpostizi/src/ObjectModel/Repository/ObjectRepository.php b/modules/inpostizi/src/ObjectModel/Repository/ObjectRepository.php new file mode 100644 index 00000000..cc9c2f38 --- /dev/null +++ b/modules/inpostizi/src/ObjectModel/Repository/ObjectRepository.php @@ -0,0 +1,261 @@ + + */ + protected $class; + + /** + * @var array + */ + protected $metadata; + + /** + * @var ObjectManagerInterface + */ + protected $manager; + + /** + * @param class-string $class + */ + public function __construct(string $class, ObjectManagerInterface $manager) + { + $this->class = $class; + $this->metadata = $manager->getMetadata($class); + $this->manager = $manager; + } + + /** + * @return class-string + */ + public function getClassName(): string + { + return $this->class; + } + + /** + * @return T|null + */ + public function find(int $id, ?int $languageId = null, ?int $shopId = null): ?\ObjectModel + { + return $this->manager->find($this->class, $id, $languageId, $shopId); + } + + /** + * @return T[] + */ + public function findAll(?int $languageId = null, ?int $shopId = null): array + { + $criteria = []; + + if (null !== $languageId && $this->metadata['multilang']) { + $criteria['id_lang'] = $languageId; + } + + if (null !== $shopId && $this->metadata['multishop']) { + $criteria['id_shop'] = $shopId; + } + + return $this->findBy($criteria, ['id' => 'ASC']); + } + + /** + * @return T|null + */ + public function findOneBy(array $criteria, ?array $orderBy = null): ?\ObjectModel + { + $collection = $this->findBy($criteria, $orderBy, 1); + + return [] === $collection ? null : current($collection); + } + + /** + * @return T[] + */ + public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array + { + return $this + ->createFindByQueryBuilder($criteria, $orderBy, $limit, $offset) + ->build() + ->getResult(); + } + + /** + * @return QueryBuilder + */ + public function createQueryBuilder(string $alias, ?int $languageId = null, ?int $shopId = null): QueryBuilder + { + $identifier = $this->metadata['primary']; + + $qb = $this->manager + ->createQueryBuilder($this->class, $languageId) + ->from($this->metadata['table'], $alias); + + if ($this->metadata['multilang']) { + $langAlias = $alias . 'l'; + $langTable = $this->metadata['table'] . '_lang'; + + $joinConditions = [ + sprintf('%s.%s = %s.%s', $langAlias, $identifier, $alias, $identifier), + ]; + + if (null !== $languageId) { + $joinConditions[] = sprintf('%s.id_lang = %d', $langAlias, $languageId); + } + + if (null !== $shopId && $this->metadata['multilang_shop']) { + $joinConditions[] = sprintf('%s.id_shop = %d', $langAlias, $shopId); + } + + $qb + ->select($langAlias . '.*') + ->select($alias . '.*') // assure primary key is not overwritten if the translation in a given language does not exist + ->leftJoin($langTable, $langAlias, implode(' AND ', $joinConditions)); + } else { + $qb->select($alias . '.*'); + } + + if (null !== $shopId && $this->metadata['multishop']) { + $shopAlias = $alias . '_shop'; + $shopTable = $this->metadata['table'] . '_shop'; + + $joinConditions = [ + sprintf('%s.%s = %s.%s', $shopAlias, $identifier, $alias, $identifier), + sprintf('%s.id_shop = %d', $shopAlias, $shopId), + ]; + + $qb + ->select($shopAlias . '.*') + ->innerJoin($shopTable, $shopAlias, implode(' AND ', $joinConditions)); + } + + return $qb; + } + + protected function createFindByQueryBuilder(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): QueryBuilder + { + if ($this->metadata['multilang'] && isset($criteria['id_lang']) && is_numeric($criteria['id_lang'])) { + $languageId = (int) $criteria['id_lang']; + unset($criteria['id_lang']); + } else { + $languageId = null; + } + + if ($this->metadata['multishop'] && isset($criteria['id_shop'])) { + $shopId = (int) $criteria['id_shop']; + unset($criteria['id_shop']); + } else { + $shopId = null; + } + + $qb = $this->createQueryBuilder('a', $languageId, $shopId); + + $this->applySearchCriteria($qb, $criteria, $shopId); + if (null !== $orderBy) { + $this->applyOrderBy($qb, $orderBy, $shopId); + } + + if (null !== $limit || 0 < (int) $offset) { + $qb->limit($limit, $offset ?? 0); + } + + return $qb; + } + + protected function generateAlias(string $field, string $alias, ?int $shopId = null): string + { + if ( + $this->metadata['multilang'] + && ('id_lang' === $field || !empty($this->metadata['fields'][$field]['lang'])) + ) { + return $alias . 'l'; + } + + if (null !== $shopId && !empty($this->metadata['fields'][$field]['shop'])) { + return $alias . '_shop'; + } + + return $alias; + } + + /** + * @param int|string $field + */ + protected function getFieldType($field): int + { + if ( + $field === $this->metadata['primary'] + || 'id_lang' === $field && $this->metadata['multilang'] + || 'id_shop' === $field && $this->metadata['multishop'] + ) { + return \ObjectModel::TYPE_INT; + } + + if (!isset($this->metadata['fields'][$field])) { + throw new \InvalidArgumentException(sprintf('Field "%s" does not exist in %s.', $field, $this->class)); + } + + return $this->metadata['fields'][$field]['type']; + } + + protected static function escapeQueryParam($value, int $type) + { + return \ObjectModel::formatValue($value, $type, true); + } + + private function applySearchCriteria(QueryBuilder $qb, array $criteria, ?int $shopId = null): void + { + foreach ($criteria as $field => $value) { + if ('id' === $field) { + $field = $this->metadata['primary']; + } + + $type = $this->getFieldType($field); + $alias = $this->generateAlias($field, 'a', $shopId); + + if (null === $value) { + $qb->where(sprintf('%s.%s IS NULL', $alias, $field)); + } elseif (is_array($value)) { + $value = implode(',', array_map(static function ($value) use ($type) { + return self::escapeQueryParam($value, $type); + }, $value)); + $qb->where(sprintf('%s.%s IN (%s)', $alias, $field, $value)); + } else { + $value = self::escapeQueryParam($value, $type); + $qb->where(sprintf('%s.%s = %s', $alias, $field, $value)); + } + } + } + + private function applyOrderBy(QueryBuilder $qb, array $orderBy, ?int $shopId = null): void + { + foreach ($orderBy as $field => $order) { + $order = \Tools::strtoupper($order); + if ('ASC' !== $order && 'DESC' !== $order) { + throw new \InvalidArgumentException(sprintf('"%s" is not a valid order.', $order)); + } + + if ('id' === $field) { + $field = $this->metadata['primary']; + } + + // check that field exists in model + $this->getFieldType($field); + + $alias = $this->generateAlias($field, 'a', $shopId); + $qb->orderBy(sprintf('%s.%s %s', $alias, $field, $order)); + } + } +} diff --git a/modules/inpostizi/src/ObjectModel/Repository/ObjectRepositoryFactory.php b/modules/inpostizi/src/ObjectModel/Repository/ObjectRepositoryFactory.php new file mode 100644 index 00000000..4c3788ce --- /dev/null +++ b/modules/inpostizi/src/ObjectModel/Repository/ObjectRepositoryFactory.php @@ -0,0 +1,37 @@ +locator = $locator; + } + + public function getRepository(ObjectManagerInterface $manager, string $class): ObjectRepositoryInterface + { + if ($this->locator->has($class)) { + return $this->locator->get($class); + } + + return $this->repositories[$class] ?? ($this->repositories[$class] = $this->createRepository($manager, $class)); + } + + private function createRepository(ObjectManagerInterface $manager, string $class): ObjectRepositoryInterface + { + return new ObjectRepository($class, $manager); + } +} diff --git a/modules/inpostizi/src/ObjectModel/Repository/ObjectRepositoryFactoryInterface.php b/modules/inpostizi/src/ObjectModel/Repository/ObjectRepositoryFactoryInterface.php new file mode 100644 index 00000000..16c8faac --- /dev/null +++ b/modules/inpostizi/src/ObjectModel/Repository/ObjectRepositoryFactoryInterface.php @@ -0,0 +1,19 @@ + $class + * + * @return ObjectRepositoryInterface + */ + public function getRepository(ObjectManagerInterface $manager, string $class): ObjectRepositoryInterface; +} diff --git a/modules/inpostizi/src/ObjectModel/Repository/ObjectRepositoryInterface.php b/modules/inpostizi/src/ObjectModel/Repository/ObjectRepositoryInterface.php new file mode 100644 index 00000000..58aa766e --- /dev/null +++ b/modules/inpostizi/src/ObjectModel/Repository/ObjectRepositoryInterface.php @@ -0,0 +1,43 @@ + + */ + public function getClassName(): string; + + /** + * @return T|null + */ + public function find(int $id, ?int $languageId = null, ?int $shopId = null): ?\ObjectModel; + + /** + * @return T[] + */ + public function findAll(?int $languageId = null, ?int $shopId = null): array; + + /** + * @return T|null + */ + public function findOneBy(array $criteria, ?array $orderBy = null): ?\ObjectModel; + + /** + * @return T[] + */ + public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array; + + /** + * @return QueryBuilder + */ + public function createQueryBuilder(string $alias, ?int $languageId = null, ?int $shopId = null): QueryBuilder; +} diff --git a/modules/inpostizi/src/ObjectModel/Repository/ProductRepository.php b/modules/inpostizi/src/ObjectModel/Repository/ProductRepository.php new file mode 100644 index 00000000..3f9ab634 --- /dev/null +++ b/modules/inpostizi/src/ObjectModel/Repository/ProductRepository.php @@ -0,0 +1,90 @@ + + */ +class ProductRepository extends ObjectRepository +{ + public function __construct(ObjectManagerInterface $manager) + { + parent::__construct(\Product::class, $manager); + } + + public function productExists(int $idProduct): bool + { + return null !== $this->find($idProduct); + } + + public function isAvailableOutOfStock(int $productId, ?int $shopId = null): bool + { + $outOfStock = \StockAvailable::outOfStock($productId, $shopId); + + return (bool) \Product::isAvailableWhenOutOfStock($outOfStock); + } + + public function getAvailableStockQuantity(int $productId, ?int $combinationId = null, ?int $shopId = null): int + { + return (int) \StockAvailable::getQuantityAvailableByProduct($productId, $combinationId, $shopId); + } + + public function getAvailableQuantity(int $productId, ?int $combinationId = null, ?\Cart $cart = null): int + { + return (int) \Product::getQuantity($productId, $combinationId, null, $cart); + } + + /** + * @param bool $useDefaultCombination whether a default combination should be returned if the product has combinations + * but no combination ID has been passed + */ + public function findWithCombination(int $productId, ?int $combinationId = null, ?int $languageId = null, ?int $shopId = null, bool $useDefaultCombination = false): ?ProductWithCombination + { + if (null === $product = $this->find($productId, $languageId, $shopId)) { + return null; + } + + if (!$product->cache_default_attribute) { + return null === $combinationId ? new ProductWithCombination($product, null) : null; + } + + if (null === $combinationId && !$useDefaultCombination) { + return null; + } + + $combinationId = $combinationId ?? (int) $product->cache_default_attribute; + $combination = $this->manager->getRepository(\Combination::class)->find($combinationId, null, $shopId); + + if (null === $combination || (int) $combination->id_product !== $productId) { + return null; + } + + return new ProductWithCombination($product, $combination); + } + + public function createSearchQueryBuilder(string $query, int $languageId, int $shopId): QueryBuilder + { + $query = pSQL($query); + + return $this + ->createQueryBuilder('p', $languageId, $shopId) + ->where('pl.name LIKE "%' . $query . '%" OR p.reference LIKE "%' . $query . '%"') + ->where('p_shop.active = 1') + ->where('p_shop.available_for_order = 1') + ->where('p_shop.customizable <> 2') + ->where('p.state = ' . \Product::STATE_SAVED); + } + + public function getProductNameByProductId(int $productId, int $languageId, ?int $combinationId = null): ?string + { + $name = \Product::getProductName($productId, $combinationId, $languageId); + + return false === $name ? null : $name; + } +} diff --git a/modules/inpostizi/src/ObjectModel/Repository/RangePriceRepository.php b/modules/inpostizi/src/ObjectModel/Repository/RangePriceRepository.php new file mode 100644 index 00000000..99202c5d --- /dev/null +++ b/modules/inpostizi/src/ObjectModel/Repository/RangePriceRepository.php @@ -0,0 +1,37 @@ + + */ +class RangePriceRepository extends ObjectRepository +{ + public function __construct(ObjectManagerInterface $manager) + { + parent::__construct(\RangePrice::class, $manager); + } + + public function getMaxPriceRangeByCarrier(\Carrier $carrier): ?float + { + if (null === $carrier->id || 0 >= $carrier->id) { + return null; + } + + $result = $this->manager + ->createQueryBuilder($this->class) + ->select('pr.*') + ->from('range_price', 'pr') + ->where('pr.id_carrier = ' . $carrier->id) + ->orderBy('pr.delimiter2 DESC') + ->limit(1) + ->build() + ->getOneOrNullResult(); + + return null !== $result ? (float) $result->delimiter2 : null; + } +} diff --git a/modules/inpostizi/src/ObjectModel/Repository/RangeWeightRepository.php b/modules/inpostizi/src/ObjectModel/Repository/RangeWeightRepository.php new file mode 100644 index 00000000..07616461 --- /dev/null +++ b/modules/inpostizi/src/ObjectModel/Repository/RangeWeightRepository.php @@ -0,0 +1,37 @@ + + */ +class RangeWeightRepository extends ObjectRepository +{ + public function __construct(ObjectManagerInterface $manager) + { + parent::__construct(\RangeWeight::class, $manager); + } + + public function getMaxWeightRangeByCarrier(\Carrier $carrier): ?float + { + if (null === $carrier->id || 0 >= $carrier->id) { + return null; + } + + $result = $this->manager + ->createQueryBuilder($this->class) + ->select('rw.*') + ->from('range_weight', 'rw') + ->where('rw.id_carrier = ' . $carrier->id) + ->orderBy('rw.delimiter2 DESC') + ->limit(1) + ->build() + ->getOneOrNullResult(); + + return null !== $result ? (float) $result->delimiter2 : null; + } +} diff --git a/modules/inpostizi/src/ObjectModel/Repository/ShipmentRepository.php b/modules/inpostizi/src/ObjectModel/Repository/ShipmentRepository.php new file mode 100644 index 00000000..caae055a --- /dev/null +++ b/modules/inpostizi/src/ObjectModel/Repository/ShipmentRepository.php @@ -0,0 +1,31 @@ + + */ +class ShipmentRepository extends ObjectRepository +{ + public function __construct(ObjectManagerInterface $manager) + { + parent::__construct(\InPostShipmentModel::class, $manager); + } + + /** + * @return \InPostShipmentModel[] + */ + public function findWithTrackingNumbersByOrderId(int $orderId): array + { + return $this + ->createQueryBuilder('s') + ->where('id_order = ' . $orderId) + ->where('s.tracking_number IS NOT NULL') + ->build() + ->getResult(); + } +} diff --git a/modules/inpostizi/src/Order/Address/AddressDataMapper.php b/modules/inpostizi/src/Order/Address/AddressDataMapper.php new file mode 100644 index 00000000..e825cda6 --- /dev/null +++ b/modules/inpostizi/src/Order/Address/AddressDataMapper.php @@ -0,0 +1,53 @@ +getAddressLine($address), + $address->city, + $address->postcode, + sprintf('%s %s', $address->firstname, $address->lastname), + 'PL' + ); + } + + public function mapPhoneNumber(\Address $address): PhoneNumber + { + [$prefix, $phone] = $this->getPhoneNumberData($address); + + return new PhoneNumber($prefix, $phone); + } + + private function getPhoneNumberData(\Address $address): array + { + foreach (['phone', 'phone_mobile'] as $field) { + $value = (string) $address->{$field}; + + if ('' !== $value && preg_match('/^\+\d+ /', $value)) { + return explode(' ', $value, 2); + } + } + + $phone = $address->phone ?: $address->phone_mobile; + + return ['+48', (string) $phone]; + } + + private function getAddressLine(\Address $address): string + { + if (!$address->address2) { + return (string) $address->address1; + } + + return sprintf('%s %s', $address->address1, $address->address2); + } +} diff --git a/modules/inpostizi/src/Order/ContextCustomerUpdater.php b/modules/inpostizi/src/Order/ContextCustomerUpdater.php new file mode 100644 index 00000000..f973c321 --- /dev/null +++ b/modules/inpostizi/src/Order/ContextCustomerUpdater.php @@ -0,0 +1,51 @@ +context = $context; + $this->manager = $manager; + } + + public function updateCustomer(int $orderId): void + { + $order = $this->manager->getRepository(\Order::class)->find($orderId); + + if (null === $order) { + throw new \RuntimeException('Order does not exist.'); + } + + if ((int) $this->context->customer->id === $customerId = (int) $order->id_customer) { + return; + } + + $customer = $this->manager->getRepository(\Customer::class)->find($customerId); + + if (null === $customer) { + throw new \RuntimeException('Customer does not exist.'); + } + + if (!$customer->is_guest) { + throw new \DomainException('Customer is not a guest.'); + } + + $this->context->updateCustomer($customer); + } +} diff --git a/modules/inpostizi/src/Order/Message/ExpressionLanguage.php b/modules/inpostizi/src/Order/Message/ExpressionLanguage.php new file mode 100644 index 00000000..655cd982 --- /dev/null +++ b/modules/inpostizi/src/Order/Message/ExpressionLanguage.php @@ -0,0 +1,17 @@ +functions['constant']); // disallow function usage since PS defines sensitive parameters (e.g. DB password) as constants + } +} diff --git a/modules/inpostizi/src/Order/Message/Message.php b/modules/inpostizi/src/Order/Message/Message.php new file mode 100644 index 00000000..90cd552d --- /dev/null +++ b/modules/inpostizi/src/Order/Message/Message.php @@ -0,0 +1,40 @@ + + */ + private $parameters; + + /** + * @param array $parameters + */ + public function __construct(string $message, array $parameters) + { + $this->message = $message; + $this->parameters = $parameters; + } + + public function getMessage(): string + { + return $this->message; + } + + /** + * @return array + */ + public function getParameters(): array + { + return $this->parameters; + } +} diff --git a/modules/inpostizi/src/Order/Message/MessageFormatter.php b/modules/inpostizi/src/Order/Message/MessageFormatter.php new file mode 100644 index 00000000..91fc5b1c --- /dev/null +++ b/modules/inpostizi/src/Order/Message/MessageFormatter.php @@ -0,0 +1,49 @@ + + */ + private $processors; + + /** + * @param iterable $processors + */ + public function __construct(ParametersExtractorInterface $parametersExtractor, iterable $processors) + { + $this->parametersExtractor = $parametersExtractor; + $this->processors = $processors; + } + + public function format(string $message, CreateOrderRequest $request): string + { + $parameters = $this->parametersExtractor->extract($request); + $formatted = $this + ->processMessage(new Message($message, $parameters)) + ->getMessage(); + + return trim($formatted); + } + + private function processMessage(Message $message): Message + { + foreach ($this->processors as $processor) { + $message = $processor($message); + } + + return $message; + } +} diff --git a/modules/inpostizi/src/Order/Message/MessageFormatterInterface.php b/modules/inpostizi/src/Order/Message/MessageFormatterInterface.php new file mode 100644 index 00000000..8a84ad31 --- /dev/null +++ b/modules/inpostizi/src/Order/Message/MessageFormatterInterface.php @@ -0,0 +1,14 @@ + description by parameter name + */ + public function getDescriptions(): array; +} diff --git a/modules/inpostizi/src/Order/Message/ParametersExtractor.php b/modules/inpostizi/src/Order/Message/ParametersExtractor.php new file mode 100644 index 00000000..d2818878 --- /dev/null +++ b/modules/inpostizi/src/Order/Message/ParametersExtractor.php @@ -0,0 +1,52 @@ +translator = $translator; + } + + public function extract(CreateOrderRequest $request): array + { + return [ + 'request' => $request, + 'order_comments' => $request->getOrderDetails()->getOrderComments(), + 'payment_type' => $request->getOrderDetails()->getPaymentType()->value, + 'delivery_type' => $request->getDelivery()->getType()->value, + 'courier_note' => $request->getDelivery()->getCourierNote(), + 'delivery_codes' => array_map(static function (ServiceCode $code): string { + return $code->value; + }, $deliveryCodes = $request->getDelivery()->getOptionalServiceCodes()), + 'is_cod' => in_array(ServiceCode::Cod(), $deliveryCodes, true), + 'is_pww' => in_array(ServiceCode::Pww(), $deliveryCodes, true), + 'delivery_point' => $request->getDelivery()->getPoint(), + ]; + } + + public function getDescriptions(): array + { + return [ + 'payment_type' => $this->translator->l('code of used payment method', self::TRANSLATION_SOURCE), + 'delivery_point' => $this->translator->l('selected APM identifier', self::TRANSLATION_SOURCE), + 'delivery_codes' => $this->translator->l('codes of selected optional services', self::TRANSLATION_SOURCE), + 'is_pww' => $this->translator->l('if Weekend Delivery option was selected', self::TRANSLATION_SOURCE), + 'is_cod' => $this->translator->l('if Cash on Delivery option was selected', self::TRANSLATION_SOURCE), + ]; + } +} diff --git a/modules/inpostizi/src/Order/Message/ParametersExtractorInterface.php b/modules/inpostizi/src/Order/Message/ParametersExtractorInterface.php new file mode 100644 index 00000000..f3205132 --- /dev/null +++ b/modules/inpostizi/src/Order/Message/ParametersExtractorInterface.php @@ -0,0 +1,15 @@ + parameter value by name + */ + public function extract(CreateOrderRequest $request): array; +} diff --git a/modules/inpostizi/src/Order/Message/Processor/ConditionalBlockProcessor.php b/modules/inpostizi/src/Order/Message/Processor/ConditionalBlockProcessor.php new file mode 100644 index 00000000..31dc3b5d --- /dev/null +++ b/modules/inpostizi/src/Order/Message/Processor/ConditionalBlockProcessor.php @@ -0,0 +1,65 @@ +expressionLanguage = $expressionLanguage ?? new ExpressionLanguage(); + } + + public function __invoke(Message $message): Message + { + if (!str_contains($originalMessage = $message->getMessage(), '{% if ')) { + return $message; + } + + $parameters = $message->getParameters(); + + $result = ''; + $currentBlock = []; + $expression = null; + + foreach (preg_split('/\r\n?|\n/', $originalMessage) as $line) { + if (preg_match('/^\{% if (.*) %}\s*$/', $line, $matches)) { + if ([] !== $currentBlock) { + throw new \LogicException('Nested conditional blocks are not supported.'); + } + + $expression = $matches[1]; + } elseif (preg_match('/^\{% endif %}\s*$/', $line)) { + if (null === $expression) { + throw new \LogicException('Syntax error: unexpected "endif".'); + } + + if ($this->expressionLanguage->evaluate($expression, $parameters)) { + $result .= implode(PHP_EOL, $currentBlock) . PHP_EOL; + } + + $currentBlock = []; + $expression = null; + } elseif (null !== $expression) { + $currentBlock[] = $line; + } else { + $result .= $line . PHP_EOL; + } + } + + if ([] !== $currentBlock) { + throw new \LogicException('Syntax error: unclosed "if" block.'); + } + + return new Message($result, $parameters); + } +} diff --git a/modules/inpostizi/src/Order/Message/Processor/ExpressionLanguageProcessor.php b/modules/inpostizi/src/Order/Message/Processor/ExpressionLanguageProcessor.php new file mode 100644 index 00000000..5de0fd86 --- /dev/null +++ b/modules/inpostizi/src/Order/Message/Processor/ExpressionLanguageProcessor.php @@ -0,0 +1,42 @@ +expressionLanguage = $expressionLanguage ?? new ExpressionLanguage(); + } + + public function __invoke(Message $message): Message + { + if (!str_contains($result = $message->getMessage(), '{{')) { + return $message; + } + + if (!preg_match_all('/\{\{\s*(.*)?\s*}}/', $result, $matches, PREG_SET_ORDER)) { + return $message; + } + + $newParameters = $parameters = $message->getParameters(); + + foreach ($matches as $i => $match) { + $key = sprintf('expression_%d', $i); + $result = str_replace($match[0], sprintf('{%s}', $key), $result); + $newParameters[$key] = $this->expressionLanguage->evaluate($match[1], $parameters); + } + + return new Message($result, $newParameters); + } +} diff --git a/modules/inpostizi/src/Order/Message/Processor/ParameterReplacementProcessor.php b/modules/inpostizi/src/Order/Message/Processor/ParameterReplacementProcessor.php new file mode 100644 index 00000000..a0827f0e --- /dev/null +++ b/modules/inpostizi/src/Order/Message/Processor/ParameterReplacementProcessor.php @@ -0,0 +1,84 @@ +dateFormat = $dateFormat ?? \DateTime::ATOM; + } + + public function __invoke(Message $message): Message + { + $originalMessage = $message->getMessage(); + + if (!str_contains($originalMessage, '{')) { + return $message; + } + + $replacements = []; + $parameters = $message->getParameters(); + + foreach ($parameters as $name => $value) { + $placeholder = sprintf('{%s}', $name); + if (!str_contains($originalMessage, $placeholder)) { + continue; + } + + $replacements[$placeholder] = $this->formatReplacement($value); + } + + return new Message(strtr($originalMessage, $replacements), $parameters); + } + + private function formatReplacement($value): string + { + if (is_bool($value)) { + return $value ? 'true' : 'false'; + } + + if (null === $value || is_scalar($value)) { + return (string) $value; + } + + if ($value instanceof Enum) { + return (string) $value->value; + } + + if ($value instanceof \DateTimeInterface) { + return $value->format($this->dateFormat); + } + + if ($value instanceof \JsonSerializable) { + return json_encode($value); + } + + if (is_array($value)) { + return $this->formatArray($value); + } + + return sprintf('[%s]', get_debug_type($value)); + } + + private function formatArray(array $value): string + { + if (is_int(key($value)) || false === $json = json_encode($value)) { + $formatted = array_map([$this, 'formatReplacement'], $value); + + return sprintf('[%s]', implode(', ', $formatted)); + } + + return $json; + } +} diff --git a/modules/inpostizi/src/Order/Message/Processor/ProcessorInterface.php b/modules/inpostizi/src/Order/Message/Processor/ProcessorInterface.php new file mode 100644 index 00000000..882deb53 --- /dev/null +++ b/modules/inpostizi/src/Order/Message/Processor/ProcessorInterface.php @@ -0,0 +1,12 @@ += $currencyId) { + return false; + } + + if (!$paymentModule->currencies) { + return true; + } + + $currencies = $paymentModule->getCurrency($currencyId); + + if (false === $currencies) { + return false; + } + + if ($currencies instanceof \Currency) { + return $currencyId === (int) $currencies->id; + } + + return in_array($currencyId, array_map('intval', array_column($currencies, 'id_currency')), true); + } +} diff --git a/modules/inpostizi/src/PrestashopOrder.php b/modules/inpostizi/src/PrestashopOrder.php new file mode 100644 index 00000000..349633ed --- /dev/null +++ b/modules/inpostizi/src/PrestashopOrder.php @@ -0,0 +1,450 @@ +order = $order; + $this->basketId = $basketId; + $this->orderData = $orderData; + $this->basketAnalytics = $basketAnalytics; + + $this->module = \Module::getInstanceByName('inpostizi'); + + $this->deliveryDetails = new \Address((int) $this->order->id_address_delivery); + $this->customer = $this->order->getCustomer(); + $this->language = new \Language((int) $this->order->id_lang); + + $this->addressDataMapper = new AddressDataMapper(); + } + + public static function getOrder(\Order $order, string $basketId, ?CreateOrderRequest $request = null, ?BasketAnalyticsInterface $basketAnalytics = null): Order + { + return (new self($order, $basketId, $request, $basketAnalytics))->mapOrder(); + } + + public function mapOrder(): Order + { + return new Order( + $this->mapOrderDetails(), + $this->mapAccountInfo(), + $this->mapDelivery(), + $this->mapProducts(), + $this->mapConsents(), + $this->mapInvoiceDetails() + ); + } + + /** + * @return Consent[] + */ + public function mapConsents(): array + { + if (null !== $this->orderData) { + return $this->orderData->getConsents(); + } + + $config = json_decode($this->getConfiguration('INPOST_PAY_CONSENTS'), true) ?? []; + + if ([] === $config) { + return []; + } + + $consents = []; + + foreach ($config as $consent) { + $date = \DateTimeImmutable::createFromFormat(\DateTime::RFC3339, $consent['dateUpdated']); + + $consents[] = new Consent( + $consent['link']['id'], + false === $date ? '0' : (string) $date->getTimestamp(), + $consent['requirementType'] !== ConsentRequirementType::Optional()->value + ); + } + + return $consents; + } + + public function mapAccountInfo(): AccountInfo + { + if (null !== $this->orderData) { + return AccountInfo::fromOrderRequestData($this->orderData->getAccountInfo()); + } + + return new AccountInfo( + (string) $this->customer->firstname, + (string) $this->customer->lastname, + $this->mapPhoneNumber(), + (string) $this->customer->email, + $this->mapClientAddress() + ); + } + + /** + * @return Product[] + */ + public function mapProducts(): array + { + return array_map([$this, 'createProduct'], $this->order->getProductsDetail()); + } + + public function mapClientAddress(): ClientAddress + { + return new ClientAddress( + (string) \Country::getIsoById($this->deliveryDetails->id_country), + $this->deliveryDetails->address1 . ' ' . $this->deliveryDetails->address2, + (string) $this->deliveryDetails->city, + (string) $this->deliveryDetails->postcode + ); + } + + public function mapInvoiceDetails(): ?InvoiceDetails + { + if (null === $this->orderData) { + return null; + } + + return $this->orderData->getInvoiceDetails(); + } + + public function mapDelivery(): Delivery + { + if (null !== $this->orderData) { + $delivery = $this->orderData->getDelivery(); + $deliveryType = $delivery->getType(); + $deliveryCodes = $delivery->getOptionalServiceCodes(); + $email = $delivery->getEmail() ?? $this->customer->email; + $phoneNumber = $delivery->getPhoneNumber() ?? $this->mapPhoneNumber(); + $deliveryPoint = $delivery->getPoint(); + $courierNote = $delivery->getCourierNote(); + } else { + $deliveryType = DeliveryType::Courier(); + $deliveryCodes = []; + $email = $this->customer->email; + $phoneNumber = $this->mapPhoneNumber(); + $deliveryPoint = null; + $courierNote = $this->deliveryDetails->other; + } + + $deliveryDate = \DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $this->order->date_add) + ->modify('+2 days') + ->setTime(12, 0); + + $deliveryOptions = array_map(static function (ServiceCode $code): OptionalService { + $serviceNameDictionary = [ + 'PWW' => 'Paczka w Weekend', + 'COD' => 'Pobranie', + ]; + + return new OptionalService( + $serviceNameDictionary[$code->value] ?? $code->value, + $code, + PriceFactory::create(0., 0.) // TODO: get the actually used prices + ); + }, $deliveryCodes); + + return new Delivery( + $deliveryType, + $deliveryDate, + $this->getDeliveryPrice(), + $deliveryOptions, + $email, + $phoneNumber, + $deliveryPoint, + $this->addressDataMapper->mapDeliveryAddress($this->deliveryDetails), + $courierNote + ); + } + + public function mapPhoneNumber(): PhoneNumber + { + return $this->addressDataMapper->mapPhoneNumber($this->deliveryDetails); + } + + private function readComments(): ?string + { + if (null !== $this->orderData) { + return $this->orderData->getOrderDetails()->getOrderComments(); + } + + return $this->order->getFirstMessage() ?: null; + } + + public function mapOrderDetails(): OrderDetails + { + return new OrderDetails( + (string) $this->order->id, + (string) $this->getConfiguration('INPOST_PAY_pos_id'), + \DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $this->order->date_add), + $this->basketId, + (string) $this->getStatusDescription($this->order), + $this->readPaymentType(), + $this->readSummaryOrderBasePrice(), + $this->readSummaryOrderFinalPrice(), + Currency::Pln(), + $this->getTrackingNumbers(), + $this->readComments(), + $this->getDiscountsTotal(), + $this->order->reference, + $this->readOrderAdditionalParameters() + ); + } + + private function readOrderAdditionalParameters(): ?OrderAdditionalParameters + { + $sendAnalyticsData = $this->getConfiguration('INPOST_PAY_SEND_ANALYTICS_DATA'); + + if (!$sendAnalyticsData || null === $this->basketAnalytics) { + return null; + } + + $orderAdditionalParameters = new OrderAdditionalParameters(); + + if (null !== $this->basketAnalytics->getClientId()) { + $orderAdditionalParameters->addParameter(new OrderAdditionalParameter('client_id', $this->basketAnalytics->getClientId())); + } + + if (null !== $this->basketAnalytics->getFbclid()) { + $orderAdditionalParameters->addParameter(new OrderAdditionalParameter('fbclid', $this->basketAnalytics->getFbclid())); + } + + if (null !== $this->basketAnalytics->getGclid()) { + $orderAdditionalParameters->addParameter(new OrderAdditionalParameter('gclid', $this->basketAnalytics->getGclid())); + } + + return $orderAdditionalParameters; + } + + public function readSummaryOrderFinalPrice(): Price + { + return PriceFactory::create( + (float) $this->order->total_paid_tax_excl, + (float) $this->order->total_paid_tax_incl + ); + } + + public function readSummaryOrderBasePrice(): Price + { + $gross = (float) $this->order->total_paid_tax_incl; + $net = (float) $this->order->total_paid_tax_excl; + + if (!$this->hasFreeShippingCartRule()) { + $gross -= $this->order->total_shipping_tax_incl; + $net -= $this->order->total_shipping_tax_excl; + } + + return PriceFactory::create($net, $gross); + } + + public function readPaymentType(): PaymentType + { + if (null === $this->orderData) { + return PaymentType::Card(); + } + + return $this->orderData->getOrderDetails()->getPaymentType(); + } + + /** + * @return false|string + */ + private function getConfiguration(string $key, bool $lang = false) + { + $languageId = $lang ? (int) $this->order->id_lang : null; + + return \Configuration::get($key, $languageId, null, $this->order->id_shop); + } + + private function getStatusDescription(\Order $order): string + { + $orderStateId = (int) $order->current_state; + $config = $this->getConfiguration('INPOST_PAY_OS_DESCRIPTION_MAP', true); + $map = $config ? json_decode($config, true) : []; + + return $map[$orderStateId] ?? (new \OrderState($orderStateId, $order->id_lang))->name; + } + + private function getTrackingNumbers(): array + { + $objectManager = $this->module->get(ObjectManagerInterface::class); + + return (new CarrierModuleTrackingNumberProvider($objectManager))->getTrackingNumbers((int) $this->order->id); + } + + private function getDiscountsTotal(): float + { + return (float) \Tools::math_round($this->order->total_discounts_tax_incl, 2); + } + + private function createProduct(array $data): Product + { + if (0 >= (int) $data['product_attribute_id'] || false === $pos = strrpos($data['product_name'], '(', -1)) { + $productName = $data['product_name']; + $attributes = []; + } else { + $productName = trim(substr($data['product_name'], 0, $pos)); + $attributes = $this->getProductAttributes(substr($data['product_name'], $pos + 1, -1)); + } + + $model = new \Product((int) $data['product_id'], false, $this->order->id_lang, (int) $data['id_shop']); + + if (\Validate::isLoadedObject($model)) { + $category = $model->id_category_default; + $description = DescriptionFormatter::formatDescription($model); + $link = \Context::getContext()->link->getProductLink($model, null, null, null, $this->order->id_lang, $this->order->id_shop, $data['product_attribute_id']); + + $imageUrls = $this->getImageProvider()->getImageUrls((int) $data['product_id'], (int) $data['product_attribute_id'], $this->language, (int) $this->order->id_shop); + $imageUrl = $imageUrls->getMainImageUrl(); + $additionalImages = $imageUrls->getAdditionalImages(); + } else { + $category = $description = $link = $imageUrl = null; + $additionalImages = []; + } + + return new Product( + (string) ReferenceId::create((int) $data['product_id'], (int) $data['product_attribute_id'], (int) $data['id_customization']), + $productName, + PriceFactory::create((float) $data['unit_price_tax_excl'], (float) $data['unit_price_tax_incl']), + Quantity::integer((int) $data['product_quantity']), + $category, + $data['product_ean13'], + $description, + $link, + $imageUrl, + $attributes, + [], + $additionalImages + ); + } + + private function getProductAttributes(string $attributes): array + { + return array_map(static function (array $attribute) { + return new ProductAttribute($attribute['group'], $attribute['name']); + }, $this->getAttributeListParser()->parse($attributes, (int) $this->order->id_shop)); + } + + private function hasFreeShippingCartRule(): bool + { + if (isset($this->freeShipping)) { + return $this->freeShipping; + } + + foreach ($this->order->getCartRules() as $cartRule) { + if ($cartRule['free_shipping']) { + return $this->freeShipping = true; + } + } + + return $this->freeShipping = false; + } + + private function getDeliveryPrice(): Price + { + if ($this->hasFreeShippingCartRule()) { + return PriceFactory::create(0., 0.); + } + + return PriceFactory::create( + (float) $this->order->total_shipping_tax_excl, + (float) $this->order->total_shipping_tax_incl + ); + } + + private function getAttributeListParser(): AttributeListParser + { + return $this->attributeListParser ?? $this->attributeListParser = new AttributeListParser( + new PrestaShopConfiguration(new Configuration()), + \Context::getContext(), + _PS_VERSION_ + ); + } + + private function getImageProvider(): ImageUrlsProvider + { + return $this->imageProvider ?? $this->imageProvider = ImageUrlsProvider::create(); + } +} diff --git a/modules/inpostizi/src/Product/Event/CombinationEvent.php b/modules/inpostizi/src/Product/Event/CombinationEvent.php new file mode 100644 index 00000000..649f086b --- /dev/null +++ b/modules/inpostizi/src/Product/Event/CombinationEvent.php @@ -0,0 +1,32 @@ +combination = $combination; + } + + public function getCombination(): \Combination + { + return $this->combination; + } +} diff --git a/modules/inpostizi/src/Product/Event/ImageEvent.php b/modules/inpostizi/src/Product/Event/ImageEvent.php new file mode 100644 index 00000000..3bf64372 --- /dev/null +++ b/modules/inpostizi/src/Product/Event/ImageEvent.php @@ -0,0 +1,28 @@ +image = $image; + } + + public function getImage(): \Image + { + return $this->image; + } +} diff --git a/modules/inpostizi/src/Product/Event/ProductEvent.php b/modules/inpostizi/src/Product/Event/ProductEvent.php new file mode 100644 index 00000000..9332329c --- /dev/null +++ b/modules/inpostizi/src/Product/Event/ProductEvent.php @@ -0,0 +1,32 @@ +product = $product; + } + + public function getProduct(): \Product + { + return $this->product; + } +} diff --git a/modules/inpostizi/src/Product/Event/SpecificPriceEvent.php b/modules/inpostizi/src/Product/Event/SpecificPriceEvent.php new file mode 100644 index 00000000..bdea8adc --- /dev/null +++ b/modules/inpostizi/src/Product/Event/SpecificPriceEvent.php @@ -0,0 +1,29 @@ +price = $price; + } + + public function getPrice(): \SpecificPrice + { + return $this->price; + } +} diff --git a/modules/inpostizi/src/Product/Event/StockQuantityUpdatedEvent.php b/modules/inpostizi/src/Product/Event/StockQuantityUpdatedEvent.php new file mode 100644 index 00000000..fd02f670 --- /dev/null +++ b/modules/inpostizi/src/Product/Event/StockQuantityUpdatedEvent.php @@ -0,0 +1,69 @@ +productId = $productId; + $this->combinationId = $combinationId; + $this->shopId = $shopId; + $this->quantity = $quantity; + $this->deltaQuantity = $deltaQuantity; + } + + public function getProductId(): int + { + return $this->productId; + } + + public function getCombinationId(): int + { + return $this->combinationId; + } + + public function getShopId(): int + { + return $this->shopId; + } + + public function getQuantity(): int + { + return $this->quantity; + } + + public function getDeltaQuantity(): ?int + { + return $this->deltaQuantity; + } +} diff --git a/modules/inpostizi/src/Product/Image/ImageUrls.php b/modules/inpostizi/src/Product/Image/ImageUrls.php new file mode 100644 index 00000000..03f68360 --- /dev/null +++ b/modules/inpostizi/src/Product/Image/ImageUrls.php @@ -0,0 +1,47 @@ +mainImageUrl = $mainImageUrl; + $this->additionalImages = $additionalImages; + } + + public static function createEmpty(): self + { + return new self(null, []); + } + + public function getMainImageUrl(): ?string + { + return $this->mainImageUrl; + } + + /** + * @return ProductImage[] + */ + public function getAdditionalImages(): array + { + return $this->additionalImages; + } +} diff --git a/modules/inpostizi/src/Product/Image/ImageUrlsProvider.php b/modules/inpostizi/src/Product/Image/ImageUrlsProvider.php new file mode 100644 index 00000000..22c0a009 --- /dev/null +++ b/modules/inpostizi/src/Product/Image/ImageUrlsProvider.php @@ -0,0 +1,165 @@ + + */ + private $imageTypes = []; + + /** + * @param ObjectRepositoryInterface<\ImageType> $imageTypeRepository + */ + public function __construct(ImageRetriever $imageRetriever, ProductConfigurationInterface $productConfiguration, \Context $context, ObjectRepositoryInterface $imageTypeRepository) + { + $this->imageRetriever = $imageRetriever; + $this->productConfiguration = $productConfiguration; + $this->context = $context; + $this->imageTypeRepository = $imageTypeRepository; + } + + /** + * @internal + */ + public static function create(?ImageRetriever $imageRetriever = null, \Context $context = null, ?ProductConfigurationInterface $configuration = null): self + { + /** @var \InPostIzi $module */ + $module = \Module::getInstanceByName('inpostizi'); + + $context = $context ?? \Context::getContext(); + $imageRetriever = $imageRetriever ?? new ImageRetriever($context->link); + $configuration = $configuration ?? $module->get(ProductConfigurationInterface::class); + + return new self( + $imageRetriever, + $configuration, + $context, + $module->get(ObjectManagerInterface::class)->getRepository(\ImageType::class) + ); + } + + public function getImageUrls(int $productId, ?int $combinationId, ?\Language $language = null, ?int $shopId = null): ImageUrls + { + $images = $this->imageRetriever->getProductImages([ + 'id_product' => $productId, + 'id_product_attribute' => (int) $combinationId, + ], $language ?? $this->context->language); + + if ([] === $images) { + return ImageUrls::createEmpty(); + } + + $shopId = $shopId ?? (int) $this->context->shop->id; + + $coverUrl = $this->getCoverUrl($images, $shopId); + $additionalImages = $this->getAdditionalImages($images, $shopId); + + return new ImageUrls($coverUrl, $additionalImages); + } + + private function getCoverUrl(array $images, int $shopId): ?string + { + if (null === $image = $this->getCover($images)) { + return null; + } + + $imageType = $this->getImageType($shopId, 'normal'); + $image = $image['bySize'][$imageType] ?? $image['small']; + + return $image['url']; + } + + private function getCover(array $images): ?array + { + foreach ($images as $image) { + if (!empty($image['cover'])) { + return $image; + } + } + + if (false !== $image = reset($images)) { + return $image; + } + + return null; + } + + /** + * @return ProductImage[] + */ + private function getAdditionalImages(array $images, int $shopId): array + { + $images = array_slice($images, 0, 10); + $imageTypes = $this->getImageTypes($shopId); + + return array_map(static function (array $image) use ($imageTypes): ProductImage { + $smallSize = $image['bySize'][$imageTypes['small']] ?? $image['medium']; + $normalSize = $image['bySize'][$imageTypes['large']] ?? $image['large']; + + return new ProductImage($smallSize['url'], $normalSize['url']); + }, $images); + } + + private function getImageType(int $shopId, string $name): string + { + return $this->getImageTypes($shopId)[$name]; + } + + /** + * @return ImageTypes + */ + private function getImageTypes(int $shopId): array + { + return $this->imageTypes[$shopId] ?? $this->imageTypes[$shopId] = [ + 'small' => $this->getTypeNameById($this->productConfiguration->getSmallImageTypeId($shopId), 'home_default'), + 'normal' => $this->getTypeNameById($this->productConfiguration->getNormalImageTypeId($shopId), 'cart_default'), + 'large' => $this->getTypeNameById($this->productConfiguration->getLargeImageTypeId($shopId), 'medium_default'), + ]; + } + + private function getTypeNameById(?int $imageTypeId, string $default): string + { + if (null === $imageTypeId) { + return $default; + } + + if (null === $imageType = $this->imageTypeRepository->find($imageTypeId)) { + return $default; + } + + return $imageType->name ?? $default; + } +} diff --git a/modules/inpostizi/src/Product/Image/ImageUrlsProviderInterface.php b/modules/inpostizi/src/Product/Image/ImageUrlsProviderInterface.php new file mode 100644 index 00000000..250cc2c0 --- /dev/null +++ b/modules/inpostizi/src/Product/Image/ImageUrlsProviderInterface.php @@ -0,0 +1,8 @@ +shopId = $shopId; + $this->currencyId = $currencyId; + $this->countryId = $countryId; + $this->customerGroupId = $customerGroupId; + } + + public function getShopId(): int + { + return $this->shopId; + } + + public function getCurrencyId(): int + { + return $this->currencyId; + } + + public function getCountryId(): int + { + return $this->countryId; + } + + public function getCustomerGroupId(): int + { + return $this->customerGroupId; + } +} diff --git a/modules/inpostizi/src/Product/Price/ErrorHandlingLowestPriceProvider.php b/modules/inpostizi/src/Product/Price/ErrorHandlingLowestPriceProvider.php new file mode 100644 index 00000000..f145c3e7 --- /dev/null +++ b/modules/inpostizi/src/Product/Price/ErrorHandlingLowestPriceProvider.php @@ -0,0 +1,70 @@ +provider = $provider; + $this->logger = $logger; + } + + public function preparePrices(LowestPriceQuery ...$queries): void + { + if (!$this->provider instanceof LowestPriceProviderInterface) { + return; + } + + try { + $this->provider->preparePrices(...$queries); + } catch (\Throwable $e) { + $this->logError($e, __METHOD__); + } + } + + public function getPrice(LowestPriceQuery $query): ?Price + { + try { + return $this->provider->getPrice($query); + } catch (\Throwable $e) { + $this->logError($e, __METHOD__); + + return null; + } + } + + public function reset(): void + { + if (!$this->provider instanceof ResetInterface) { + return; + } + + $this->provider->reset(); + } + + private function logError(\Throwable $e, string $method): void + { + $this->logger->error('Lowest price provider "{class}::{method}()" error: {exception}', [ + 'class' => get_class($this->provider), + 'method' => $method, + 'exception' => $e, + ]); + } +} diff --git a/modules/inpostizi/src/Product/Price/LowestPriceProviderFactory.php b/modules/inpostizi/src/Product/Price/LowestPriceProviderFactory.php new file mode 100644 index 00000000..912a002f --- /dev/null +++ b/modules/inpostizi/src/Product/Price/LowestPriceProviderFactory.php @@ -0,0 +1,52 @@ +moduleRepository = $moduleRepository; + $this->logger = $logger; + } + + public function create(): LowestPriceProviderInterface + { + return $this->createX13Provider() ?? new NullLowestPriceProvider(); + } + + private function createX13Provider(): ?LowestPriceProviderInterface + { + $module = $this->moduleRepository->findByName('x13pricehistory'); + + if (null === $module || !$module->active) { + return null; + } + + if (null === $priceProvider = X13PriceHistoryLowestPriceProvider::create($module)) { + return null; + } + + return $this->createErrorHandlingProvider($priceProvider); + } + + private function createErrorHandlingProvider(LowestPriceProviderInterface $provider): ErrorHandlingLowestPriceProvider + { + return new ErrorHandlingLowestPriceProvider($provider, $this->logger); + } +} diff --git a/modules/inpostizi/src/Product/Price/LowestPriceProviderInterface.php b/modules/inpostizi/src/Product/Price/LowestPriceProviderInterface.php new file mode 100644 index 00000000..431a07ea --- /dev/null +++ b/modules/inpostizi/src/Product/Price/LowestPriceProviderInterface.php @@ -0,0 +1,12 @@ +productId = $productId; + $this->shopId = $shopId; + $this->currencyId = $currencyId; + $this->countryId = $countryId; + $this->customerGroupId = $customerGroupId; + $this->combinationId = $combinationId; + } + + public function getProductId(): int + { + return $this->productId; + } + + public function getShopId(): int + { + return $this->shopId; + } + + public function getCurrencyId(): int + { + return $this->currencyId; + } + + public function getCountryId(): int + { + return $this->countryId; + } + + public function getCustomerGroupId(): int + { + return $this->customerGroupId; + } + + public function getCombinationId(): ?int + { + return $this->combinationId; + } +} diff --git a/modules/inpostizi/src/Product/Price/NullLowestPriceProvider.php b/modules/inpostizi/src/Product/Price/NullLowestPriceProvider.php new file mode 100644 index 00000000..434b8b9c --- /dev/null +++ b/modules/inpostizi/src/Product/Price/NullLowestPriceProvider.php @@ -0,0 +1,15 @@ + country IDs by shop ID + */ + private $countryIds = []; + + /** + * @param CurrencyRepository $currencyRepository + * @param ObjectRepositoryInterface<\Country> $countryRepository + */ + public function __construct(PrestaShopConfiguration $configuration, ObjectRepositoryInterface $currencyRepository, ObjectRepositoryInterface $countryRepository) + { + $this->configuration = $configuration; + $this->currencyRepository = $currencyRepository; + $this->countryRepository = $countryRepository; + } + + public function calculatePrice(PriceQuery $query): ?Price + { + $parameters = $this->getCalculationParameters( + $query->getCurrency(), + $query->getShopId() + ); + + $net = $this->getPrice($query, false, $parameters); + $gross = $this->getPrice($query, true, $parameters); + + return PriceFactory::create($net, $gross); + } + + public function getCalculationParameters(Currency $currency, ?int $shopId = null): CalculationParameters + { + $shopId = $shopId ?? $this->configuration->getDefaultShopId(); + + $currencyId = $this->getCurrencyId($currency->value, $shopId); + $countryId = $this->getCountryId($shopId); + $groupId = $this->configuration->getAnonymousCustomerGroupId($shopId); + + return new CalculationParameters($shopId, $currencyId, $countryId, $groupId); + } + + private function getPrice(PriceQuery $query, bool $withTax, CalculationParameters $parameters): float + { + return (float) \Product::priceCalculation( + $query->getShopId(), + $query->getProductId(), + $query->getCombinationId(), + $parameters->getCountryId(), + 0, + '', + $parameters->getCurrencyId(), + $parameters->getCustomerGroupId(), + 1, + $withTax, + 6, + false, + true, + true, + $specificPrice, + true + ); + } + + private function getCurrencyId(string $isoCode, int $shopId): int + { + $currency = $this->currencyRepository->findOneByIsoCode($isoCode, $shopId); + + if (null === $currency) { + throw new \RuntimeException('Currency not found.'); + } + + return (int) $currency->id; + } + + private function getCountryId(int $shopId): int + { + if (isset($this->countryIds[$shopId])) { + return $this->countryIds[$shopId]; + } + + $country = $this->countryRepository->findOneBy([ + 'iso_code' => 'PL', + 'id_shop' => $shopId, + ], ['active' => 'DESC']); + + if (null !== $country) { + return $this->countryIds[$shopId] = (int) $country->id; + } + + return $this->countryIds[$shopId] = $this->configuration->getDefaultCountryId($shopId); + } +} diff --git a/modules/inpostizi/src/Product/Price/PriceCalculatorInterface.php b/modules/inpostizi/src/Product/Price/PriceCalculatorInterface.php new file mode 100644 index 00000000..510a307d --- /dev/null +++ b/modules/inpostizi/src/Product/Price/PriceCalculatorInterface.php @@ -0,0 +1,15 @@ +productId = $productId; + $this->shopId = $shopId; + $this->currency = $currency ?? Currency::getDefault(); + $this->combinationId = $combinationId; + } + + public function getProductId(): int + { + return $this->productId; + } + + public function getShopId(): int + { + return $this->shopId; + } + + public function getCurrency(): Currency + { + return $this->currency; + } + + public function getCombinationId(): ?int + { + return $this->combinationId; + } +} diff --git a/modules/inpostizi/src/Product/Price/X13PriceHistoryLowestPriceProvider.php b/modules/inpostizi/src/Product/Price/X13PriceHistoryLowestPriceProvider.php new file mode 100644 index 00000000..4cedaf0d --- /dev/null +++ b/modules/inpostizi/src/Product/Price/X13PriceHistoryLowestPriceProvider.php @@ -0,0 +1,172 @@ +> + * } + */ +final class X13PriceHistoryLowestPriceProvider implements BatchLowestPriceProviderInterface +{ + /** + * @var BatchLowestPriceProvider + */ + private $priceProvider; + + /** + * @var array>|null prices by product and combination ID + */ + private $prices; + + /** + * @param BatchLowestPriceProvider $priceProvider + */ + public function __construct($priceProvider) + { + $this->priceProvider = $priceProvider; + } + + /** + * @param \X13PriceHistory|\Module $module + */ + public static function create(\Module $module): ?self + { + if (!property_exists($module, 'batchLowestPriceProvider')) { + return null; + } + + /** @var \x13pricehistory\Providers\BatchLowestPriceProvider $priceProvider */ + $priceProvider = $module->batchLowestPriceProvider; + + if (!is_callable([$priceProvider, 'getPricesForProductList'])) { + return null; + } + + return new self($priceProvider); + } + + public function preparePrices(LowestPriceQuery ...$queries): void + { + if ([] === $queries) { + $this->prices = []; + + return; + } + + $this->prices = $this->doGetPrices(...$queries); + } + + public function getPrice(LowestPriceQuery $query): ?Price + { + $prices = $this->prices ?? $this->doGetPrices($query); + + $productId = $query->getProductId(); + $combinationId = (int) $query->getCombinationId(); + + return $prices[$productId][$combinationId] ?? null; + } + + public function reset(): void + { + $this->prices = null; + } + + /** + * @return array> prices by product and combination ID + */ + private function doGetPrices(LowestPriceQuery ...$queries): array + { + $groupedQueries = []; + array_map(function ($query) use (&$groupedQueries) { + $groupedQueries[$query->getShopId()][] = $query; + }, $queries); + + $prices = []; + foreach ($groupedQueries as $idShop => $shopQueries) { + $productIds = array_map([$this, 'getProductId'], $shopQueries); + $query = current($shopQueries); + + $prices += $this->getPricesForShop($idShop, $productIds, $query); + } + + return $prices; + } + + private function getPricesForShop(int $idShop, array $productIds, LowestPriceQuery $query) + { + $args = [ + $productIds, + $idShop, + $query->getCurrencyId(), + $query->getCountryId(), + $query->getCustomerGroupId(), + false, + ]; + + if ([] === $netPrices = $this->priceProvider->getPricesForProductList(...$args)) { + return []; + } + + $args[5] = true; + $grossPrices = $this->priceProvider->getPricesForProductList(...$args); + + $prices = []; + + foreach ($productIds as $product) { + $productId = $product['id_product']; + $combinationId = $product['id_product_attribute']; + + if (null === $netPrice = $netPrices[$productId][$combinationId] ?? $netPrices[$productId][0] ?? null) { + continue; + } + + if (null === $grossPrice = $grossPrices[$productId][$combinationId] ?? $grossPrices[$productId][0] ?? null) { + continue; + } + + if (!isset($netPrice['lowest_price_amount'], $grossPrice['lowest_price_amount'])) { + continue; + } + + $prices[$productId][$combinationId] = PriceFactory::create( + (float) $netPrice['lowest_price_amount'], + (float) $grossPrice['lowest_price_amount'] + ); + } + + return $prices; + } + + private function getProductId(LowestPriceQuery $query): array + { + return [ + 'id_product' => $query->getProductId(), + 'id_product_attribute' => (int) $query->getCombinationId(), + ]; + } +} diff --git a/modules/inpostizi/src/Product/ProductAttribute.php b/modules/inpostizi/src/Product/ProductAttribute.php new file mode 100644 index 00000000..25d000f8 --- /dev/null +++ b/modules/inpostizi/src/Product/ProductAttribute.php @@ -0,0 +1,49 @@ +id_attribute_group !== (int) $group->id) { + throw new \InvalidArgumentException('Attribute does not belong to the given group.'); + } + + $this->attribute = $attribute; + $this->group = $group; + } + + /** + * @return T + */ + public function getAttribute(): \ObjectModel + { + return $this->attribute; + } + + public function getGroup(): \AttributeGroup + { + return $this->group; + } +} diff --git a/modules/inpostizi/src/Product/ProductType.php b/modules/inpostizi/src/Product/ProductType.php new file mode 100644 index 00000000..a9812cd4 --- /dev/null +++ b/modules/inpostizi/src/Product/ProductType.php @@ -0,0 +1,67 @@ +l('Standard products', 'producttype'); + case self::Combination(): + return $translator->l('Products with combinations', 'producttype'); + case self::Customizable(): + return $translator->l('Customizable products', 'producttype'); + case self::Pack(): + return $translator->l('Packs of products', 'producttype'); + case self::Virtual(): + return $translator->l('Virtual products', 'producttype'); + default: + throw new \LogicException('Unreachable statement.'); + } + } + + /** + * @param ProductLazyArray|array $product + */ + public static function fromProductData($product): self + { + if (!is_array($product) && !$product instanceof \ArrayAccess) { + throw new \InvalidArgumentException(sprintf('Expected $product to be an array or an instance of "%s", "%s" given.', ProductLazyArray::class, get_debug_type($product))); + } + + if ($product['is_virtual'] ?? false) { + return self::Virtual(); + } + + if ($product['pack'] ?? false) { + return self::Pack(); + } + + if (0 < (int) ($product['id_product_attribute'] ?? 0)) { + return self::Combination(); + } + + return self::Standard(); + } +} diff --git a/modules/inpostizi/src/Product/ProductWithCombination.php b/modules/inpostizi/src/Product/ProductWithCombination.php new file mode 100644 index 00000000..85f5b89c --- /dev/null +++ b/modules/inpostizi/src/Product/ProductWithCombination.php @@ -0,0 +1,43 @@ +id_product !== (int) $product->id) { + throw new \InvalidArgumentException('Combination does not belong to the product.'); + } + + $this->product = $product; + $this->combination = $combination; + } + + public function getProduct(): \Product + { + return $this->product; + } + + public function getCombination(): ?\Combination + { + return $this->combination; + } + + public function hasCombination(): bool + { + return null !== $this->combination; + } +} diff --git a/modules/inpostizi/src/Product/ReferenceId.php b/modules/inpostizi/src/Product/ReferenceId.php new file mode 100644 index 00000000..1fbd387f --- /dev/null +++ b/modules/inpostizi/src/Product/ReferenceId.php @@ -0,0 +1,102 @@ +productId = $productId; + $this->combinationId = $combinationId; + $this->customizationId = $customizationId; + } + + public static function create(int $productId, ?int $combinationId = null, ?int $customizationId = null): self + { + return new self($productId, $combinationId, $customizationId); + } + + public static function fromString(string $referenceId): ?self + { + $parts = explode('.', $referenceId); + + if (1 === count($parts)) { + $result = is_numeric($parts[0]) ? self::create((int) $parts[0]) : null; + } elseif (2 === count($parts)) { + $result = is_numeric($parts[0]) && is_numeric($parts[1]) ? self::create((int) $parts[0], (int) $parts[1]) : null; + } elseif (3 === count($parts) && is_numeric($parts[0]) && is_numeric($parts[1]) && is_numeric($parts[2])) { + $result = self::create((int) $parts[0], (int) $parts[1], (int) $parts[2]); + } else { + $result = null; + } + + if (null !== $result) { + $result->originalReferenceId = $referenceId; + } + + return $result; + } + + public function __toString(): string + { + if (null !== $this->originalReferenceId) { + return $this->originalReferenceId; + } + + if (null === $this->customizationId) { + return sprintf('%d.%d', $this->productId, $this->combinationId); + } + + return sprintf('%d.%d.%d', $this->productId, $this->combinationId, $this->customizationId); + } + + public function getProductId(): int + { + return $this->productId; + } + + public function getCombinationId(): ?int + { + if (0 === (int) $this->combinationId) { + return null; + } + + return $this->combinationId; + } + + public function getCustomizationId(): ?int + { + if (0 === (int) $this->customizationId) { + return null; + } + + return $this->customizationId; + } + + public function hasCustomization(): bool + { + return 0 !== (int) $this->customizationId; + } +} diff --git a/modules/inpostizi/src/Product/Util/AttributeListParser.php b/modules/inpostizi/src/Product/Util/AttributeListParser.php new file mode 100644 index 00000000..84fe536f --- /dev/null +++ b/modules/inpostizi/src/Product/Util/AttributeListParser.php @@ -0,0 +1,105 @@ +configuration = $configuration; + $this->context = $context; + $this->psVersion = $psVersion; + } + + /** + * @return array{group: string, name: string}[] + */ + public function parse(string $attributes, ?int $shopId = null): array + { + $pattern = $this->getPattern( + $colon = $this->getColon(), + $separator = $this->getSeparator($shopId) + ); + + if (!preg_match_all($pattern, $attributes . $separator, $matches)) { + return []; + } + + $result = []; + + foreach ($matches['attribute'] as $attribute) { + [$group, $name] = array_map('trim', explode($colon, $attribute, 2)); + + if ('' === $group || '' === $name) { + continue; + } + + $result[] = [ + 'group' => $group, + 'name' => $name, + ]; + } + + return $result; + } + + private function getPattern(string $colon, string $separator): string + { + if ('' === $colonChar = trim($colon)) { + return strtr('/(?>(?P.+?{colon}.+?){separator}(?!{separator}(?:[^{separator_char}])+{colon}))/', [ + '{colon}' => $colon, + '{separator}' => $separator, + '{separator_char}' => trim($separator), + ]); + } + + return strtr('/(?>(?P.+?{colon}[^{colon_char}]+){separator}(?!{separator}(?:[^{colon_char}{separator_char}])+{colon}))/', [ + '{colon}' => $colon, + '{separator}' => $separator, + '{colon_char}' => $colonChar, + '{separator_char}' => trim($separator), + ]); + } + + private function getSeparator(?int $shopId): string + { + $separator = $this->configuration->getAttributesSeparator($shopId); + + if ('-' === $separator && \Tools::version_compare($this->psVersion, '1.7.8', '>=')) { + $separator = ' -'; + } + + return $separator . ' '; + } + + private function getColon(): string + { + if (\Tools::version_compare($this->psVersion, '1.7.8')) { + return ' : '; + } + + return $this->context->getTranslator()->trans(': ', [], 'Shop.Pdf'); + } +} diff --git a/modules/inpostizi/src/Product/Util/DescriptionFormatter.php b/modules/inpostizi/src/Product/Util/DescriptionFormatter.php new file mode 100644 index 00000000..1db69e3c --- /dev/null +++ b/modules/inpostizi/src/Product/Util/DescriptionFormatter.php @@ -0,0 +1,36 @@ +description)) { + return $description; + } + + return self::doFormat((string) $product->description_short); + } + + private static function doFormat(string $description): string + { + $description = strip_tags($description); + $description = trim(preg_replace('/\s+/', ' ', $description)); + + if ('' === $description) { + return ''; + } + + $description = htmlentities($description, ENT_HTML401, 'utf-8', false); + $description = htmlspecialchars_decode($description); + $description = preg_replace('/&(?:#\d+|[a-zA-Z]+);/', '', $description); + + return \Tools::substr($description, 0, 1000); + } +} diff --git a/modules/inpostizi/src/PromoCode/AvailableCartRulesProvider.php b/modules/inpostizi/src/PromoCode/AvailableCartRulesProvider.php new file mode 100644 index 00000000..f75abf65 --- /dev/null +++ b/modules/inpostizi/src/PromoCode/AvailableCartRulesProvider.php @@ -0,0 +1,203 @@ + + */ + private $cmsRepository; + + /** + * @var CartRuleRepository + */ + private $cartRuleRepository; + + /** + * @var \Context + */ + private $context; + + /** + * @param ObjectRepositoryInterface<\CMS> $cmsRepository + * @param CartRuleRepository $cartRuleRepository + */ + public function __construct(CartRuleOptionsRepositoryInterface $repository, PromoCodesConfigurationInterface $configuration, ObjectRepositoryInterface $cmsRepository, ObjectRepositoryInterface $cartRuleRepository, \Context $context) + { + $this->repository = $repository; + $this->configuration = $configuration; + $this->cmsRepository = $cmsRepository; + $this->cartRuleRepository = $cartRuleRepository; + $this->context = $context; + } + + public function getAvailablePromotions(\Cart $cart): array + { + if ([] === $cartRules = $this->getAvailableHighlightedCartRules($cart)) { + return []; + } + + $promotions = []; + + foreach ($cartRules as $cartRule) { + if (null === $promotion = $this->mapPromotionData($cart, $cartRule)) { + continue; + } + + $promotions[] = $promotion; + + if (count($promotions) >= self::MAX_PROMO_COUNT) { + break; + } + } + + return $promotions; + } + + private function mapPromotionData(\Cart $cart, array $cartRule): ?AvailablePromotion + { + if (null === $details = $this->getPromoDetails($cart, (int) $cartRule['id_cart_rule'])) { + return null; + } + + $description = trim(\Tools::substr($cartRule['name'], 0, 60)); + $startDate = $this->parseDateTime($cartRule['date_from']); + $endDate = $this->parseDateTime($cartRule['date_to']); + + return new AvailablePromotion( + PromotionType::Merchant(), + (string) $cartRule['code'], + $description, + $details, + $startDate, + $endDate, + (int) $cartRule['priority'] + ); + } + + private function getAvailableHighlightedCartRules(\Cart $cart): array + { + if ([] === $discounts = $cart->getDiscounts()) { + return []; + } + + $cartRuleIdsToSkip = array_map(static function (array $cartRule): int { + return (int) $cartRule['id_cart_rule']; + }, $cart->getCartRules(\CartRule::FILTER_ACTION_ALL, false)); + + $cartRules = array_filter($discounts, function ($discount) use ($cart, $cartRuleIdsToSkip) { + if ('' === (string) $discount['code']) { + return false; + } + + if (!empty($discount['carrier_restriction'])) { + // discount cannot be added unless a delivery option is selected via checkout page, + // and before finalizing the order we do not know what delivery option will actually be used + return false; + } + + $cartRuleId = (int) $discount['id_cart_rule']; + + if (!empty($discount['country_restriction']) && !$this->canCountryRestrictedCartRuleBeAdded($cart, $cartRuleId)) { + return false; + } + + return !in_array($cartRuleId, $cartRuleIdsToSkip, true); + }); + + usort($cartRules, static function (array $a, array $b): int { + if (0 !== $result = $a['priority'] <=> $b['priority']) { + return $result; + } + + return $b['id_cart_rule'] <=> $a['id_cart_rule']; + }); + + return $cartRules; + } + + private function getPromoDetails(\Cart $cart, int $cartRuleId): ?PromoDetails + { + $shopId = (int) $this->context->shop->id; + + if (null === $pageId = $this->getPromoDetailsPageId($cartRuleId, $shopId)) { + return null; + } + + $languageId = (int) $cart->id_lang; + $cms = $this->cmsRepository->find($pageId, $languageId, $shopId); + + if (null === $cms || !$cms->active) { + return null; + } + + $url = $this->context->link->getCMSLink($cms, null, null, $languageId, $shopId); + + return new PromoDetails($url); + } + + private function parseDateTime(?string $dateTimeString): ?\DateTimeImmutable + { + if (null === $dateTimeString || self::NULL_DATE === $dateTimeString) { + return null; + } + + return new \DateTimeImmutable($dateTimeString); + } + + private function getPromoDetailsPageId(int $cartRuleId, int $shopId): ?int + { + $options = $this->repository->find($cartRuleId); + + if (null !== $options && null !== $pageId = $options->getPromoDetailsPageId()) { + return $pageId; + } + + return $this->configuration->getDefaultPromoDetailsPageId($shopId); + } + + private function canCountryRestrictedCartRuleBeAdded(\Cart $cart, int $cartRuleId): bool + { + if (0 >= $addressId = (int) $cart->id_address_delivery) { + // discount cannot be added unless the delivery address is selected + return false; + } + + foreach ($this->cartRuleRepository->getCompatibleCountries($cartRuleId) as $country) { + if ('PL' !== $country->iso_code) { + // as of writing this comment, only domestic delivery is available + continue; + } + + /** @var \Address[] $addresses */ + $addresses = $cart->getAddressCollection(); + $address = $addresses[$addressId]; + + return (int) $address->id_country === (int) $country->id; + } + + return false; + } +} diff --git a/modules/inpostizi/src/PromoCode/AvailablePromotionsProviderInterface.php b/modules/inpostizi/src/PromoCode/AvailablePromotionsProviderInterface.php new file mode 100644 index 00000000..b377e111 --- /dev/null +++ b/modules/inpostizi/src/PromoCode/AvailablePromotionsProviderInterface.php @@ -0,0 +1,17 @@ += $cartRuleId) { + throw new \DomainException('Cart rule ID must be greater than 0.'); + } + + $this->cartRuleId = $cartRuleId; + } + + public function getCartRuleId(): int + { + return $this->cartRuleId; + } + + public function isOmnibus(): bool + { + return $this->isOmnibus; + } + + public function setIsOmnibus(bool $isOmnibus): self + { + $this->isOmnibus = $isOmnibus; + + return $this; + } + + /** + * @return int|null ID of {@see \CMS} + */ + public function getPromoDetailsPageId(): ?int + { + return $this->promoDetailsPageId; + } + + /** + * @param int|null $cmsId ID of {@see \CMS} + */ + public function setPromoDetailsPageId(?int $cmsId): self + { + if (null !== $cmsId && 0 >= $cmsId) { + throw new \DomainException('CMS page ID must be greater than 0.'); + } + + $this->promoDetailsPageId = $cmsId; + + return $this; + } +} diff --git a/modules/inpostizi/src/PromoCode/CartRuleOptionsRepository.php b/modules/inpostizi/src/PromoCode/CartRuleOptionsRepository.php new file mode 100644 index 00000000..d5c26318 --- /dev/null +++ b/modules/inpostizi/src/PromoCode/CartRuleOptionsRepository.php @@ -0,0 +1,167 @@ + options by cart rule ID + */ + private $options = []; + + public function __construct(Connection $connection, ShopAwareConfigurationInterface $configuration) + { + $this->connection = $connection; + $this->configuration = $configuration; + } + + /** + * @internal + */ + public static function create(): self + { + $db = \Db::getInstance(); + + return new self(new Connection($db), new Configuration($db)); + } + + public function add(CartRuleOptions $options): void + { + $cartRuleId = $options->getCartRuleId(); + + try { + $this->connection->insert(self::TABLE_NAME, [ + 'id_cart_rule' => $cartRuleId, + 'is_omnibus' => $options->isOmnibus(), + 'details_cms_id' => $options->getPromoDetailsPageId(), + ]); + } catch (\PrestaShopDatabaseException $e) { + if (1062 !== $e->getCode()) { + throw $e; + } + + throw new \DomainException('Cart rule options already exist.'); + } + + $this->options[$cartRuleId] = $options; + $this->updateOmnibusCache($options); + } + + public function find(int $cartRuleId): ?CartRuleOptions + { + if (0 >= $cartRuleId) { + return null; + } + + if (array_key_exists($cartRuleId, $this->options)) { + return $this->options[$cartRuleId]; + } + + $qb = $this->createQueryBuilder()->where('id_cart_rule = ' . $cartRuleId); + + return $this->options[$cartRuleId] = $this->getOneOrNullResult($qb); + } + + public function update(CartRuleOptions $options): void + { + $cartRuleId = $options->getCartRuleId(); + + $affectedRowCount = $this->connection->update(self::TABLE_NAME, [ + 'is_omnibus' => $options->isOmnibus(), + 'details_cms_id' => $options->getPromoDetailsPageId(), + ], ['id_cart_rule' => $cartRuleId]); + + if (0 === $affectedRowCount) { + return; + } + + $this->options[$cartRuleId] = $options; + $this->updateOmnibusCache($options); + } + + public function isOmnibus(int $cartRuleId): bool + { + if (!$this->configuration->getGlobal(self::OMNIBUS_CACHE_CONFIG_KEY)) { + return false; + } + + if (null === $options = $this->find($cartRuleId)) { + return false; + } + + return $options->isOmnibus(); + } + + public function setOmnibus(int $cartRuleId, bool $isOmnibus): void + { + @trigger_error(sprintf('Method "%s::%s()" is deprecated since 2.1.0. Use "%s::add()" or "%s::update()" instead.', CartRuleRepositoryInterface::class, __FUNCTION__, CartRuleOptionsRepositoryInterface::class, CartRuleOptionsRepositoryInterface::class), E_USER_DEPRECATED); + + $options = $this->find($cartRuleId); + + if (null === $options) { + $options = (new CartRuleOptions($cartRuleId))->setIsOmnibus($isOmnibus); + $this->add($options); + } elseif ($isOmnibus !== $options->isOmnibus()) { + $options->setIsOmnibus($isOmnibus); + $this->update($options); + } + } + + protected function createQueryBuilder(): \DbQuery + { + return (new \DbQuery())->from(self::TABLE_NAME); + } + + protected function getOneOrNullResult(\DbQuery $qb): ?CartRuleOptions + { + if (false === $row = $this->connection->fetchAssociative((string) $qb)) { + return null; + } + + return $this->hydrate($row); + } + + protected function hydrate(array $row): CartRuleOptions + { + return (new CartRuleOptions((int) $row['id_cart_rule'])) + ->setIsOmnibus((bool) $row['is_omnibus']) + ->setPromoDetailsPageId($row['details_cms_id'] ? (int) $row['details_cms_id'] : null); + } + + private function updateOmnibusCache(CartRuleOptions $options): void + { + $hasOmnibusRules = $options->isOmnibus() || $this->hasOmnibusRules(); + $this->configuration->setGlobal(self::OMNIBUS_CACHE_CONFIG_KEY, $hasOmnibusRules); + } + + private function hasOmnibusRules(): bool + { + $qb = $this + ->createQueryBuilder() + ->select('1') + ->where('is_omnibus = 1'); + + return (bool) $this->connection->fetchOne('SELECT EXISTS(' . $qb . ')'); + } +} diff --git a/modules/inpostizi/src/PromoCode/CartRuleOptionsRepositoryInterface.php b/modules/inpostizi/src/PromoCode/CartRuleOptionsRepositoryInterface.php new file mode 100644 index 00000000..bde0ed8c --- /dev/null +++ b/modules/inpostizi/src/PromoCode/CartRuleOptionsRepositoryInterface.php @@ -0,0 +1,19 @@ +repository = $cartRuleRepository; + } + + /** + * @internal + */ + public static function create(): self + { + $repository = CartRuleOptionsRepository::create(); + + return new self($repository); + } + + public function getPromoCodes(\Cart $cart): array + { + $cartRules = $cart->getCartRules(\CartRule::FILTER_ACTION_ALL, true, true); + + return array_map([$this, 'createPromoCode'], $cartRules); + } + + private function createPromoCode(array $cartRule): PromoCode + { + $code = $cartRule['code'] ?: $cartRule['name']; + $regulationType = $this->getRegulationType($cartRule); + + return new PromoCode($cartRule['name'], $code, $regulationType); + } + + private function getRegulationType(array $cartRule): ?string + { + if ($this->repository->isOmnibus((int) $cartRule['id_cart_rule'])) { + return PromoCode::REGULATION_TYPE_OMNIBUS; + } + + return null; + } +} diff --git a/modules/inpostizi/src/PromoCode/NullAvailablePromotionsProvider.php b/modules/inpostizi/src/PromoCode/NullAvailablePromotionsProvider.php new file mode 100644 index 00000000..b0d4f350 --- /dev/null +++ b/modules/inpostizi/src/PromoCode/NullAvailablePromotionsProvider.php @@ -0,0 +1,13 @@ + + */ +final class BasketSessionRepository implements BasketSessionRepositoryInterface, OrderDataRepositoryInterface +{ + /** + * @var SerializerInterface + */ + private $serializer; + + /** + * @var ObjectManagerInterface + */ + private $manager; + + private $sessionsByCartId = []; + private $sessionsByBasketId = []; + private $sessionsByOrderId = []; + + public function __construct(SerializerInterface $serializer, ObjectManagerInterface $manager) + { + $this->serializer = $serializer; + $this->manager = $manager; + } + + public function findByBasketId(string $basketId): ?BasketSessionInterface + { + if (isset($this->sessionsByBasketId[$basketId])) { + return $this->sessionsByBasketId[$basketId]; + } + + $model = $this->manager + ->getRepository(InPostIziBasketSession::class) + ->findOneBy(['cart_id' => $basketId]); + + if (null === $model) { + return null; + } + + return $this->createExistingSession($model); + } + + public function findByEntityId($id): ?BasketSessionInterface + { + if (0 >= $cartId = (int) $id) { + return null; + } + + if (isset($this->sessionsByCartId[$cartId])) { + return $this->sessionsByCartId[$cartId]; + } + + $model = $this->manager + ->getRepository(InPostIziBasketSession::class) + ->findOneBy(['session_id' => $cartId]); + + if (null === $model) { + return null; + } + + return $this->createExistingSession($model); + } + + public function findByOrderId(string $id): ?BasketSessionInterface + { + if (0 >= $orderId = (int) $id) { + return null; + } + + if (isset($this->sessionsByOrderId[$orderId])) { + return $this->sessionsByOrderId[$orderId]; + } + + $model = $this->manager + ->getRepository(InPostIziBasketSession::class) + ->findOneBy(['order_id' => $orderId]); + + if (null === $model) { + return null; + } + + return $this->createExistingSession($model); + } + + public function createNewSession(BasketInterface $basket): BasketSessionInterface + { + return BasketSession::new($basket, Uuid::v4()); + } + + public function persist(BasketSessionInterface $session): void + { + if (!$session instanceof BasketSession) { + throw new \InvalidArgumentException(sprintf('Expected an instance of %s, %s given', BasketSession::class, get_class($session))); + } + + $model = $session->getModel(); + $this->doPersist($model); + + if (isset($this->sessionsByBasketId[$model->cart_id]) && $session->wasBasketSwitched()) { + $cartId = array_search($session, $this->sessionsByCartId, true); + unset($this->sessionsByCartId[$cartId]); + } + + $this->sessionsByBasketId[$model->cart_id] = $session; + $this->sessionsByCartId[$model->session_id] = $session; + if ($model->order_id) { + $this->sessionsByOrderId[$model->order_id] = $session; + } + } + + public function refresh(BasketSessionInterface $session): void + { + $session->unbind(); + $model = $session->getModel(); + $this->manager->refresh($model); + } + + public function resetBindingKeysCache(): void + { + $this->manager->getConnection()->update(InPostIziBasketSession::TABLE_NAME, [ + 'binding_api_key' => null, + ], ['order_id' => null]); + } + + public function getOrderData(string $orderId): ?CreateOrderRequest + { + if (null === $session = $this->findByOrderId($orderId)) { + return null; + } + + return $session->getOrderRequest(); + } + + private function doPersist(InPostIziBasketSession $model): void + { + try { + $this->manager->save($model); + + return; + } catch (\PrestaShopDatabaseException $e) { + if (null !== $model->id || !in_array((int) $e->getCode(), [1062, 1557, 1569, 1586], true)) { + throw $e; + } + } + + // unique constraint violation, try to create with a different UUID + $model->cart_id = (string) Uuid::v4(); + $this->doPersist($model); + } + + private function createExistingSession(InPostIziBasketSession $model): BasketSessionInterface + { + $cart = new CartProxy((int) $model->session_id, $this->manager); + $session = BasketSession::existing($model, $cart, $this->serializer); + + $this->sessionsByBasketId[$model->cart_id] = $session; + $this->sessionsByCartId[$model->session_id] = $session; + if ($model->order_id) { + $this->sessionsByOrderId[$model->order_id] = $session; + } + + return $session; + } +} diff --git a/modules/inpostizi/src/Repository/BasketSessionRepositoryInterface.php b/modules/inpostizi/src/Repository/BasketSessionRepositoryInterface.php new file mode 100644 index 00000000..3ce707ca --- /dev/null +++ b/modules/inpostizi/src/Repository/BasketSessionRepositoryInterface.php @@ -0,0 +1,45 @@ +connection = $connection; + } + + public function isCategoryRestricted(int $categoryId, ?int $shopId = null): bool + { + if (0 >= $categoryId) { + return false; + } + + $subQuery = $this + ->createByShopIdQueryBuilder(self::CATEGORY_RESTRICTIONS_TABLE, $shopId) + ->where('id_category = ' . $categoryId); + + return $this->exists($subQuery); + } + + public function hasCategoryRestrictions(?int $shopId = null): bool + { + $subQuery = $this->createByShopIdQueryBuilder(self::CATEGORY_RESTRICTIONS_TABLE, $shopId); + + return $this->exists($subQuery); + } + + public function isManufacturerRestricted(int $manufacturerId, ?int $shopId = null): bool + { + if (0 >= $manufacturerId) { + return false; + } + + $subQuery = $this + ->createByShopIdQueryBuilder(self::MANUFACTURER_RESTRICTIONS_TABLE, $shopId) + ->where('id_manufacturer = ' . $manufacturerId); + + return $this->exists($subQuery); + } + + public function hasManufacturerRestrictions(?int $shopId = null): bool + { + $subQuery = $this->createByShopIdQueryBuilder(self::MANUFACTURER_RESTRICTIONS_TABLE, $shopId); + + return $this->exists($subQuery); + } + + public function isAnyAttributeGroupRestricted(array $attributeGroupIds, ?int $shopId = null): bool + { + if ([] === $attributeGroupIds) { + return false; + } + + $subQuery = $this + ->createByShopIdQueryBuilder(self::ATTRIBUTE_GROUP_RESTRICTIONS_TABLE, $shopId) + ->where(sprintf('id_attribute_group IN (%s)', implode(',', array_map('intval', $attributeGroupIds)))); + + return $this->exists($subQuery); + } + + public function hasAttributeGroupRestrictions(?int $shopId = null): bool + { + $subQuery = $this->createByShopIdQueryBuilder(self::ATTRIBUTE_GROUP_RESTRICTIONS_TABLE, $shopId); + + return $this->exists($subQuery); + } + + public function isAnyFeatureRestricted(array $featureIds, ?int $shopId = null): bool + { + if ([] === $featureIds) { + return false; + } + + $subQuery = $this + ->createByShopIdQueryBuilder(self::FEATURE_RESTRICTIONS_TABLE, $shopId) + ->where(sprintf('id_feature IN (%s)', implode(',', array_map('intval', $featureIds)))); + + return $this->exists($subQuery); + } + + public function hasFeatureRestrictions(?int $shopId = null): bool + { + $subQuery = $this->createByShopIdQueryBuilder(self::FEATURE_RESTRICTIONS_TABLE, $shopId); + + return $this->exists($subQuery); + } + + public function getProductRestrictions(?int $shopId = null): ProductRestrictions + { + $categoryIds = $this->getRestrictedCategoryIds($shopId); + $manufacturerIds = $this->getRestrictedManufacturerIds($shopId); + $attributeGroupIds = $this->getRestrictedAttributeGroupIds($shopId); + $featureIds = $this->getRestrictedFeatureIds($shopId); + + return (new ProductRestrictions()) + ->setCategoryIds($categoryIds) + ->setManufacturerIds($manufacturerIds) + ->setAttributeGroupIds($attributeGroupIds) + ->setFeatureIds($featureIds); + } + + public function updateProductRestrictions(ProductRestrictions $productRestrictions, ?int $shopId = null): void + { + $this->updateRestrictedCategories($productRestrictions->getCategoryIds(), $shopId); + $this->updateRestrictedManufacturers($productRestrictions->getManufacturerIds(), $shopId); + $this->updateRestrictedAttributes($productRestrictions->getAttributeGroupIds(), $shopId); + $this->updateRestrictedFeatures($productRestrictions->getFeatureIds(), $shopId); + } + + private function getRestrictedCategoryIds(?int $shopId): array + { + $qb = (new \DbQuery()) + ->select('id_category') + ->from(self::CATEGORY_RESTRICTIONS_TABLE); + + return $this->getRestrictions($qb, $shopId); + } + + private function getRestrictedManufacturerIds(?int $shopId): array + { + $qb = (new \DbQuery()) + ->select('id_manufacturer') + ->from(self::MANUFACTURER_RESTRICTIONS_TABLE); + + return $this->getRestrictions($qb, $shopId); + } + + private function getRestrictedAttributeGroupIds(?int $shopId): array + { + $qb = (new \DbQuery()) + ->select('id_attribute_group') + ->from(self::ATTRIBUTE_GROUP_RESTRICTIONS_TABLE); + + return $this->getRestrictions($qb, $shopId); + } + + private function getRestrictedFeatureIds(?int $shopId): array + { + $qb = (new \DbQuery()) + ->select('id_feature') + ->from(self::FEATURE_RESTRICTIONS_TABLE); + + return $this->getRestrictions($qb, $shopId); + } + + /** + * @param int[] $categoryIds + */ + private function updateRestrictedCategories(array $categoryIds, ?int $shopId): void + { + $this->deleteByShopId(self::CATEGORY_RESTRICTIONS_TABLE, $shopId); + + if ([] === $categoryIds) { + return; + } + + $data = array_map(static function ($categoryId) use ($shopId): array { + return [ + 'id_category' => (int) $categoryId, + 'id_shop' => $shopId, + ]; + }, $categoryIds); + + $this->connection->insert(self::CATEGORY_RESTRICTIONS_TABLE, $data); + } + + /** + * @param int[] $manufacturerIds + */ + private function updateRestrictedManufacturers(array $manufacturerIds, ?int $shopId): void + { + $this->deleteByShopId(self::MANUFACTURER_RESTRICTIONS_TABLE, $shopId); + + if ([] === $manufacturerIds) { + return; + } + + $data = array_map(static function ($manufacturerId) use ($shopId): array { + return [ + 'id_manufacturer' => (int) $manufacturerId, + 'id_shop' => $shopId, + ]; + }, $manufacturerIds); + + $this->connection->insert(self::MANUFACTURER_RESTRICTIONS_TABLE, $data); + } + + /** + * @param int[] $attributeGroupIds + */ + private function updateRestrictedAttributes(array $attributeGroupIds, ?int $shopId): void + { + $this->deleteByShopId(self::ATTRIBUTE_GROUP_RESTRICTIONS_TABLE, $shopId); + + if ([] === $attributeGroupIds) { + return; + } + + $data = array_map(static function ($attributeGroupId) use ($shopId): array { + return [ + 'id_attribute_group' => (int) $attributeGroupId, + 'id_shop' => $shopId, + ]; + }, $attributeGroupIds); + + $this->connection->insert(self::ATTRIBUTE_GROUP_RESTRICTIONS_TABLE, $data); + } + + /** + * @param int[] $featureIds + */ + private function updateRestrictedFeatures(array $featureIds, ?int $shopId): void + { + $this->deleteByShopId(self::FEATURE_RESTRICTIONS_TABLE, $shopId); + + if ([] === $featureIds) { + return; + } + + $data = array_map(static function ($featureId) use ($shopId): array { + return [ + 'id_feature' => (int) $featureId, + 'id_shop' => $shopId, + ]; + }, $featureIds); + + $this->connection->insert(self::FEATURE_RESTRICTIONS_TABLE, $data); + } + + private function exists(\DbQuery $qb): bool + { + $sql = 'SELECT EXISTS(' . $qb->select('1') . ')'; + + return (bool) $this->connection->fetchOne($sql); + } + + private function createByShopIdQueryBuilder(string $table, ?int $shopId): \DbQuery + { + $qb = (new \DbQuery())->from($table); + + if (null !== $shopId) { + $qb->where('id_shop IS NULL OR id_shop = ' . $shopId); + } else { + $qb->where('id_shop IS NULL'); + } + + return $qb; + } + + private function getRestrictions(\DbQuery $qb, ?int $shopId): array + { + if (null !== $shopId) { + $qb->where('id_shop = ' . $shopId); + } else { + $qb->where('id_shop IS NULL'); + } + + return $this->connection->fetchFirstColumn((string) $qb); + } + + private function deleteByShopId(string $table, ?int $shopId): void + { + $qb = (new \DbQuery()) + ->type('DELETE') + ->from($table); + + if (null !== $shopId) { + $qb->where('id_shop = ' . $shopId); + } else { + $qb->where('id_shop IS NULL'); + } + + $this->connection->executeStatement((string) $qb); + } +} diff --git a/modules/inpostizi/src/Repository/ProductRestrictionsRepositoryInterface.php b/modules/inpostizi/src/Repository/ProductRestrictionsRepositoryInterface.php new file mode 100644 index 00000000..ef2d8abc --- /dev/null +++ b/modules/inpostizi/src/Repository/ProductRestrictionsRepositoryInterface.php @@ -0,0 +1,21 @@ +router = $router; + $this->requestStack = $requestStack ?? new RequestStack(); + } + + public function setContext(RequestContext $context): void + { + $this->router->setContext($context); + } + + public function getContext(): RequestContext + { + return $this->router->getContext(); + } + + /** + * @param string $name + * @param array $parameters + * @param int $referenceType + */ + public function generate($name, $parameters = [], $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH): string + { + $originalParameters = $parameters; + + if (null !== $request = $this->requestStack->getCurrentRequest()) { + $parameters += $request->query->all(); + } + + try { + return $this->router->generate($name, $parameters, $referenceType); + } catch (RouteNotFoundException $e) { + if (null === $generator = $this->getPrestaShopGenerator()) { + throw $e; + } + + return $generator->generate($name, $originalParameters, $referenceType); + } + } + + /** + * @param string $pathinfo + */ + public function match($pathinfo): array + { + return $this->router->match($pathinfo); + } + + public function getRouteCollection(): RouteCollection + { + return $this->router->getRouteCollection(); + } + + private function getPrestaShopGenerator(): ?UrlGeneratorInterface + { + if (isset($this->psGenerator)) { + return $this->psGenerator; + } + + global $kernel; + + if (!$kernel instanceof KernelInterface) { + return null; + } + + try { + return $this->psGenerator = $kernel->getContainer()->get('router'); + } catch (ServiceNotFoundException $e) { + return null; + } + } +} diff --git a/modules/inpostizi/src/Routing/AnnotationDirectoryLoader.php b/modules/inpostizi/src/Routing/AnnotationDirectoryLoader.php new file mode 100644 index 00000000..ae25a4cc --- /dev/null +++ b/modules/inpostizi/src/Routing/AnnotationDirectoryLoader.php @@ -0,0 +1,75 @@ +loader = $loader; + parent::__construct($loader->getLocator()); + } + + public function load($resource, $type = null): RouteCollection + { + return $this->loader->load($resource); + } + + public function supports($resource, $type = null): bool + { + if ('annotation' === $type) { + return true; + } + + return $this->loader->supports($resource); + } + + public function getResolver(): LoaderResolverInterface + { + return $this->loader->getResolver(); + } + + public function setResolver(LoaderResolverInterface $resolver): void + { + $this->loader->setResolver($resolver); + } + + /** + * @param string $dir + */ + public function setCurrentDir($dir): void + { + $this->loader->setCurrentDir($dir); + } + + public function getLocator(): FileLocatorInterface + { + return $this->loader->getLocator(); + } + + public function import($resource, $type = null, $ignoreErrors = false, $sourceResource = null) + { + return $this->loader->import($resource, $type, $ignoreErrors, $sourceResource); + } +} diff --git a/modules/inpostizi/src/Security/AuthorizationChecker.php b/modules/inpostizi/src/Security/AuthorizationChecker.php new file mode 100644 index 00000000..369025f4 --- /dev/null +++ b/modules/inpostizi/src/Security/AuthorizationChecker.php @@ -0,0 +1,60 @@ +accessDecisionManager = $accessDecisionManager; + } + + public static function create(iterable $voters = []): self + { + return new self(new AccessDecisionManager($voters)); + } + + /** + * @param string|string[] $attributes + */ + public function isGranted($attributes, $subject = null): bool + { + if (!is_array($attributes)) { + $attributes = [$attributes]; + } + + $token = $this->getToken(); + + return $this->accessDecisionManager->decide($token, $attributes, $subject); + } + + // we don't care about authentication for now + private function getToken(): TokenInterface + { + return $this->token ?? ($this->token = new AnonymousToken('secret', 'anon')); + } +} diff --git a/modules/inpostizi/src/Security/EmployeeAuthenticator.php b/modules/inpostizi/src/Security/EmployeeAuthenticator.php new file mode 100644 index 00000000..7c4e0821 --- /dev/null +++ b/modules/inpostizi/src/Security/EmployeeAuthenticator.php @@ -0,0 +1,91 @@ +context = $context; + } + + public function start(Request $request, AuthenticationException $authException = null): RedirectResponse + { + return $this->redirectToLoginPage(); + } + + public function getCredentials(Request $request): ?string + { + $cookieName = $this->context->cookie->getName(); + + if (!$request->cookies->has($cookieName)) { + return null; + } + + return $this->context->cookie->email; + } + + public function getUser($credentials, UserProviderInterface $userProvider): ?UserInterface + { + if (null === $credentials) { + return null; + } + + return $userProvider->loadUserByUsername($credentials); + } + + /** + * @param Employee $user + */ + public function checkCredentials($credentials, UserInterface $user): bool + { + /** @var \Employee $employee */ + $employee = $user->getData(); + + return (bool) $employee->isLoggedBack(); + } + + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): RedirectResponse + { + return $this->redirectToLoginPage(); + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey): ?Response + { + return null; + } + + public function supportsRememberMe(): bool + { + return false; + } + + private function redirectToLoginPage(): RedirectResponse + { + $url = $this->context->link->getAdminLink('AdminLogin'); + + return new RedirectResponse($url); + } +} diff --git a/modules/inpostizi/src/Security/LazyUserProvider.php b/modules/inpostizi/src/Security/LazyUserProvider.php new file mode 100644 index 00000000..e8c6c9e2 --- /dev/null +++ b/modules/inpostizi/src/Security/LazyUserProvider.php @@ -0,0 +1,62 @@ +container = $container; + $this->serviceId = $serviceId; + } + + public function loadUserByUsername($username): UserInterface + { + return $this->getUserProvider()->loadUserByUsername($username); + } + + public function refreshUser(UserInterface $user): UserInterface + { + return $this->getUserProvider()->refreshUser($user); + } + + public function supportsClass($class): bool + { + return $this->getUserProvider()->supportsClass($class); + } + + private function getUserProvider(): UserProviderInterface + { + return $this->userProvider ?? $this->userProvider = $this->container->get($this->serviceId); + } +} diff --git a/modules/inpostizi/src/Security/Voter/BindingWidgetVoter.php b/modules/inpostizi/src/Security/Voter/BindingWidgetVoter.php new file mode 100644 index 00000000..0e228a3c --- /dev/null +++ b/modules/inpostizi/src/Security/Voter/BindingWidgetVoter.php @@ -0,0 +1,67 @@ +configuration = $configuration; + $this->context = $context; + } + + /** + * @param string $attribute + */ + protected function supports($attribute, $subject): bool + { + return self::VIEW === $attribute && $subject instanceof Request; + } + + /** + * @param string $attribute + * @param Request $subject + */ + protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool + { + if (!$subject instanceof Request) { + throw new \InvalidArgumentException('Expected an instance of "%s", "%s" given.', Request::class, get_debug_type($subject)); + } + + if ($this->configuration->isEnabledForEveryone()) { + return true; + } + + if (isset($this->context->cookie->izi_show) && $this->context->cookie->izi_show) { + return true; + } + + if ('true' === $subject->query->get('showIzi')) { + $this->context->cookie->izi_show = true; + + return true; + } + + return $this->context->controller instanceof \ProductControllerCore + && $this->configuration->isFullPageCacheModuleInUse(); + } +} diff --git a/modules/inpostizi/src/Serializer/Exception/MissingConstructorArgumentsException.php b/modules/inpostizi/src/Serializer/Exception/MissingConstructorArgumentsException.php new file mode 100644 index 00000000..c21fb08d --- /dev/null +++ b/modules/inpostizi/src/Serializer/Exception/MissingConstructorArgumentsException.php @@ -0,0 +1,11 @@ +serializer = $serializer; + } + + public function denormalize($data, $type, $format = null, array $context = []): ?PaginationPage + { + if (null === $data) { + return null; + } + + if (!is_array($data)) { + throw new InvalidArgumentException(sprintf('Data expected to be an array, "%s" given.', get_debug_type($data))); + } + + if (PaginationPage::class !== $type) { + throw new InvalidArgumentException(sprintf('Unsupported type: "%s".', $type)); + } + + if (!$this->serializer instanceof DenormalizerInterface) { + throw new LogicException('Cannot denormalize object because injected serializer is not a denormalizer.'); + } + + if ($itemType = $context[self::ITEM_TYPE_KEY] ?? null) { + $data['content'] = $this->serializer->denormalize($data['content'], $itemType . '[]', $format, $context); + } + + return new PaginationPage( + $data['content'], + (int) $data['total_items'], + (int) $data['page_index'], + (int) $data['page_size'] + ); + } + + public function supportsDenormalization($data, $type, $format = null): bool + { + return PaginationPage::class === $type; + } +} diff --git a/modules/inpostizi/src/Serializer/Normalizer/CustomDenormalizer.php b/modules/inpostizi/src/Serializer/Normalizer/CustomDenormalizer.php new file mode 100644 index 00000000..e952acd3 --- /dev/null +++ b/modules/inpostizi/src/Serializer/Normalizer/CustomDenormalizer.php @@ -0,0 +1,41 @@ + $type + * + * @return T|null + */ + public function denormalize($data, $type, $format = null, array $context = []) + { + if (null === $data) { + return null; + } + + if (!is_array($data)) { + throw new InvalidArgumentException(sprintf('Data expected to be an array, "%s" given.', get_debug_type($data))); + } + + if (!is_subclass_of($type, DenormalizableInterface::class)) { + throw new UnexpectedValueException(sprintf('Unsupported type: "%s".', $type)); + } + + return $type::denormalize($data); + } + + public function supportsDenormalization($data, $type, $format = null): bool + { + return is_subclass_of($type, DenormalizableInterface::class); + } +} diff --git a/modules/inpostizi/src/Serializer/Normalizer/DateTimeNormalizer.php b/modules/inpostizi/src/Serializer/Normalizer/DateTimeNormalizer.php new file mode 100644 index 00000000..5b143d40 --- /dev/null +++ b/modules/inpostizi/src/Serializer/Normalizer/DateTimeNormalizer.php @@ -0,0 +1,130 @@ + + * + * @see \Symfony\Component\Serializer\Normalizer\DateTimeNormalizer + */ +class DateTimeNormalizer implements NormalizerInterface, DenormalizerInterface +{ + public const FORMAT_KEY = 'datetime_format'; + public const TIMEZONE_KEY = 'datetime_timezone'; + + private const DEFAULT_FORMAT = \DateTime::RFC3339; + + private const SUPPORTED_TYPES = [ + \DateTimeInterface::class => true, + \DateTimeImmutable::class => true, + \DateTime::class => true, + ]; + + public function normalize($object, $format = null, array $context = []): string + { + if (!$object instanceof \DateTimeInterface) { + throw new InvalidArgumentException('The object must implement the "\DateTimeInterface".'); + } + + $dateTimeFormat = $context[self::FORMAT_KEY] ?? self::DEFAULT_FORMAT; + $timezone = $this->getTimezone($context); + + if (null !== $timezone && is_callable([$object, 'setTimezone'])) { + $object = clone $object; + $object = $object->setTimezone($timezone); + } + + return $object->format($dateTimeFormat); + } + + public function supportsNormalization($data, $format = null): bool + { + return $data instanceof \DateTimeInterface; + } + + public function denormalize($data, $type, $format = null, array $context = []): \DateTimeInterface + { + $dateTimeFormat = $context[self::FORMAT_KEY] ?? null; + $timezone = $this->getTimezone($context); + + if (null === $data || is_string($data) && '' === trim($data)) { + throw new UnexpectedValueException('The data is either an empty string or null, you should pass a string that can be parsed with the passed format or a valid DateTime string.'); + } + + $data = (string) $data; + + if (null !== $dateTimeFormat) { + return $this->doDenormalize($data, $type, $dateTimeFormat, $timezone); + } + + $object = \DateTime::class === $type + ? \DateTime::createFromFormat(self::DEFAULT_FORMAT, $data, $timezone) + : \DateTimeImmutable::createFromFormat(self::DEFAULT_FORMAT, $data, $timezone); + + if (false !== $object) { + return $object; + } + + try { + return \DateTime::class === $type ? new \DateTime($data, $timezone) : new \DateTimeImmutable($data, $timezone); + } catch (\Exception $e) { + throw new UnexpectedValueException($e->getMessage(), $e->getCode(), $e); + } + } + + public function supportsDenormalization($data, $type, $format = null): bool + { + return isset(self::SUPPORTED_TYPES[$type]); + } + + private function doDenormalize(string $data, string $type, string $dateTimeFormat, ?\DateTimeZone $timezone): \DateTimeInterface + { + $object = \DateTime::class === $type + ? \DateTime::createFromFormat($dateTimeFormat, $data, $timezone) + : \DateTimeImmutable::createFromFormat($dateTimeFormat, $data, $timezone); + + if (false !== $object) { + return $object; + } + + $dateTimeErrors = \DateTime::class === $type ? \DateTime::getLastErrors() : \DateTimeImmutable::getLastErrors(); + + throw new UnexpectedValueException(sprintf('Parsing datetime string "%s" using format "%s" resulted in %d errors: ', $data, $dateTimeFormat, $dateTimeErrors['error_count']) . "\n" . implode("\n", $this->formatDateTimeErrors($dateTimeErrors['errors']))); + } + + /** + * @return string[] + */ + private function formatDateTimeErrors(array $errors): array + { + $formattedErrors = []; + + foreach ($errors as $pos => $message) { + $formattedErrors[] = sprintf('at position %d: %s', $pos, $message); + } + + return $formattedErrors; + } + + private function getTimezone(array $context): ?\DateTimeZone + { + $dateTimeZone = $context[self::TIMEZONE_KEY] ?? null; + + if (null === $dateTimeZone) { + return null; + } + + return $dateTimeZone instanceof \DateTimeZone ? $dateTimeZone : new \DateTimeZone($dateTimeZone); + } +} diff --git a/modules/inpostizi/src/Serializer/Normalizer/DenormalizableInterface.php b/modules/inpostizi/src/Serializer/Normalizer/DenormalizableInterface.php new file mode 100644 index 00000000..b8af1649 --- /dev/null +++ b/modules/inpostizi/src/Serializer/Normalizer/DenormalizableInterface.php @@ -0,0 +1,13 @@ + $type + * + * @return T|null + */ + public function denormalize($data, $type, $format = null, array $context = []) + { + if (null === $data) { + return null; + } + + if (!is_scalar($data)) { + throw new InvalidArgumentException(sprintf('Data expected to be scalar, "%s" given.', get_class($data))); + } + + if (!is_subclass_of($type, Enum::class)) { + throw new InvalidArgumentException(sprintf('Unsupported type: "%s".', $type)); + } + + return $type::tryFrom($data); + } + + public function supportsDenormalization($data, $type, $format = null): bool + { + return is_subclass_of($type, Enum::class); + } +} diff --git a/modules/inpostizi/src/Serializer/Normalizer/JsonSerializableNormalizer.php b/modules/inpostizi/src/Serializer/Normalizer/JsonSerializableNormalizer.php new file mode 100644 index 00000000..e5d89d2a --- /dev/null +++ b/modules/inpostizi/src/Serializer/Normalizer/JsonSerializableNormalizer.php @@ -0,0 +1,46 @@ + + * + * @see \Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer + */ +class JsonSerializableNormalizer implements NormalizerInterface, SerializerAwareInterface +{ + private $serializer; + + public function setSerializer(SerializerInterface $serializer): void + { + $this->serializer = $serializer; + } + + public function normalize($object, $format = null, array $context = []) + { + if (!$object instanceof \JsonSerializable) { + throw new InvalidArgumentException(sprintf('The object must implement "%s".', \JsonSerializable::class)); + } + + if (!$this->serializer instanceof NormalizerInterface) { + throw new LogicException('Cannot normalize object because injected serializer is not a normalizer.'); + } + + return $this->serializer->normalize($object->jsonSerialize(), $format, $context); + } + + public function supportsNormalization($data, $format = null): bool + { + return $data instanceof \JsonSerializable; + } +} diff --git a/modules/inpostizi/src/Serializer/Normalizer/ObjectNormalizer.php b/modules/inpostizi/src/Serializer/Normalizer/ObjectNormalizer.php new file mode 100644 index 00000000..b9606524 --- /dev/null +++ b/modules/inpostizi/src/Serializer/Normalizer/ObjectNormalizer.php @@ -0,0 +1,318 @@ + + * + * @see \Symfony\Component\Serializer\Normalizer\AbstractNormalizer + * @see \Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer + * @see BaseNormalizer + */ +final class ObjectNormalizer extends BaseNormalizer +{ + /** + * @var PropertyTypeExtractorInterface|null + */ + private $propertyTypeExtractor; + + private $typesCache = []; + + public function __construct(?ClassMetadataFactoryInterface $classMetadataFactory = null, ?NameConverterInterface $nameConverter = null, ?PropertyAccessorInterface $propertyAccessor = null, ?PropertyTypeExtractorInterface $propertyTypeExtractor = null) + { + parent::__construct($classMetadataFactory, $nameConverter, $propertyAccessor); + $this->propertyTypeExtractor = $propertyTypeExtractor; + } + + public function denormalize($data, $class, $format = null, array $context = []) + { + if (!isset($context['cache_key'])) { + $context['cache_key'] = $this->getCacheKey($format, $context); + } + $allowedAttributes = $this->getAllowedAttributes($class, $context, true); + $normalizedData = $this->prepareForDenormalization($data); + + $reflectionClass = new \ReflectionClass($class); + $object = $this->instantiateObject($normalizedData, $class, $context, $reflectionClass, $allowedAttributes, $format); + $resolvedClass = get_class($object); + + foreach ($normalizedData as $attribute => $value) { + if ($this->nameConverter) { + $attribute = $this->nameConverter->denormalize($attribute); + } + + $allowed = $allowedAttributes === false || in_array($attribute, $allowedAttributes); + $ignored = in_array($attribute, $this->ignoredAttributes); + + if ($allowed && !$ignored) { + $value = $this->validateAndDenormalize($resolvedClass, $attribute, $value, $format, $context); + + try { + $this->propertyAccessor->setValue($object, $attribute, $value); + } catch (NoSuchPropertyException $exception) { + // Properties not found are ignored + } + } + } + + return $object; + } + + protected function instantiateObject(array &$data, $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes, ?string $format = null) + { + if (!$constructor = $reflectionClass->getConstructor()) { + return new $class(); + } + + if (!$constructor->isPublic()) { + return $reflectionClass->newInstanceWithoutConstructor(); + } + + $constructorParameters = $constructor->getParameters(); + + $params = []; + foreach ($constructorParameters as $constructorParameter) { + $paramName = $constructorParameter->name; + $key = $this->nameConverter ? $this->nameConverter->normalize($paramName) : $paramName; + + $allowed = false === $allowedAttributes || in_array($paramName, $allowedAttributes); + if ($constructorParameter->isVariadic()) { + if ($allowed && (isset($data[$key]) || array_key_exists($key, $data))) { + if (!is_array($data[$paramName])) { + throw new RuntimeException(sprintf('Cannot create an instance of "%s" from serialized data because the variadic parameter "%s" can only accept an array.', $class, $constructorParameter->name)); + } + + $variadicParameters = []; + foreach ($data[$paramName] as $parameterData) { + $variadicParameters[] = $this->denormalizeParameter($reflectionClass, $constructorParameter, $paramName, $parameterData, $context, $format); + } + + $params = array_merge($params, $variadicParameters); + unset($data[$key]); + } + } elseif ($allowed && (isset($data[$key]) || array_key_exists($key, $data))) { + $parameterData = $data[$key]; + if (null === $parameterData && $constructorParameter->allowsNull()) { + $params[] = null; + // Don't run set for a parameter passed to the constructor + unset($data[$key]); + continue; + } + + // Don't run set for a parameter passed to the constructor + $params[] = $this->denormalizeParameter($reflectionClass, $constructorParameter, $paramName, $parameterData, $context, $format); + unset($data[$key]); + } elseif ($constructorParameter->isDefaultValueAvailable()) { + $params[] = $constructorParameter->getDefaultValue(); + } elseif ($constructorParameter->hasType() && $constructorParameter->getType()->allowsNull()) { + $params[] = null; + } else { + throw new MissingConstructorArgumentsException(sprintf('Cannot create an instance of "%s" from serialized data because its constructor requires parameter "%s" to be present.', $class, $constructorParameter->name)); + } + } + + return $constructor->isConstructor() + ? $reflectionClass->newInstanceArgs($params) + : $constructor->invokeArgs(null, $params); + } + + protected function denormalizeParameter(\ReflectionClass $class, \ReflectionParameter $parameter, $parameterName, $parameterData, array $context, $format = null) + { + if (null !== $this->propertyTypeExtractor && !$parameter->isVariadic() && null !== $this->propertyTypeExtractor->getTypes($class->getName(), $parameterName)) { + return $this->validateAndDenormalize($class->getName(), $parameterName, $parameterData, $format, $context); + } + + try { + if (($parameterType = $parameter->getType()) instanceof \ReflectionNamedType && !$parameterType->isBuiltin()) { + $parameterClass = $parameterType->getName(); + new \ReflectionClass($parameterClass); // throws a \ReflectionException if the class doesn't exist + + if (!$this->serializer instanceof DenormalizerInterface) { + throw new LogicException(sprintf('Cannot create an instance of "%s" from serialized data because the serializer inject in "%s" is not a denormalizer.', $parameterClass, self::class)); + } + + $parameterData = $this->serializer->denormalize($parameterData, $parameterClass, $format, $this->createChildContext($context, $format)); + } + } catch (\ReflectionException $e) { + throw new RuntimeException(sprintf('Could not determine the class of the parameter "%s".', $parameterName), 0, $e); + } catch (MissingConstructorArgumentsException $e) { + if (!$parameter->getType()->allowsNull()) { + throw $e; + } + + return null; + } + + return $parameterData; + } + + protected function createChildContext(array $parentContext, $attribute, ?string $format = null): array + { + $context = $parentContext; + $context['cache_key'] = $this->getCacheKey($format, $context); + + return $context; + } + + private function getCacheKey(?string $format, array $context) + { + unset($context['cache_key']); + + try { + return md5($format . serialize($context)); + } catch (\Exception $e) { + // The context cannot be serialized, skip the cache + return false; + } + } + + private function validateAndDenormalize(string $currentClass, string $attribute, $data, ?string $format, array $context) + { + if (null === $types = $this->getTypes($currentClass, $attribute)) { + return $data; + } + + $expectedTypes = []; + $isUnionType = \count($types) > 1; + $missingConstructorArgumentException = null; + foreach ($types as $type) { + if (null === $data && $type->isNullable()) { + return null; + } + + $collectionValueType = $type->isCollection() ? $type->getCollectionValueType() : null; + + // Fix a collection that contains the only one element + // This is special to xml format only + if ('xml' === $format && null !== $collectionValueType && (!is_array($data) || !is_int(key($data)))) { + $data = [$data]; + } + + if ('xml' === $format && '' === $data && Type::BUILTIN_TYPE_ARRAY === $type->getBuiltinType()) { + return []; + } + + if (null !== $collectionValueType && Type::BUILTIN_TYPE_OBJECT === $collectionValueType->getBuiltinType()) { + $builtinType = Type::BUILTIN_TYPE_OBJECT; + $class = $collectionValueType->getClassName() . '[]'; + + if (null !== $collectionKeyType = $type->getCollectionKeyType()) { + $context['key_type'] = $collectionKeyType; + } + } elseif ($type->isCollection() && null !== ($collectionValueType = $type->getCollectionValueType()) && Type::BUILTIN_TYPE_ARRAY === $collectionValueType->getBuiltinType()) { + // get inner type for any nested array + $innerType = $collectionValueType; + + // note that it will break for any other builtinType + $dimensions = '[]'; + while (null !== $innerType->getCollectionValueType() && Type::BUILTIN_TYPE_ARRAY === $innerType->getBuiltinType()) { + $dimensions .= '[]'; + $innerType = $innerType->getCollectionValueType(); + } + + if (null !== $innerType->getClassName()) { + // the builtinType is the inner one and the class is the class followed by []...[] + $builtinType = $innerType->getBuiltinType(); + $class = $innerType->getClassName() . $dimensions; + } else { + // default fallback (keep it as array) + $builtinType = $type->getBuiltinType(); + $class = $type->getClassName(); + } + } else { + $builtinType = $type->getBuiltinType(); + $class = $type->getClassName(); + } + + $expectedTypes[Type::BUILTIN_TYPE_OBJECT === $builtinType && $class ? $class : $builtinType] = true; + + // This try-catch should cover all NotNormalizableValueException (and all return branches after the first + // exception) so we could try denormalizing all types of a union type. If the target type is not an union + // type, we will just re-throw the caught exception. + // In the case of no denormalization succeeds with a union type, it will fall back to the default exception + // with the acceptable types list. + try { + if (Type::BUILTIN_TYPE_OBJECT === $builtinType) { + if (!$this->serializer instanceof DenormalizerInterface) { + throw new LogicException(sprintf('Cannot denormalize attribute "%s" for class "%s" because injected serializer is not a denormalizer.', $attribute, $class)); + } + + $childContext = $this->createChildContext($context, $format); + if ($this->serializer->supportsDenormalization($data, $class, $format)) { + return $this->serializer->denormalize($data, $class, $format, $childContext); + } + } + + // JSON only has a Number type corresponding to both int and float PHP types. + // PHP's json_encode, JavaScript's JSON.stringify, Go's json.Marshal as well as most other JSON encoders convert + // floating-point numbers like 12.0 to 12 (the decimal part is dropped when possible). + // PHP's json_decode automatically converts Numbers without a decimal part to integers. + // To circumvent this behavior, integers are converted to floats when denormalizing JSON based formats and when + // a float is expected. + if (Type::BUILTIN_TYPE_FLOAT === $builtinType && is_int($data) && null !== $format && false !== strpos($format, 'json')) { + return (float) $data; + } + + if ('false' === $builtinType && false === $data) { + return false; + } + + if (('is_' . $builtinType)($data)) { + return $data; + } + } catch (MissingConstructorArgumentsException $e) { + if (!$isUnionType) { + throw $e; + } + + if (!$missingConstructorArgumentException) { + $missingConstructorArgumentException = $e; + } + } + } + + if ($missingConstructorArgumentException) { + throw $missingConstructorArgumentException; + } + + throw new RuntimeException(sprintf('The type of the "%s" attribute for class "%s" must be one of "%s" ("%s" given).', $attribute, $currentClass, implode('", "', array_keys($expectedTypes)), gettype($data))); + } + + private function getTypes(string $currentClass, string $attribute): ?array + { + if (null === $this->propertyTypeExtractor) { + return null; + } + + $key = $currentClass . '::' . $attribute; + if (isset($this->typesCache[$key])) { + return false === $this->typesCache[$key] ? null : $this->typesCache[$key]; + } + + if (null !== $types = $this->propertyTypeExtractor->getTypes($currentClass, $attribute)) { + return $this->typesCache[$key] = $types; + } + + $this->typesCache[$key] = false; + + return null; + } +} diff --git a/modules/inpostizi/src/Serializer/Normalizer/PriceAmountNormalizer.php b/modules/inpostizi/src/Serializer/Normalizer/PriceAmountNormalizer.php new file mode 100644 index 00000000..69616633 --- /dev/null +++ b/modules/inpostizi/src/Serializer/Normalizer/PriceAmountNormalizer.php @@ -0,0 +1,31 @@ +jsonSerialize(), 2, '.', ''); + } + + public function supportsNormalization($data, $format = null): bool + { + return $data instanceof PriceAmount && 'json' === $format; + } +} diff --git a/modules/inpostizi/src/Serializer/Normalizer/PriceNormalizer.php b/modules/inpostizi/src/Serializer/Normalizer/PriceNormalizer.php new file mode 100644 index 00000000..bb1e7098 --- /dev/null +++ b/modules/inpostizi/src/Serializer/Normalizer/PriceNormalizer.php @@ -0,0 +1,33 @@ +jsonSerialize()); + } + + public function supportsNormalization($data, $format = null): bool + { + return $data instanceof Price && 'json' === $format; + } +} diff --git a/modules/inpostizi/src/Serializer/PropertyDocBlockTypeExtractor.php b/modules/inpostizi/src/Serializer/PropertyDocBlockTypeExtractor.php new file mode 100644 index 00000000..c7ced2b0 --- /dev/null +++ b/modules/inpostizi/src/Serializer/PropertyDocBlockTypeExtractor.php @@ -0,0 +1,223 @@ +parser = $parser ?? (new ParserFactory())->create(ParserFactory::ONLY_PHP7); + } + + /** + * @param string $class + * @param string $property + * + * @return Type[]|null + */ + public function getTypes($class, $property, array $context = []): ?array + { + $key = sprintf('%s::%s', $class, $property); + + if (array_key_exists($key, $this->types)) { + return $this->types[$key]; + } + + return $this->types[$key] = $this->getPropertyTypesFromDocBlock((string) $class, (string) $property); + } + + private function getPropertyTypesFromDocBlock(string $class, string $property): ?array + { + try { + $reflectionProperty = new \ReflectionProperty($class, $property); + } catch (\ReflectionException $e) { + return null; + } + + if (false === $comment = $reflectionProperty->getDocComment()) { + return null; + } + + if (!preg_match('/@var\s+(.+)\s*?\n/', $comment, $matches)) { + return null; + } + + try { + $context = $this->getNamespaceContext($reflectionProperty->getDeclaringClass()); + } catch (\Exception $e) { + return null; + } + + if (null === $type = $this->resolveType($matches[1], $context)) { + return null; + } + + return is_array($type) ? $type : [$type]; + } + + /** + * @return array{0: string, 1: array} + */ + private function getNamespaceContext(\ReflectionClass $class): array + { + if (array_key_exists($className = $class->getName(), $this->namespaces)) { + return $this->namespaces[$className]; + } + + $fileName = $class->getFileName(); + $namespace = $class->getNamespaceName(); + + return $this->namespaces[$className] = $this->createNamespaceContext($namespace, file_get_contents($fileName)); + } + + private function createNamespaceContext(string $namespace, string $fileContents): array + { + $namespace = trim($namespace, '\\'); + + $useStatements = []; + + /* Depending on the PS version, the available library version might not be able to parse PHP >=7.1 syntax. + To circumvent that, we try to limit parsing to the code preceding the first class declaration found in the file. */ + if (preg_match('/\n(?:(?:final|abstract)\s+)?class\s+/', $fileContents, $matches, PREG_OFFSET_CAPTURE)) { + $fileContents = substr($fileContents, 0, (int) $matches[0][1]); + } + + foreach ($this->parser->parse($fileContents) as $node) { + if (!$node instanceof Namespace_ || $namespace !== (string) $node->name) { + continue; + } + + $useStatements[] = $this->extractUseStatements($node); + } + + $useStatements = array_merge(...$useStatements); + + return [$namespace, $useStatements]; + } + + private function extractUseStatements(Namespace_ $namespace): array + { + $useStatements = []; + + foreach ($namespace->stmts as $node) { + if (!$node instanceof Use_) { + continue; + } + + foreach ($node->uses as $use) { + if (null === $use->alias) { + $alias = $use->name->getLast(); + } elseif (is_string($use->alias)) { + $alias = $use->alias; + } else { + $alias = $use->alias->name; + } + + $useStatements[$alias] = (string) $use->name; + } + } + + return $useStatements; + } + + /** + * @return Type[]|Type|null + */ + private function resolveType(string $type, array $context, bool $nullable = false) + { + if ('mixed' === $type) { + return null; + } + + if (in_array($type, Type::$builtinTypes)) { + return new Type($type, $nullable); + } + + if (false !== strpos($type, '|')) { + return $this->resolveCompoundType($type, $context); + } + + if ('[]' === substr($type, -2)) { + return $this->resolveCollectionType($type, $context, $nullable); + } + + $className = $this->resolveClassName($type, $context); + + if (!class_exists($className)) { + return null; + } + + return new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, $className); + } + + /** + * @return Type[] + */ + private function resolveCompoundType(string $value, array $context): array + { + $types = explode('|', $value); + $nullable = in_array(Type::BUILTIN_TYPE_NULL, $types, true); + + $result = []; + + foreach ($types as $type) { + if (null === $type = $this->resolveType($type, $context, $nullable)) { + continue; + } + + $result[] = $type; + } + + return $result; + } + + private function resolveCollectionType(string $type, array $context, bool $nullable): Type + { + if ('mixed[]' === $type) { + $collectionKeyType = null; + $collectionValueType = null; + } else { + $collectionKeyType = new Type(Type::BUILTIN_TYPE_INT); + $collectionValueType = $this->resolveType(substr($type, 0, -2), $context); + } + + return new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true, $collectionKeyType, $collectionValueType); + } + + private function resolveClassName(string $type, array $context): string + { + if (0 === strpos($type, '\\')) { + return substr($type, 1); + } + + [$namespace, $aliases] = $context; + $parts = explode('\\', $type, 2); + + if (isset($aliases[$parts[0]])) { + $parts[0] = $aliases[$parts[0]]; + + return implode('\\', $parts); + } + + return '' === $namespace + ? $type + : sprintf('%s\\%s', $namespace, $type); + } +} diff --git a/modules/inpostizi/src/Serializer/SafeDeserializerTrait.php b/modules/inpostizi/src/Serializer/SafeDeserializerTrait.php new file mode 100644 index 00000000..db5939a8 --- /dev/null +++ b/modules/inpostizi/src/Serializer/SafeDeserializerTrait.php @@ -0,0 +1,32 @@ + $class + * + * @return T|null + */ + private function deserialize(string $value, string $class, string $format = 'json', array $context = []) + { + try { + return $this->serializer->deserialize($value, $class, $format, $context); + } catch (ExceptionInterface $e) { + return null; + } + } +} diff --git a/modules/inpostizi/src/Serializer/SerializerFactory.php b/modules/inpostizi/src/Serializer/SerializerFactory.php new file mode 100644 index 00000000..659d9850 --- /dev/null +++ b/modules/inpostizi/src/Serializer/SerializerFactory.php @@ -0,0 +1,85 @@ +getConstructor()->getParameters(); + + return 3 < count($params) && \Tools::version_compare(_PS_VERSION_, '1.7.5.1', '>=') + ? new ObjectNormalizer(null, null, null, $typeExtractor) + : new CustomObjectNormalizer(null, null, null, $typeExtractor); + } + + private static function createTypeExtractor(): PropertyTypeExtractorInterface + { + $typeExtractors = []; + + if ( + class_exists(DocBlock::class) + && (\Tools::version_compare(_PS_VERSION_, '1.7.4', '>=') || class_exists(FileReflector::class)) + ) { + $typeExtractors[] = new PhpDocExtractor(); + } else { + $typeExtractors[] = new PropertyDocBlockTypeExtractor(); + } + + $typeExtractors[] = new ReflectionExtractor(); + + return new PropertyInfoExtractor([], $typeExtractors); + } +} diff --git a/modules/inpostizi/src/Shipping/CarrierModuleTrackingNumberProvider.php b/modules/inpostizi/src/Shipping/CarrierModuleTrackingNumberProvider.php new file mode 100644 index 00000000..53c6ff7a --- /dev/null +++ b/modules/inpostizi/src/Shipping/CarrierModuleTrackingNumberProvider.php @@ -0,0 +1,36 @@ +manager = $manager; + } + + public function getTrackingNumbers(int $orderId): array + { + if (!class_exists(\InPostShipmentModel::class)) { + return []; + } + + /** @var ShipmentRepository $repository */ + $repository = $this->manager->getRepository(\InPostShipmentModel::class); + $shipments = $repository->findWithTrackingNumbersByOrderId($orderId); + + return array_map(static function (\InPostShipmentModel $shipment): string { + return $shipment->tracking_number; + }, $shipments); + } +} diff --git a/modules/inpostizi/src/Shipping/CartTotal/CartTotalDeliveryStrategyInterface.php b/modules/inpostizi/src/Shipping/CartTotal/CartTotalDeliveryStrategyInterface.php new file mode 100644 index 00000000..4f71c5fe --- /dev/null +++ b/modules/inpostizi/src/Shipping/CartTotal/CartTotalDeliveryStrategyInterface.php @@ -0,0 +1,15 @@ +range_behavior || (int) $carrier->shipping_method !== \Carrier::SHIPPING_METHOD_PRICE; + } +} diff --git a/modules/inpostizi/src/Shipping/CartTotal/PriceRangeStrategy.php b/modules/inpostizi/src/Shipping/CartTotal/PriceRangeStrategy.php new file mode 100644 index 00000000..7e4fdbb2 --- /dev/null +++ b/modules/inpostizi/src/Shipping/CartTotal/PriceRangeStrategy.php @@ -0,0 +1,38 @@ + + */ + private $rangePriceRepository; + + public function __construct(CartTotalDeliveryStrategyInterface $genericStrategy, ObjectRepositoryInterface $rangePriceRepository) + { + $this->genericStrategy = $genericStrategy; + $this->rangePriceRepository = $rangePriceRepository; + } + + public function isShippingAvailableBasedOnTotalPrice(\Carrier $carrier, Price $cartTotal): bool + { + if ($this->genericStrategy->isShippingAvailableBasedOnTotalPrice($carrier, $cartTotal)) { + return true; + } + + $maxPriceRange = $this->rangePriceRepository->getMaxPriceRangeByCarrier($carrier); + + return null !== $maxPriceRange && $cartTotal->getGross() <= $maxPriceRange; + } +} diff --git a/modules/inpostizi/src/Shipping/CartWeight/CartWeightDeliveryStrategyInterface.php b/modules/inpostizi/src/Shipping/CartWeight/CartWeightDeliveryStrategyInterface.php new file mode 100644 index 00000000..7002f259 --- /dev/null +++ b/modules/inpostizi/src/Shipping/CartWeight/CartWeightDeliveryStrategyInterface.php @@ -0,0 +1,15 @@ +checkMaxWeight($carrier, $cartWeight); + } + + private function checkMaxWeight(\Carrier $carrier, Weight $cartWeight): bool + { + $carrierMaxWeight = new Weight((float) $carrier->max_weight); + + return $carrierMaxWeight->equals(new Weight(0.)) || $carrierMaxWeight->greaterThan($cartWeight); + } +} diff --git a/modules/inpostizi/src/Shipping/CartWeight/WeightRangeStrategy.php b/modules/inpostizi/src/Shipping/CartWeight/WeightRangeStrategy.php new file mode 100644 index 00000000..0e4c06b5 --- /dev/null +++ b/modules/inpostizi/src/Shipping/CartWeight/WeightRangeStrategy.php @@ -0,0 +1,46 @@ + + */ + private $rangeWeightRepository; + + public function __construct(CartWeightDeliveryStrategyInterface $genericStrategy, ObjectRepositoryInterface $rangeWeightRepository) + { + $this->genericStrategy = $genericStrategy; + $this->rangeWeightRepository = $rangeWeightRepository; + } + + public function isShippingAvailableBasedOnTotalWeight(\Carrier $carrier, Weight $cartWeight): bool + { + if (!$this->genericStrategy->isShippingAvailableBasedOnTotalWeight($carrier, $cartWeight)) { + return false; + } + + if (!$carrier->range_behavior || \Carrier::SHIPPING_METHOD_WEIGHT !== (int) $carrier->shipping_method) { + return true; + } + + $maxWeightRange = $this->rangeWeightRepository->getMaxWeightRangeByCarrier($carrier); + + if (null === $maxWeightRange) { + return true; + } + + return (new Weight((float) $maxWeightRange))->greaterThanOrEqual($cartWeight); + } +} diff --git a/modules/inpostizi/src/Shipping/DeliveryPriceCalculator.php b/modules/inpostizi/src/Shipping/DeliveryPriceCalculator.php new file mode 100644 index 00000000..c091ab51 --- /dev/null +++ b/modules/inpostizi/src/Shipping/DeliveryPriceCalculator.php @@ -0,0 +1,110 @@ + + */ + private $addressRepository; + + /** + * @var PriceConverterInterface + */ + private $priceConverter; + + /** + * @var FreeDelivery\MinAmountCalculationStrategyInterface + */ + private $freeDeliveryStrategy; + + /** + * @param ObjectRepositoryInterface<\Address> $addressRepository + */ + public function __construct(PrestaShopConfiguration $configuration, ObjectRepositoryInterface $addressRepository, PriceConverterInterface $priceConverter, FreeDelivery\MinAmountCalculationStrategyInterface $freeDeliveryStrategy) + { + $this->configuration = $configuration; + $this->addressRepository = $addressRepository; + $this->priceConverter = $priceConverter; + $this->freeDeliveryStrategy = $freeDeliveryStrategy; + } + + public function getDeliveryPrice(\Cart $cart, \Carrier $carrier, ?array $productList = null): Price + { + $carrierId = (int) $carrier->id; + + if (false === $netAmount = $cart->getPackageShippingCost($carrierId, false, null, $productList)) { + throw UnavailableDeliveryOptionException::for($carrier); + } + + $grossAmount = (float) $cart->getPackageShippingCost($carrierId, true, null, $productList); + + return PriceFactory::create((float) $netAmount, $grossAmount); + } + + public function getAdditionalServicePrice(\Cart $cart, \Carrier $carrier, ServiceOptions $options): Price + { + if (!$carrier->shipping_handling) { + return PriceFactory::create(0., 0.); + } + + if (0. === $netAmount = (float) $options->getAdditionalCost()) { + return PriceFactory::create(0., 0.); + } + + $grossAmount = $this->applyTaxes($cart, $carrier, $netAmount); + + return PriceFactory::create($netAmount, $grossAmount); + } + + public function getFreeDeliveryMinAmount(\Cart $cart, \Carrier $carrier): ?PriceAmount + { + $amount = $this->freeDeliveryStrategy->getMinAmount($cart, $carrier); + + if (null === $amount || 0. === $amount) { + return null; + } + + $amount = $this->priceConverter->convertByIds($amount, (int) $cart->id_currency); + + return new PriceAmount(\Tools::ps_round($amount, 2)); + } + + private function applyTaxes(\Cart $cart, \Carrier $carrier, float $netAmount): float + { + if (0. === $netAmount) { + return 0.; + } + + $address = $this->getTaxAddress($cart); + + return $carrier + ->getTaxCalculator($address) + ->addTaxes($netAmount); + } + + private function getTaxAddress(\Cart $cart): \Address + { + $taxAddressType = $this->configuration->getTaxAddressType(); + $addressId = (int) $cart->$taxAddressType; + + return $this->addressRepository->find($addressId) ?? \Address::initialize(); + } +} diff --git a/modules/inpostizi/src/Shipping/DeliveryPriceCalculatorInterface.php b/modules/inpostizi/src/Shipping/DeliveryPriceCalculatorInterface.php new file mode 100644 index 00000000..4d1974a3 --- /dev/null +++ b/modules/inpostizi/src/Shipping/DeliveryPriceCalculatorInterface.php @@ -0,0 +1,25 @@ +name)); + } +} diff --git a/modules/inpostizi/src/Shipping/FreeDelivery/GenericStrategy.php b/modules/inpostizi/src/Shipping/FreeDelivery/GenericStrategy.php new file mode 100644 index 00000000..0e317b41 --- /dev/null +++ b/modules/inpostizi/src/Shipping/FreeDelivery/GenericStrategy.php @@ -0,0 +1,33 @@ +configuration = $configuration; + } + + public function getMinAmount(\Cart $cart, \Carrier $carrier): ?float + { + if (\Carrier::SHIPPING_METHOD_FREE === $carrier->getShippingMethod()) { + return 0.; + } + + if (0. >= $minAmount = $this->configuration->getFreeDeliveryMinAmount()) { + return null; + } + + return $minAmount; + } +} diff --git a/modules/inpostizi/src/Shipping/FreeDelivery/MinAmountCalculationStrategyInterface.php b/modules/inpostizi/src/Shipping/FreeDelivery/MinAmountCalculationStrategyInterface.php new file mode 100644 index 00000000..4d171faa --- /dev/null +++ b/modules/inpostizi/src/Shipping/FreeDelivery/MinAmountCalculationStrategyInterface.php @@ -0,0 +1,13 @@ +defaultStrategy = $defaultStrategy; + $this->configuration = $configuration; + $this->manager = $manager; + } + + public function getMinAmount(\Cart $cart, \Carrier $carrier): ?float + { + $minAmount = $this->defaultStrategy->getMinAmount($cart, $carrier); + + if (0. === $minAmount || \Carrier::SHIPPING_METHOD_PRICE !== $carrier->getShippingMethod()) { + return $minAmount; + } + + if ($carrier->shipping_handling && 0. !== $this->configuration->getShippingHandlingCost()) { + return $minAmount; + } + + $country = $this->getDeliveryCountry(); + + if (0. !== (float) $carrier->getMaxDeliveryPriceByPrice((int) $country->id_zone)) { + return $minAmount; + } + + $range = $this->getLastPriceRange((int) $carrier->id); + $amount = (float) $range->delimiter1; + + return null === $minAmount ? $amount : min($minAmount, $amount); + } + + private function getDeliveryCountry(): \Country + { + // as of writing this comment, only domestic delivery is available + $country = $this->manager + ->getRepository(\Country::class) + ->findOneBy(['iso_code' => 'PL'], ['active' => 'DESC']); + + if (null !== $country) { + return $country; + } + + throw new \RuntimeException('Country "PL" does not exist.'); + } + + private function getLastPriceRange(int $carrierId): \RangePrice + { + $range = $this->manager + ->getRepository(\RangePrice::class) + ->findOneBy(['id_carrier' => $carrierId], ['delimiter1' => 'DESC']); + + if (null !== $range) { + return $range; + } + + throw new \RuntimeException('Price range not found.'); + } +} diff --git a/modules/inpostizi/src/Shipping/ProductDimensions/GenericStrategy.php b/modules/inpostizi/src/Shipping/ProductDimensions/GenericStrategy.php new file mode 100644 index 00000000..bc986162 --- /dev/null +++ b/modules/inpostizi/src/Shipping/ProductDimensions/GenericStrategy.php @@ -0,0 +1,28 @@ +max_width, (float) $carrier->max_height, (float) $carrier->max_depth); + + if ($carrierMaxDimensions->getWidth() === 0. && $carrierMaxDimensions->getHeight() === 0. && $carrierMaxDimensions->getDepth() === 0.) { + return true; + } + + return $this->checkMaxDimensions($carrierMaxDimensions, $productDimension); + } + + private function checkMaxDimensions(Dimensions $carrierMaxDimensions, Dimensions $productDimension): bool + { + return $carrierMaxDimensions->getWidth() >= $productDimension->getWidth() && + $carrierMaxDimensions->getHeight() >= $productDimension->getHeight() && + $carrierMaxDimensions->getDepth() >= $productDimension->getDepth(); + } +} diff --git a/modules/inpostizi/src/Shipping/ProductDimensions/ProductDimensionsDeliveryStrategyInterface.php b/modules/inpostizi/src/Shipping/ProductDimensions/ProductDimensionsDeliveryStrategyInterface.php new file mode 100644 index 00000000..4cad7e93 --- /dev/null +++ b/modules/inpostizi/src/Shipping/ProductDimensions/ProductDimensionsDeliveryStrategyInterface.php @@ -0,0 +1,15 @@ +getCarriers(), static function (array $carrier): bool { + return (bool) $carrier['active']; + }); + + if ([] === $carriersRestricted) { + return true; + } + + $availableCarriers = array_map(static function (array $carrier) { + return (int) $carrier['id_reference']; + }, $carriersRestricted); + + return in_array((int) $carrier->id_reference, $availableCarriers, true); + } +} diff --git a/modules/inpostizi/src/Shipping/ProductRestriction/ProductRestrictionDeliveryInterface.php b/modules/inpostizi/src/Shipping/ProductRestriction/ProductRestrictionDeliveryInterface.php new file mode 100644 index 00000000..818152c5 --- /dev/null +++ b/modules/inpostizi/src/Shipping/ProductRestriction/ProductRestrictionDeliveryInterface.php @@ -0,0 +1,13 @@ +translator = $translator; + } + + public function getCatalogue($locale = null): MessageCatalogueInterface + { + return $this->translator->getCatalogue($locale); + } + + public function trans($id, array $parameters = [], $domain = null, $locale = null): string + { + $domain = $this->normalizeDomain($domain); + + return $this->translator->trans($id, $parameters, $domain, $locale); + } + + public function transChoice($id, $number, array $parameters = [], $domain = null, $locale = null): string + { + $domain = $this->normalizeDomain($domain); + + return $this->translator->transChoice($id, $number, $parameters, $domain, $locale); + } + + public function setLocale($locale): void + { + $this->translator->setLocale($locale); + } + + public function getLocale(): string + { + return $this->translator->getLocale(); + } + + private function normalizeDomain(?string $domain): ?string + { + if (null === $domain) { + return null; + } + + return str_replace('.', '', $domain); + } +} diff --git a/modules/inpostizi/src/Translation/LegacyTranslator.php b/modules/inpostizi/src/Translation/LegacyTranslator.php new file mode 100644 index 00000000..96eea489 --- /dev/null +++ b/modules/inpostizi/src/Translation/LegacyTranslator.php @@ -0,0 +1,27 @@ +moduleName = $moduleName; + } + + public function l(string $source, ?string $domain = null): string + { + return \Translate::getModuleTranslation( + $this->moduleName, + $source, + $domain ?? $this->moduleName + ); + } +} diff --git a/modules/inpostizi/src/Translation/PaymentTypeTranslator.php b/modules/inpostizi/src/Translation/PaymentTypeTranslator.php new file mode 100644 index 00000000..ccba24bb --- /dev/null +++ b/modules/inpostizi/src/Translation/PaymentTypeTranslator.php @@ -0,0 +1,50 @@ +translator = $translator; + } + + public function getLabel(PaymentType $paymentType): string + { + switch ($paymentType) { + case PaymentType::Card(): + return $this->translator->l('Credit card', self::TRANSLATION_SOURCE); + case PaymentType::CardToken(): + return $this->translator->l('Remembered credit card', self::TRANSLATION_SOURCE); + case PaymentType::GooglePay(): + return $this->translator->l('Google Pay', self::TRANSLATION_SOURCE); + case PaymentType::ApplePay(): + return $this->translator->l('Apple Pay', self::TRANSLATION_SOURCE); + case PaymentType::BlikCode(): + return $this->translator->l('BLIK code', self::TRANSLATION_SOURCE); + case PaymentType::BlikToken(): + return $this->translator->l('Remembered BLIK account', self::TRANSLATION_SOURCE); + case PaymentType::PayByLink(): + return $this->translator->l('Pay by Link', self::TRANSLATION_SOURCE); + case PaymentType::ShoppingLimit(): + return $this->translator->l('Shopping limit', self::TRANSLATION_SOURCE); + case PaymentType::DeferredPayment(): + return $this->translator->l('Deferred payment', self::TRANSLATION_SOURCE); + case PaymentType::CashOnDelivery(): + return $this->translator->l('Cash on Delivery', self::TRANSLATION_SOURCE); + default: + return $paymentType->value; + } + } +} diff --git a/modules/inpostizi/src/Translation/ServiceNameTranslator.php b/modules/inpostizi/src/Translation/ServiceNameTranslator.php new file mode 100644 index 00000000..220a2bad --- /dev/null +++ b/modules/inpostizi/src/Translation/ServiceNameTranslator.php @@ -0,0 +1,34 @@ +translator = $translator; + } + + public function getName(ServiceCode $serviceCode): string + { + switch ($serviceCode) { + case ServiceCode::Cod(): + return $this->translator->l('Cash on Delivery', self::TRANSLATION_SOURCE); + case ServiceCode::Pww(): + return $this->translator->l('Weekend Delivery', self::TRANSLATION_SOURCE); + default: + return $serviceCode->value; + } + } +} diff --git a/modules/inpostizi/src/Twig/Extension/LegacyTranslationExtension.php b/modules/inpostizi/src/Twig/Extension/LegacyTranslationExtension.php new file mode 100644 index 00000000..7d2042e0 --- /dev/null +++ b/modules/inpostizi/src/Twig/Extension/LegacyTranslationExtension.php @@ -0,0 +1,44 @@ +translator = $translator; + } + + /** + * @return TwigFilter[] + */ + public function getFilters(): array + { + return [ + new TwigFilter('legacy_trans', [$this, 'legacyTrans'], [ + 'is_safe' => ['html'], + ]), + ]; + } + + public function legacyTrans(string $value, ?string $domain = null): string + { + return $this->translator->l($value, $domain ?? 'admin_template_translations'); + } +} diff --git a/modules/inpostizi/src/Twig/Loader/TemplateNameMappingLoader.php b/modules/inpostizi/src/Twig/Loader/TemplateNameMappingLoader.php new file mode 100644 index 00000000..6205eeb3 --- /dev/null +++ b/modules/inpostizi/src/Twig/Loader/TemplateNameMappingLoader.php @@ -0,0 +1,73 @@ + + */ + private $templateNamesMap; + + /** + * @param \Twig_LoaderInterface&\Twig_ExistsLoaderInterface&\Twig_SourceContextLoaderInterface $loader + * @param array $templatesMap + */ + public function __construct(\Twig_LoaderInterface $loader, array $templatesMap) + { + $this->loader = $loader; + $this->templateNamesMap = $templatesMap; + } + + public function exists($name): bool + { + $name = $this->getMappedTemplateName($name); + + return $this->loader->exists($name); + } + + public function getSource($name): string + { + $name = $this->getMappedTemplateName($name); + + return $this->loader->getSource($name); + } + + public function getCacheKey($name): string + { + $name = $this->getMappedTemplateName($name); + + return $this->loader->getCacheKey($name); + } + + public function isFresh($name, $time): bool + { + $name = $this->getMappedTemplateName($name); + + return $this->loader->isFresh($name, $time); + } + + private function getMappedTemplateName(string $name): string + { + return $this->templateNamesMap[$name] ?? $name; + } + + public function getSourceContext($name): \Twig_Source + { + $name = $this->getMappedTemplateName($name); + + return $this->loader->getSourceContext($name); + } +} diff --git a/modules/inpostizi/src/Uuid/Uuid.php b/modules/inpostizi/src/Uuid/Uuid.php new file mode 100644 index 00000000..2838e0d1 --- /dev/null +++ b/modules/inpostizi/src/Uuid/Uuid.php @@ -0,0 +1,46 @@ +uuid = static::generate(); + } else { + $version = preg_match('/^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/Di', $uuid) ? (int) $uuid[14] : false; + + if (false === $version || $version !== static::getVersion()) { + throw new \DomainException(sprintf('Invalid UUIDv%d: "%s".', static::getVersion(), $uuid)); + } + + $this->uuid = strtolower($uuid); + } + } + + public static function v4(): UuidV4 + { + return new UuidV4(); + } + + public function __toString(): string + { + return $this->uuid; + } + + public function jsonSerialize(): string + { + return $this->uuid; + } + + abstract protected static function generate(): string; + + abstract protected static function getVersion(): int; +} diff --git a/modules/inpostizi/src/Uuid/UuidV4.php b/modules/inpostizi/src/Uuid/UuidV4.php new file mode 100644 index 00000000..b8cb4d00 --- /dev/null +++ b/modules/inpostizi/src/Uuid/UuidV4.php @@ -0,0 +1,29 @@ +bindingPlace instanceof BindingPlace) { + throw new InvalidArgumentException(sprintf('The "bindingPlace" option must be a "%s" case, "%s" given.', BindingPlace::class, get_debug_type($this->bindingPlace))); + } + } + + public function getDefaultOption(): string + { + return 'bindingPlace'; + } + + public function getRequiredOptions(): array + { + return ['bindingPlace']; + } +} diff --git a/modules/inpostizi/src/Validator/Cart/BindableValidator.php b/modules/inpostizi/src/Validator/Cart/BindableValidator.php new file mode 100644 index 00000000..f16d0300 --- /dev/null +++ b/modules/inpostizi/src/Validator/Cart/BindableValidator.php @@ -0,0 +1,59 @@ +paymentModule = $paymentModule; + } + + public function validate($value, Constraint $constraint): void + { + if (!$constraint instanceof Bindable) { + throw new UnexpectedTypeException($constraint, Bindable::class); + } + + if (null === $value) { + return; + } + + if (!$value instanceof \Cart) { + throw new UnexpectedTypeException($value, \Cart::class); + } + + $constraints = iterator_to_array($this->getConstraints($constraint->bindingPlace)); + + $context = $this->context; + $validator = $context->getValidator()->inContext($context); + + $validator->validate($value, new Sequentially($constraints)); + } + + private function getConstraints(BindingPlace $bindingPlace): \Generator + { + if ($bindingPlace->requiresExistingBasket()) { + yield new HasProducts(); + } + + yield new PaymentInCurrencyAvailable($this->paymentModule); + + if (BindingPlace::ProductCard() !== $bindingPlace) { + yield new HasUnrestrictedProduct(); + } + } +} diff --git a/modules/inpostizi/src/Validator/Cart/HasProducts.php b/modules/inpostizi/src/Validator/Cart/HasProducts.php new file mode 100644 index 00000000..514c1bcb --- /dev/null +++ b/modules/inpostizi/src/Validator/Cart/HasProducts.php @@ -0,0 +1,15 @@ +getProducts()) { + return; + } + + $this->context + ->buildViolation($constraint->message) + ->setTranslationDomain('Shop.Notifications.Error') + ->addViolation(); + } +} diff --git a/modules/inpostizi/src/Validator/Cart/HasUnrestrictedProduct.php b/modules/inpostizi/src/Validator/Cart/HasUnrestrictedProduct.php new file mode 100644 index 00000000..8c699020 --- /dev/null +++ b/modules/inpostizi/src/Validator/Cart/HasUnrestrictedProduct.php @@ -0,0 +1,10 @@ +getProducts() as $product) { + $violations = $this->context->getValidator()->startContext()->validate($product, new Unrestricted())->getViolations(); + + if (0 === count($violations)) { + return; + } + } + + $this->context + ->buildViolation($constraint->message) + ->setTranslationDomain('Shop.Notifications.Error') + ->addViolation(); + } +} diff --git a/modules/inpostizi/src/Validator/Cart/PaymentInCurrencyAvailable.php b/modules/inpostizi/src/Validator/Cart/PaymentInCurrencyAvailable.php new file mode 100644 index 00000000..b3beca54 --- /dev/null +++ b/modules/inpostizi/src/Validator/Cart/PaymentInCurrencyAvailable.php @@ -0,0 +1,35 @@ +paymentModule instanceof \PaymentModule) { + throw new InvalidArgumentException(sprintf('The "paymentModule" option must be an instance of "%s", "%s" given.', \PaymentModule::class, get_debug_type($this->paymentModule))); + } + } + + public function getDefaultOption(): string + { + return 'paymentModule'; + } + + public function getRequiredOptions(): array + { + return ['paymentModule']; + } +} diff --git a/modules/inpostizi/src/Validator/Cart/PaymentInCurrencyAvailableValidator.php b/modules/inpostizi/src/Validator/Cart/PaymentInCurrencyAvailableValidator.php new file mode 100644 index 00000000..f9582ebf --- /dev/null +++ b/modules/inpostizi/src/Validator/Cart/PaymentInCurrencyAvailableValidator.php @@ -0,0 +1,46 @@ +currencyChecker = $currencyChecker; + } + + public function validate($value, Constraint $constraint): void + { + if (!$constraint instanceof PaymentInCurrencyAvailable) { + throw new UnexpectedTypeException($constraint, PaymentInCurrencyAvailable::class); + } + + if (null === $value) { + return; + } + + if (!$value instanceof \Cart) { + throw new UnexpectedTypeException($value, \Cart::class); + } + + if ($this->currencyChecker->check($constraint->paymentModule, (int) $value->id_currency)) { + return; + } + + $this->context + ->buildViolation('Payment option is not available for the selected currency.') + ->addViolation(); + } +} diff --git a/modules/inpostizi/src/Validator/Consent/DescriptionUsesIdPlaceholders.php b/modules/inpostizi/src/Validator/Consent/DescriptionUsesIdPlaceholders.php new file mode 100644 index 00000000..1afc1479 --- /dev/null +++ b/modules/inpostizi/src/Validator/Consent/DescriptionUsesIdPlaceholders.php @@ -0,0 +1,22 @@ +translator = $translator; + } + + public function validate($value, Constraint $constraint): void + { + if (!$constraint instanceof DescriptionUsesIdPlaceholders) { + throw new UnexpectedTypeException($constraint, DescriptionUsesIdPlaceholders::class); + } + + if (null === $value) { + return; + } + + if (!$value instanceof Consent) { + throw new UnexpectedTypeException($value, Consent::class); + } + + if ([] === $placeholders = $this->collectIdPlaceholders($value)) { + return; + } + + $hasAdditionalLinks = [] !== $value->getAdditionalLinks(); + + foreach ($value->getDescriptions() as $languageId => $description) { + if ('' === $description = (string) $description) { + continue; + } + + $this->validateDescription($description, $languageId, $placeholders, $hasAdditionalLinks); + } + } + + private function validateDescription(string $description, int $languageId, array $placeholders, bool $hasAdditionalLinks): void + { + $duplicated = []; + + foreach ($placeholders as $id => $placeholder) { + $count = substr_count($description, $placeholder); + + $description = strtr($description, [ + $placeholder => '', + ]); + + if (0 === $count) { + continue; + } + + unset($placeholders[$id]); + + if (1 === $count) { + continue; + } + + $duplicated[] = $placeholder; + } + + if ([] !== $placeholders && $hasAdditionalLinks) { + $this->context + ->buildViolation(sprintf($this->translator->l('Unused ID placeholders: %s.', self::TRANSLATION_SOURCE), sprintf('"%s"', implode('", "', $placeholders)))) + ->atPath('descriptions[' . $languageId . ']') + ->addViolation(); + } elseif ([] !== $duplicated) { + $this->context + ->buildViolation(sprintf($this->translator->l('Duplicated ID placeholders: %s.', self::TRANSLATION_SOURCE), sprintf('"%s"', implode('", "', $duplicated)))) + ->atPath('descriptions[' . $languageId . ']') + ->addViolation(); + } + } + + /** + * @return array placeholders by link ID + */ + private function collectIdPlaceholders(Consent $consent): array + { + $ids = []; + + if ('' !== $id = (string) $consent->getId()) { + $ids[$id] = '#' . $id; + } + + foreach ($consent->getAdditionalLinks() as $link) { + if ('' === $id = (string) $link->getId()) { + continue; + } + + $ids[$id] = '#' . $id; + } + + usort($ids, static function ($a, $b) { + return strlen($b) - strlen($a); + }); + + return $ids; + } +} diff --git a/modules/inpostizi/src/Validator/Consent/UniqueIdentifiers.php b/modules/inpostizi/src/Validator/Consent/UniqueIdentifiers.php new file mode 100644 index 00000000..238530ad --- /dev/null +++ b/modules/inpostizi/src/Validator/Consent/UniqueIdentifiers.php @@ -0,0 +1,22 @@ +translator = $translator; + } + + public function validate($value, Constraint $constraint): void + { + if (!$constraint instanceof UniqueIdentifiers) { + throw new UnexpectedTypeException($constraint, UniqueIdentifiers::class); + } + + if (null === $value) { + return; + } + + if (!$value instanceof Consent) { + throw new UnexpectedTypeException($value, Consent::class); + } + + if (null === $id = $value->getId()) { + return; + } + + if ([] === $linkIds = $this->collectLinkIds($value)) { + return; + } + + foreach ($linkIds as $linkId) { + if ($linkId !== $id) { + continue; + } + + $this->context + ->buildViolation($this->translator->l('Identifier should be unique.', self::TRANSLATION_SOURCE)) + ->atPath('link.id') + ->addViolation(); + + return; + } + } + + /** + * @return string[] + */ + private function collectLinkIds(Consent $consent): array + { + return array_map([ConsentLink::class, 'normalize'], $consent->getAdditionalLinks()); + } +} diff --git a/modules/inpostizi/src/Validator/ConstraintValidatorFactory.php b/modules/inpostizi/src/Validator/ConstraintValidatorFactory.php new file mode 100644 index 00000000..e3dd4110 --- /dev/null +++ b/modules/inpostizi/src/Validator/ConstraintValidatorFactory.php @@ -0,0 +1,68 @@ + + * + * @internal + */ +final class ConstraintValidatorFactory implements ConstraintValidatorFactoryInterface +{ + /** + * @var ContainerInterface + */ + private $container; + + /** + * @var array + */ + private $validators = []; + + /** + * @param ContainerInterface $container + */ + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + public function getInstance(Constraint $constraint): ConstraintValidatorInterface + { + $name = $constraint->validatedBy(); + + return $this->validators[$name] ?? $this->validators[$name] = $this->create($name, $constraint); + } + + private function create(string $name, Constraint $constraint): ConstraintValidatorInterface + { + $serviceId = strtolower($name); // Sf 2.8 normalizes all service ids by converting them to lowercase + + if ($this->container->has($serviceId)) { + $validator = $this->container->get($serviceId); + } else { + if (!class_exists($name)) { + throw new ValidatorException(sprintf('Constraint validator "%s" does not exist or is not enabled. Check the "validatedBy" method in your constraint class "%s".', $name, get_class($constraint))); + } + + $validator = new $name(); + } + + if (!$validator instanceof ConstraintValidatorInterface) { + throw new UnexpectedTypeException($validator, ConstraintValidatorInterface::class); + } + + return $validator; + } +} diff --git a/modules/inpostizi/src/Validator/InPostApiCredentials.php b/modules/inpostizi/src/Validator/InPostApiCredentials.php new file mode 100644 index 00000000..2f936608 --- /dev/null +++ b/modules/inpostizi/src/Validator/InPostApiCredentials.php @@ -0,0 +1,19 @@ +translator = $translator; + $this->authProviderFactory = $authProviderFactory; + } + + public function validate($value, Constraint $constraint): void + { + if (!$constraint instanceof InPostApiCredentials) { + throw new UnexpectedTypeException($constraint, InPostApiCredentials::class); + } + + if (null === $value) { + return; + } + + if (!$value instanceof ApiConfigurationInterface) { + throw new UnexpectedTypeException($value, ApiConfigurationInterface::class); + } + + if (null === $credentials = $value->getClientCredentials()) { + return; + } + + $uriCollection = new AuthServerUriCollection($value->getEnvironment()); + + try { + $token = $this->authProviderFactory + ->create($uriCollection, $credentials) + ->getAccessToken(); + + $this->validateTokenScopes($token); + } catch (AccessTokenRequestException $e) { + $this->context + ->buildViolation($this->translator->l('Invalid client credentials.', self::TRANSLATION_SOURCE)) + ->addViolation(); + } catch (NetworkExceptionInterface $e) { + $this->context + ->buildViolation($this->translator->l('Could not connect to the authorization server.', self::TRANSLATION_SOURCE)) + ->addViolation(); + } catch (\Exception $e) { + $this->context + ->buildViolation($this->translator->l('Could not validate client credentials.', self::TRANSLATION_SOURCE)) + ->addViolation(); + } + } + + private function validateTokenScopes(AccessTokenInterface $token): void + { + if (null === $scopes = $token->getScopes()) { + return; + } + + if ([] === array_diff(self::REQUIRED_SCOPES, $scopes)) { + return; + } + + $this->context + ->buildViolation($this->translator->l('The granted access token does not have all of the required permissions. To resolve this issue, please contact support.', self::TRANSLATION_SOURCE)) + ->addViolation(); + } +} diff --git a/modules/inpostizi/src/Validator/NotBlankInDefaultLanguage.php b/modules/inpostizi/src/Validator/NotBlankInDefaultLanguage.php new file mode 100644 index 00000000..221180df --- /dev/null +++ b/modules/inpostizi/src/Validator/NotBlankInDefaultLanguage.php @@ -0,0 +1,29 @@ +configuration = $configuration; + } + + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint): void + { + if (!$constraint instanceof NotBlankInDefaultLanguage) { + throw new UnexpectedTypeException($constraint, NotBlankInDefaultLanguage::class); + } + + if (null === $value) { + return; + } + + if (!is_array($value)) { + throw new UnexpectedTypeException($value, 'array'); + } + + $defaultLanguageId = (int) $this->configuration->get(self::DEFAULT_LANGUAGE_ID_CONFIG_KEY); + + if (!isset($value[$defaultLanguageId]) || '' === $value[$defaultLanguageId]) { + $this->context->buildViolation($constraint->message) + ->setTranslationDomain('Admin.Notifications.Error') + ->setParameter('%field_name%', $constraint->fieldName) + ->addViolation(); + } + } +} diff --git a/modules/inpostizi/src/Validator/ProcessableMessageFormat.php b/modules/inpostizi/src/Validator/ProcessableMessageFormat.php new file mode 100644 index 00000000..c46de692 --- /dev/null +++ b/modules/inpostizi/src/Validator/ProcessableMessageFormat.php @@ -0,0 +1,15 @@ +formatter = $formatter; + $this->translator = $translator; + } + + public function validate($value, Constraint $constraint): void + { + if (!$constraint instanceof ProcessableMessageFormat) { + throw new UnexpectedTypeException($constraint, ProcessableMessageFormat::class); + } + + if (null === $value) { + return; + } + + if (!is_string($value)) { + throw new UnexpectedTypeException($value, 'string'); + } + + try { + foreach ($this->getExampleRequests() as $request) { + $this->formatter->format($value, $request); + } + } catch (SyntaxError|\LogicException $e) { + $this->context + ->buildViolation(sprintf($this->translator->l('Invalid message format. %s', self::TRANSLATION_SOURCE), $e->getMessage())) + ->addViolation(); + + return; + } + } + + private function getExampleRequests(): \Generator + { + $phoneNumber = new PhoneNumber('+48', '123456789'); + $orderDetails = new OrderDetails((string) Uuid::v4(), Currency::Pln(), new Price(0., 0., 0.), PaymentType::Card(), 'comment'); + $accountInfo = new AccountInfo('firstname', 'lastname', $phoneNumber, 'test@example.com', new ClientAddress('PL', 'address', 'city', '12-123')); + $consents = [new Consent('1', '1', true)]; + $invoiceDetails = new InvoiceDetails(LegalForm::Company(), 'PL', 'city', 'street', '1', '12-123', 'PL', '0123456789', 'company'); + + $deliveryType = DeliveryType::Apm(); + + yield new CreateOrderRequest( + $orderDetails, + $accountInfo, + new Delivery($deliveryType, $deliveryType->getAvailableServiceCodes(), 'test@example.com', $phoneNumber, 'APM123'), + $consents, + $invoiceDetails + ); + + $deliveryType = DeliveryType::Courier(); + $deliveryAddress = new DeliveryAddress('full name', 'PL', 'address', 'city', '12-123'); + + yield new CreateOrderRequest( + $orderDetails, + $accountInfo, + new Delivery($deliveryType, $deliveryType->getAvailableServiceCodes(), 'test@example.com', $phoneNumber, null, $deliveryAddress, 'note'), + $consents, + $invoiceDetails + ); + } +} diff --git a/modules/inpostizi/src/Validator/Product/NotFromRestrictedManufacturer.php b/modules/inpostizi/src/Validator/Product/NotFromRestrictedManufacturer.php new file mode 100644 index 00000000..dd247ade --- /dev/null +++ b/modules/inpostizi/src/Validator/Product/NotFromRestrictedManufacturer.php @@ -0,0 +1,20 @@ +repository = $repository; + } + + public function validate($value, Constraint $constraint): void + { + if (!$constraint instanceof NotFromRestrictedManufacturer) { + throw new UnexpectedTypeException($constraint, NotFromRestrictedManufacturer::class); + } + + if (!is_array($value) && !$value instanceof \ArrayAccess) { + throw new UnexpectedTypeException($value, 'array|ArrayAccess'); + } + + if (0 >= $manufacturerId = (int) ($value['id_manufacturer'] ?? 0)) { + return; + } + + if (!$this->repository->isManufacturerRestricted($manufacturerId, $constraint->shopId)) { + return; + } + + $this->context + ->buildViolation('Product comes from a restricted manufacturer.') + ->addViolation(); + } +} diff --git a/modules/inpostizi/src/Validator/Product/NotInRestrictedCategory.php b/modules/inpostizi/src/Validator/Product/NotInRestrictedCategory.php new file mode 100644 index 00000000..9cf2341b --- /dev/null +++ b/modules/inpostizi/src/Validator/Product/NotInRestrictedCategory.php @@ -0,0 +1,20 @@ +repository = $repository; + } + + public function validate($value, Constraint $constraint): void + { + if (!$constraint instanceof NotInRestrictedCategory) { + throw new UnexpectedTypeException($constraint, NotInRestrictedCategory::class); + } + + if (!is_array($value) && !$value instanceof \ArrayAccess) { + throw new UnexpectedTypeException($value, 'array|ArrayAccess'); + } + + if (0 >= $categoryId = (int) ($value['id_category_default'] ?? 0)) { + return; + } + + if (!$this->repository->isCategoryRestricted($categoryId, $constraint->shopId)) { + return; + } + + $this->context + ->buildViolation('Product belongs to a restricted category.') + ->addViolation(); + } +} diff --git a/modules/inpostizi/src/Validator/Product/NotOfType.php b/modules/inpostizi/src/Validator/Product/NotOfType.php new file mode 100644 index 00000000..61438389 --- /dev/null +++ b/modules/inpostizi/src/Validator/Product/NotOfType.php @@ -0,0 +1,45 @@ +types; + if (!is_array($types)) { + $types = [$types]; + } + + foreach ($types as $type) { + if (!$type instanceof ProductType) { + throw new InvalidArgumentException(sprintf('The "types" option must be a list of "%s" cases, "%s" given.', ProductType::class, get_debug_type($type))); + } + } + + $this->types = array_unique($types, SORT_REGULAR); + } + + public function getDefaultOption(): string + { + return 'types'; + } + + public function getRequiredOptions(): array + { + return ['types']; + } +} diff --git a/modules/inpostizi/src/Validator/Product/NotOfTypeValidator.php b/modules/inpostizi/src/Validator/Product/NotOfTypeValidator.php new file mode 100644 index 00000000..9a179b15 --- /dev/null +++ b/modules/inpostizi/src/Validator/Product/NotOfTypeValidator.php @@ -0,0 +1,45 @@ +types as $type) { + if ( + $baseType !== $type + && (ProductType::Customizable() !== $type || !$this->isCustomizable($value)) + ) { + continue; + } + + $this->context + ->buildViolation('Product is of restricted type "{{ type }}".') + ->setParameter('{{ type }}', $type->value) + ->addViolation(); + } + } + + private function isCustomizable($product): bool + { + return 0 < ($product['customizable'] ?? 0); + } +} diff --git a/modules/inpostizi/src/Validator/Product/NotWithRestrictedAttributes.php b/modules/inpostizi/src/Validator/Product/NotWithRestrictedAttributes.php new file mode 100644 index 00000000..964e6f16 --- /dev/null +++ b/modules/inpostizi/src/Validator/Product/NotWithRestrictedAttributes.php @@ -0,0 +1,20 @@ +repository = $repository; + $this->combinationRepository = $combinationRepository ?? self::createCombinationRepository(); + } + + public function validate($value, Constraint $constraint): void + { + if (!$constraint instanceof NotWithRestrictedAttributes) { + throw new UnexpectedTypeException($constraint, NotWithRestrictedAttributes::class); + } + + if (!is_array($value) && !$value instanceof \ArrayAccess) { + throw new UnexpectedTypeException($value, 'array|ArrayAccess'); + } + + if ([] === $attributes = ($value['attributes'] ?? [])) { + return; + } + + if (!is_array($attributes)) { + $attributeGroupIds = $this->combinationRepository->getAttributeGroupIds((int) $value['id_product'], (int) $value['id_product_attribute']); + } else { + $attributeGroupIds = array_keys($attributes); + } + + if (!$this->repository->isAnyAttributeGroupRestricted($attributeGroupIds, $constraint->shopId)) { + return; + } + + $this->context + ->buildViolation('Product has attributes from restricted groups.') + ->addViolation(); + } + + private static function createCombinationRepository(): ObjectRepositoryInterface + { + /** @var \InPostIzi $module */ + $module = \Module::getInstanceByName('inpostizi'); + + return $module->get(ObjectManagerInterface::class)->getRepository(\Combination::class); + } +} diff --git a/modules/inpostizi/src/Validator/Product/NotWithRestrictedFeatures.php b/modules/inpostizi/src/Validator/Product/NotWithRestrictedFeatures.php new file mode 100644 index 00000000..62f3990c --- /dev/null +++ b/modules/inpostizi/src/Validator/Product/NotWithRestrictedFeatures.php @@ -0,0 +1,20 @@ +repository = $repository; + } + + public function validate($value, Constraint $constraint): void + { + if (!$constraint instanceof NotWithRestrictedFeatures) { + throw new UnexpectedTypeException($constraint, NotWithRestrictedFeatures::class); + } + + if (!is_array($value) && !$value instanceof \ArrayAccess) { + throw new UnexpectedTypeException($value, 'array|ArrayAccess'); + } + + if ([] === $features = ($value['features'] ?? [])) { + return; + } + + $featureIds = array_map(static function (array $feature): int { + return (int) $feature['id_feature']; + }, $features); + + if (!$this->repository->isAnyFeatureRestricted($featureIds, $constraint->shopId)) { + return; + } + + $this->context + ->buildViolation('Product has restricted features.') + ->addViolation(); + } +} diff --git a/modules/inpostizi/src/Validator/Product/Unrestricted.php b/modules/inpostizi/src/Validator/Product/Unrestricted.php new file mode 100644 index 00000000..fce0ba29 --- /dev/null +++ b/modules/inpostizi/src/Validator/Product/Unrestricted.php @@ -0,0 +1,20 @@ + constraints by shop ID + */ + private $constraints; + + public function __construct(ProductRestrictionsConfigurationInterface $productRestrictionConstraints) + { + $this->productRestrictionConstraints = $productRestrictionConstraints; + } + + public function validate($value, Constraint $constraint): void + { + if (!$constraint instanceof Unrestricted) { + throw new UnexpectedTypeException($constraint, Unrestricted::class); + } + + if (null === $value) { + return; + } + + if (!is_array($value) && !$value instanceof \ArrayAccess) { + throw new UnexpectedTypeException($value, 'array|ArrayAccess'); + } + + $constraints = $this->getConstraints($constraint->shopId); + if (empty($constraints)) { + return; + } + + $validator = $this->context->getValidator()->inContext($this->context); + $validator->validate($value, new Sequentially($constraints)); + } + + /** + * @return Constraint[] + */ + private function getConstraints(?int $shopId): array + { + $key = (int) $shopId; + + if (!isset($this->constraints[$key])) { + $this->constraints[$key] = $this->productRestrictionConstraints->getProductRestrictionConstraints($shopId); + } + + return $this->constraints[$key]; + } +} diff --git a/modules/inpostizi/src/Validator/Sequentially.php b/modules/inpostizi/src/Validator/Sequentially.php new file mode 100644 index 00000000..b0287bc8 --- /dev/null +++ b/modules/inpostizi/src/Validator/Sequentially.php @@ -0,0 +1,46 @@ + + */ +class Sequentially extends Composite +{ + public $constraints = []; + + public function __construct($constraints = null) + { + parent::__construct($constraints ?? []); + } + + public function getDefaultOption(): ?string + { + return 'constraints'; + } + + public function getRequiredOptions(): array + { + return ['constraints']; + } + + protected function getCompositeOption(): string + { + return 'constraints'; + } + + public function getTargets(): array + { + return [self::CLASS_CONSTRAINT, self::PROPERTY_CONSTRAINT]; + } +} diff --git a/modules/inpostizi/src/Validator/SequentiallyValidator.php b/modules/inpostizi/src/Validator/SequentiallyValidator.php new file mode 100644 index 00000000..a1b7a4b3 --- /dev/null +++ b/modules/inpostizi/src/Validator/SequentiallyValidator.php @@ -0,0 +1,34 @@ + + */ +final class SequentiallyValidator extends ConstraintValidator +{ + public function validate($value, Constraint $constraint): void + { + if (!$constraint instanceof Sequentially) { + throw new UnexpectedTypeException($constraint, Sequentially::class); + } + + $context = $this->context; + + $validator = $context->getValidator()->inContext($context); + + $originalCount = $validator->getViolations()->count(); + + foreach ($constraint->constraints as $nestedConstraint) { + if ($originalCount !== $validator->validate($value, $nestedConstraint)->getViolations()->count()) { + break; + } + } + } +} diff --git a/modules/inpostizi/src/Validator/Unique.php b/modules/inpostizi/src/Validator/Unique.php new file mode 100644 index 00000000..370c8636 --- /dev/null +++ b/modules/inpostizi/src/Validator/Unique.php @@ -0,0 +1,39 @@ + + */ +final class Unique extends Constraint +{ + /** + * @var string + */ + public $message = 'This collection should contain only unique elements.'; + + /** + * @var callable + */ + public $normalizer; + + public function __construct(?array $options = null, ?string $message = null, ?callable $normalizer = null) + { + parent::__construct($options); + + $this->message = $message ?? $this->message; + $this->normalizer = $normalizer ?? $this->normalizer; + + if (null !== $this->normalizer && !is_callable($this->normalizer)) { + throw new InvalidArgumentException(sprintf('The "normalizer" option must be a valid callable ("%s" given).', get_debug_type($this->normalizer))); + } + } +} diff --git a/modules/inpostizi/src/Validator/UniqueValidator.php b/modules/inpostizi/src/Validator/UniqueValidator.php new file mode 100644 index 00000000..c952cc8f --- /dev/null +++ b/modules/inpostizi/src/Validator/UniqueValidator.php @@ -0,0 +1,57 @@ + + */ +final class UniqueValidator extends ConstraintValidator +{ + public function validate($value, Constraint $constraint): void + { + if (!$constraint instanceof Unique) { + throw new UnexpectedTypeException($constraint, Unique::class); + } + + if (null === $value) { + return; + } + + if (!is_array($value) && !$value instanceof \IteratorAggregate) { + throw new UnexpectedValueException($value, 'array|IteratorAggregate'); + } + + $collectionElements = []; + $normalizer = $this->getNormalizer($constraint); + + foreach ($value as $element) { + $element = $normalizer($element); + + if (!in_array($element, $collectionElements, true)) { + $collectionElements[] = $element; + + continue; + } + + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($element)) + ->addViolation(); + + return; + } + } + + private function getNormalizer(Unique $unique): callable + { + return $unique->normalizer ?? static function ($value) { + return $value; + }; + } +} diff --git a/modules/inpostizi/src/Validator/ValidatorFactory.php b/modules/inpostizi/src/Validator/ValidatorFactory.php new file mode 100644 index 00000000..be979861 --- /dev/null +++ b/modules/inpostizi/src/Validator/ValidatorFactory.php @@ -0,0 +1,37 @@ +setConstraintValidatorFactory($constraintValidatorFactory) + ->getValidator(); + } + + private static function createConstraintValidatorFactory(ContainerInterface $container): ConstraintValidatorFactoryInterface + { + return class_exists(ContainerConstraintValidatorFactory::class) + ? new ContainerConstraintValidatorFactory($container) + : new ConstraintValidatorFactory($container); + } +} diff --git a/modules/inpostizi/src/View/Asset/AbstractAssetManager.php b/modules/inpostizi/src/View/Asset/AbstractAssetManager.php new file mode 100644 index 00000000..cc3987b3 --- /dev/null +++ b/modules/inpostizi/src/View/Asset/AbstractAssetManager.php @@ -0,0 +1,71 @@ +module = $module; + $this->context = $context; + $this->versionStrategy = $versionStrategy; + } + + public function registerJavaScriptVariables(array $variables): AssetManagerInterface + { + \Media::addJsDef($variables); + + return $this; + } + + /** + * @return PathPackage + */ + public function getPackage(): PackageInterface + { + return $this->package ?? ($this->package = $this->createPackage()); + } + + abstract protected function getBasePath(): string; + + private function createPackage(): PathPackage + { + $basePath = $this->getBasePath(); + $versionStrategy = $this->getVersionStrategy(); + + return new PathPackage($basePath, $versionStrategy, $this->context); + } + + private function getVersionStrategy(): VersionStrategyInterface + { + return $this->versionStrategy ?? ($this->versionStrategy = new StaticVersionStrategy($this->module->version, '%s?v=%s')); + } +} diff --git a/modules/inpostizi/src/View/Asset/AdminAssetManager.php b/modules/inpostizi/src/View/Asset/AdminAssetManager.php new file mode 100644 index 00000000..5ac20dda --- /dev/null +++ b/modules/inpostizi/src/View/Asset/AdminAssetManager.php @@ -0,0 +1,46 @@ +context = $context; + } + + public function registerJavaScript(string $path, array $options = []): AssetManagerInterface + { + $url = $this->getPackage()->getUrl($path); + $this->getController()->addJS($url); + + return $this; + } + + public function registerStyleSheet(string $path, array $options = []): AssetManagerInterface + { + $url = $this->getPackage()->getUrl($path); + $this->getController()->addCSS($url, $options['media'] ?? 'all', null, false); + + return $this; + } + + protected function getBasePath(): string + { + return sprintf('%s/views', rtrim($this->module->getPathUri(), '/')); + } + + private function getController(): \AdminController + { + return $this->context->controller; + } +} diff --git a/modules/inpostizi/src/View/Asset/AssetManagerInterface.php b/modules/inpostizi/src/View/Asset/AssetManagerInterface.php new file mode 100644 index 00000000..ea59f136 --- /dev/null +++ b/modules/inpostizi/src/View/Asset/AssetManagerInterface.php @@ -0,0 +1,27 @@ +createVersionStrategy($module)); + + $this->context = $context; + } + + public function registerJavaScript(string $path, array $options = []): AssetManagerInterface + { + $options = $this->resolveOptions($path, $options); + $this->getController()->registerJavascript($options['id'], $path, $options); + + return $this; + } + + public function registerStyleSheet(string $path, array $options = []): AssetManagerInterface + { + $options = $this->resolveOptions($path, $options); + $this->getController()->registerStylesheet($options['id'], $path, $options); + + return $this; + } + + protected function getBasePath(): string + { + return sprintf('modules/%s/views', $this->module->name); + } + + private function resolveOptions(string &$path, array $options): array + { + $options['id'] = $options['id'] ?? $this->getAssetId($path); + if ($this->isAbsoluteUrl($path)) { + $options['server'] = $options['server'] ?? 'remote'; + } else { + $path = $this->getPackage()->getUrl($path); + } + + return $options; + } + + private function getController(): \FrontController + { + return $this->context->controller; + } + + private function getAssetId(string $path): string + { + return sprintf('modules-%s-%s', $this->module->name, pathinfo($path, PATHINFO_FILENAME)); + } + + private function isAbsoluteUrl(string $url): bool + { + return false !== strpos($url, '://') || 0 === strpos($url, '//'); + } + + private function createVersionStrategy(\Module $module): VersionStrategyInterface + { + $manifestPath = sprintf('%s/views/manifest.json', rtrim($module->getLocalPath(), '/')); + + return class_exists(JsonManifestVersionStrategy::class) + ? new JsonManifestVersionStrategy($manifestPath) + : new VersionStrategyPolyfill($manifestPath); + } +} diff --git a/modules/inpostizi/src/View/Asset/Provider/Admin/CartRulesAssetsProvider.php b/modules/inpostizi/src/View/Asset/Provider/Admin/CartRulesAssetsProvider.php new file mode 100644 index 00000000..355ea01b --- /dev/null +++ b/modules/inpostizi/src/View/Asset/Provider/Admin/CartRulesAssetsProvider.php @@ -0,0 +1,45 @@ +context = $context; + $this->requestStack = $requestStack; + } + + public function getAssets(): ?Assets + { + if (!$this->context->controller instanceof \AdminCartRulesControllerCore) { + return null; + } + + if (null === $request = $this->requestStack->getCurrentRequest()) { + return null; + } + + if (!$request->query->has('addcart_rule') && !$request->query->has('updatecart_rule')) { + return null; + } + + return (new Assets())->addJavaScript('js/admin/cart-rules.js'); + } +} diff --git a/modules/inpostizi/src/View/Asset/Provider/AssetsProviderInterface.php b/modules/inpostizi/src/View/Asset/Provider/AssetsProviderInterface.php new file mode 100644 index 00000000..bb12b04b --- /dev/null +++ b/modules/inpostizi/src/View/Asset/Provider/AssetsProviderInterface.php @@ -0,0 +1,12 @@ + + */ + private $javaScripts = []; + + /** + * @var array + */ + private $styleSheets = []; + + /** + * @var array + */ + private $jsVariables = []; + + /** + * @return array options by path + */ + public function getJavaScripts(): array + { + return $this->javaScripts; + } + + public function addJavaScript(string $path, array $options = []): self + { + $this->javaScripts[$path] = $options; + + return $this; + } + + public function removeJavaScript(string $path): self + { + unset($this->javaScripts[$path]); + + return $this; + } + + /** + * @return array options by path + */ + public function getStyleSheets(): array + { + return $this->styleSheets; + } + + public function addStyleSheet(string $path, array $options = []): self + { + $this->styleSheets[$path] = $options; + + return $this; + } + + /** + * @return array values by name + */ + public function getJavaScriptVariables(): array + { + return $this->jsVariables; + } + + public function addJavaScriptVariable(string $name, $value): self + { + $this->jsVariables[$name] = $value; + + return $this; + } +} diff --git a/modules/inpostizi/src/View/Asset/Provider/Front/CommonAssetsProvider.php b/modules/inpostizi/src/View/Asset/Provider/Front/CommonAssetsProvider.php new file mode 100644 index 00000000..1c5e46f9 --- /dev/null +++ b/modules/inpostizi/src/View/Asset/Provider/Front/CommonAssetsProvider.php @@ -0,0 +1,52 @@ +module = $module; + $this->context = $context; + $this->environment = $environment; + } + + public function getAssets(): Assets + { + return (new Assets()) + ->addJavaScript($this->environment->getWidgetJavaScriptUri(), [ + 'id' => 'inpostpay-widget', + 'position' => 'bottom', + 'priority' => 100, + ]) + ->addJavaScript('v2.js', [ + 'position' => 'bottom', + 'priority' => 101, + ]) + ->addJavaScriptVariable('inpostizi_backend_ajax_url', $this->context->link->getModuleLink($this->module->name, 'backend')) + ->addJavaScriptVariable('inpostizi_generic_http_error', $this->module->l('Something went wrong. Please try again later.', \Tools::strtolower(ActionFrontControllerSetMedia::HOOK_NAME))); + } +} diff --git a/modules/inpostizi/src/View/Asset/Provider/Front/ProductPageAssetsProvider.php b/modules/inpostizi/src/View/Asset/Provider/Front/ProductPageAssetsProvider.php new file mode 100644 index 00000000..8b9a8b5f --- /dev/null +++ b/modules/inpostizi/src/View/Asset/Provider/Front/ProductPageAssetsProvider.php @@ -0,0 +1,44 @@ +configuration = $configuration; + $this->context = $context; + } + + public function getAssets(): ?Assets + { + if (!$this->context->controller instanceof \ProductControllerCore) { + return null; + } + + $assets = new Assets(); + + if (DisplayProductActions::HOOK_NAME === $this->configuration->getProductCardDisplayHook()) { + $assets->addStyleSheet('product.css'); + } + + return $assets; + } +} diff --git a/modules/inpostizi/src/View/Asset/Provider/Front/WidgetConfigurationProvider.php b/modules/inpostizi/src/View/Asset/Provider/Front/WidgetConfigurationProvider.php new file mode 100644 index 00000000..1cca4dde --- /dev/null +++ b/modules/inpostizi/src/View/Asset/Provider/Front/WidgetConfigurationProvider.php @@ -0,0 +1,77 @@ + $repository + */ + public function __construct(\Context $context, ApiConfigurationInterface $apiConfiguration, GeneralConfigurationInterface $configuration, BasketSessionRepositoryInterface $repository) + { + $this->context = $context; + $this->apiConfiguration = $apiConfiguration; + $this->configuration = $configuration; + $this->repository = $repository; + } + + public function getAssets(): Assets + { + $fetchAfterRender = $this->shouldFetchBindingKeyAfterPageRender(); + $bindingApiKey = $fetchAfterRender ? null : $this->getBindingApiKey(); + + return (new Assets()) + ->addJavaScriptVariable('inpostizi_fetch_binding_key', $fetchAfterRender) + ->addJavaScriptVariable('inpostizi_merchant_client_id', $this->apiConfiguration->getMerchantClientId()) + ->addJavaScriptVariable('inpostizi_binding_api_key', $bindingApiKey); + } + + private function getBindingApiKey(): ?string + { + $cartId = (int) $this->context->cart->id; + + if (null === $session = $this->repository->findByEntityId($cartId)) { + return null; + } + + return $session->getBindingApiKey(); + } + + private function shouldFetchBindingKeyAfterPageRender(): bool + { + if (!$this->context->controller instanceof \ProductControllerCore) { + return false; + } + + return $this->configuration->isFullPageCacheModuleInUse(); + } +} diff --git a/modules/inpostizi/src/View/Asset/VersionStrategy/JsonManifestVersionStrategy.php b/modules/inpostizi/src/View/Asset/VersionStrategy/JsonManifestVersionStrategy.php new file mode 100644 index 00000000..9794ad22 --- /dev/null +++ b/modules/inpostizi/src/View/Asset/VersionStrategy/JsonManifestVersionStrategy.php @@ -0,0 +1,57 @@ +manifestPath = $manifestPath; + } + + /** + * With a manifest, we don't really know or care about what + * the version is. Instead, this returns the path to the + * versioned file. + */ + public function getVersion($path): string + { + return $this->applyVersion($path); + } + + public function applyVersion($path): string + { + return $this->getManifestPath($path) ?: $path; + } + + private function getManifestPath(string $path): ?string + { + if (null === $this->manifestData) { + if (!file_exists($this->manifestPath)) { + throw new \RuntimeException(sprintf('Asset manifest file "%s" does not exist. Did you forget to build the assets with npm or yarn?', $this->manifestPath)); + } + + $this->manifestData = json_decode(file_get_contents($this->manifestPath), true); + if (0 < json_last_error()) { + throw new \RuntimeException(sprintf('Error parsing JSON from asset manifest file "%s": ', $this->manifestPath) . json_last_error_msg()); + } + } + + return $this->manifestData[$path] ?? null; + } +} diff --git a/modules/inpostizi/src/View/Templating/RendererInterface.php b/modules/inpostizi/src/View/Templating/RendererInterface.php new file mode 100644 index 00000000..f80171a9 --- /dev/null +++ b/modules/inpostizi/src/View/Templating/RendererInterface.php @@ -0,0 +1,10 @@ +smarty = $smarty; + } + + public function render(string $name, array $parameters = []): string + { + $isAdmin = $this->adjustModuleResourcePluginConfig(); + + try { + return $this->doRender($name, $parameters); + } finally { + $this->restoreModuleResourcePluginConfig($isAdmin); + } + } + + private function doRender(string $name, array $parameters) + { + if ([] !== $parameters) { + $scope = $this->smarty->createData($this->smarty); + $scope->assign($parameters); + } else { + $scope = $this->smarty; + } + + return $this->smarty + ->createTemplate($name, null, null, $scope) + ->fetch(); + } + + private function adjustModuleResourcePluginConfig(): ?bool + { + if (null === $plugin = $this->getModuleResourcePlugin()) { + $this->smarty->registered_resources['module'] = new \SmartyResourceModule([ + 'modules' => _PS_MODULE_DIR_, + ]); + + return null; + } + + if (!isset($plugin->isAdmin)) { + return null; + } + + $isAdmin = $plugin->isAdmin; + $plugin->isAdmin = false; + + return $isAdmin; + } + + private function restoreModuleResourcePluginConfig(?bool $isAdmin): void + { + if (null === $isAdmin) { + return; + } + + $this->getModuleResourcePlugin()->isAdmin = $isAdmin; + } + + private function getModuleResourcePlugin(): ?\SmartyResourceModule + { + return $this->smarty->registered_resources['module'] ?? null; + } +} diff --git a/modules/inpostizi/src/View/Widget/FrameStyle.php b/modules/inpostizi/src/View/Widget/FrameStyle.php new file mode 100644 index 00000000..8a6997d0 --- /dev/null +++ b/modules/inpostizi/src/View/Widget/FrameStyle.php @@ -0,0 +1,15 @@ +l('Extra small', 'size'); + case self::Small(): + return $translator->l('Small', 'size'); + case self::Medium(): + return $translator->l('Medium', 'size'); + case self::Large(): + return $translator->l('Large', 'size'); + case self::ExtraLarge(): + return $translator->l('Extra large', 'size'); + default: + throw new \LogicException('Unreachable statement.'); + } + } +} diff --git a/modules/inpostizi/src/View/Widget/Variant.php b/modules/inpostizi/src/View/Widget/Variant.php new file mode 100644 index 00000000..81d31c02 --- /dev/null +++ b/modules/inpostizi/src/View/Widget/Variant.php @@ -0,0 +1,17 @@ +bindingPlace = $bindingPlace; + } + + public function getBindingPlace(): BindingPlace + { + return $this->bindingPlace; + } + + public function withBindingPlace(BindingPlace $bindingPlace): self + { + $clone = clone $this; + $clone->bindingPlace = $bindingPlace; + + return $clone; + } + + public function getProductId(): ?string + { + return $this->productId; + } + + /** + * @return $this + */ + public function setProductId(?string $productId): WidgetConfigurationInterface + { + $this->productId = $productId; + + return $this; + } + + public function getFrameStyle(): ?FrameStyle + { + return $this->frameStyle; + } + + /** + * @return $this + */ + public function setFrameStyle(?FrameStyle $frameStyle): self + { + $this->frameStyle = $frameStyle; + + return $this; + } + + public function isDarkMode(): bool + { + return true === $this->darkMode; + } + + /** + * @return $this + */ + public function setDarkMode(?bool $darkMode): self + { + $this->darkMode = $darkMode; + + return $this; + } + + public function getVariant(): Variant + { + return $this->variant ?? Variant::Secondary(); + } + + /** + * @return $this + */ + public function setVariant(?Variant $variant): self + { + $this->variant = $variant; + + return $this; + } + + public function getSize(): ?Size + { + return $this->size; + } + + /** + * @return $this + */ + public function setSize(?Size $size): self + { + $this->size = $size; + + return $this; + } + + public function getMaxWidthPx(): ?int + { + return $this->maxWidthPx; + } + + /** + * @return $this + */ + public function setMaxWidthPx(?int $maxWidth): self + { + $this->maxWidthPx = $maxWidth; + + return $this; + } + + public function jsonSerialize(): array + { + return array_filter(get_object_vars($this), static function ($value) { + return null !== $value; + }); + } + + /** + * @return \Traversable + */ + public function getIterator(): \Traversable + { + yield 'binding_place' => $this->bindingPlace->value; + + if (null !== $this->productId) { + yield 'data-product-id' => $this->productId; + } + + if (null !== $variation = $this->getVariation()) { + yield 'variation' => $variation; + } + + if (null !== $this->maxWidthPx) { + yield 'style' => sprintf('max-width: %dpx;', $this->maxWidthPx); + } + } + + private function getVariation(): ?string + { + $parts = []; + + if (null !== $this->frameStyle) { + $parts[] = $this->frameStyle->value; + } + + if ($this->isDarkMode()) { + $parts[] = 'dark'; + } + + if (Variant::Primary() === $variant = $this->getVariant()) { + $parts[] = $variant->value; + } + + if (null !== $this->size) { + $parts[] = $this->size->value; + } + + if ([] === $parts) { + return null; + } + + return implode(' ', $parts); + } +} diff --git a/modules/inpostizi/src/View/Widget/WidgetConfigurationInterface.php b/modules/inpostizi/src/View/Widget/WidgetConfigurationInterface.php new file mode 100644 index 00000000..2fd583f8 --- /dev/null +++ b/modules/inpostizi/src/View/Widget/WidgetConfigurationInterface.php @@ -0,0 +1,22 @@ + HTML attribute values by name + */ +interface WidgetConfigurationInterface extends \IteratorAggregate, \JsonSerializable +{ + public function getBindingPlace(): BindingPlace; + + public function getProductId(): ?string; + + /** + * @return $this + */ + public function setProductId(?string $productId): self; +} diff --git a/modules/inpostizi/src/View/Widget/WidgetConfigurationResolver.php b/modules/inpostizi/src/View/Widget/WidgetConfigurationResolver.php new file mode 100644 index 00000000..813f965e --- /dev/null +++ b/modules/inpostizi/src/View/Widget/WidgetConfigurationResolver.php @@ -0,0 +1,104 @@ + + */ +final class WidgetConfigurationResolver implements WidgetConfigurationResolverInterface +{ + /** + * @var OptionsResolver|null + */ + private $optionsResolver; + + public function resolve(array $options): WidgetConfigurationInterface + { + if (isset($options['config']) && $options['config'] instanceof WidgetConfigurationInterface) { + return $options['config']; + } + + $options = $this + ->getOptionsResolver() + ->setDefined(array_keys($options)) + ->resolve($options); + + return (new WidgetConfiguration($options['binding_place'])) + ->setVariant($options['variant']) + ->setDarkMode($options['dark_mode']) + ->setFrameStyle($options['frame_style']) + ->setMaxWidthPx($options['max_width']) + ->setProductId($options['product_id']); + } + + private function getOptionsResolver(): OptionsResolver + { + return $this->optionsResolver ?? ($this->optionsResolver = $this->createOptionsResolver()); + } + + private function createOptionsResolver(): OptionsResolver + { + return (new OptionsResolver()) + ->setDefaults([ + 'binding_place' => BindingPlace::ProductCard(), + 'variant' => Variant::Secondary(), + 'dark_mode' => false, + 'frame_style' => null, + 'max_width' => null, + 'product_id' => null, + ]) + ->setAllowedTypes('binding_place', [BindingPlace::class, 'string']) + ->setNormalizer('binding_place', static function (Options $options, $value) { + if ($value instanceof BindingPlace) { + return $value; + } + + return BindingPlace::from($value); + }) + ->setAllowedTypes('variant', [Variant::class, 'string']) + ->setNormalizer('variant', static function (Options $options, $value) { + if ($value instanceof Variant) { + return $value; + } + + return Variant::from($value); + }) + ->setAllowedTypes('dark_mode', 'bool') + ->setAllowedTypes('frame_style', [FrameStyle::class, 'string', 'null']) + ->setNormalizer('frame_style', static function (Options $options, $value) { + if (null === $value || $value instanceof FrameStyle) { + return $value; + } + + return FrameStyle::from($value); + }) + ->setAllowedValues('max_width', static function ($value) { + return self::isValidWidth($value); + }) + ->setAllowedTypes('product_id', ['string', 'int', 'null']) + ->setNormalizer('product_id', static function (Options $options, $value) { + if (null === $value) { + return null; + } + + return (string) $value; + }); + } + + private static function isValidWidth($width): bool + { + if (null === $width) { + return true; + } + + $width = (int) $width; + + return WidgetConfiguration::WIDTH_MIN_PX <= $width && WidgetConfiguration::WIDTH_MAX_PX >= $width; + } +} diff --git a/modules/inpostizi/src/View/Widget/WidgetConfigurationResolverInterface.php b/modules/inpostizi/src/View/Widget/WidgetConfigurationResolverInterface.php new file mode 100644 index 00000000..88e9f995 --- /dev/null +++ b/modules/inpostizi/src/View/Widget/WidgetConfigurationResolverInterface.php @@ -0,0 +1,14 @@ + + */ + private $repository; + + /** + * @var EventDispatcherInterface + */ + private $eventDispatcher; + + /** + * @var ValidatorInterface + */ + private $validator; + + public function __construct(?\Context $context = null, ?Hashing $crypto = null, ?\PaymentModule $module = null, ShippingConfigurationInterface $shippingConfiguration = null, ?CommandBusInterface $bus = null, ?BasketSessionRepositoryInterface $repository = null, ?EventDispatcherInterface $eventDispatcher = null, ?ValidatorInterface $validator = null) + { + $this->context = $context ?? \Context::getContext(); + $this->crypto = $crypto ?? new Hashing(); + $this->module = $module ?? \Module::getInstanceByName('inpostizi'); + $this->shippingConfiguration = $shippingConfiguration ?? $this->module->get(ShippingConfigurationInterface::class); + $this->bus = $bus ?? $this->module->get(CommandBusInterface::class); + $this->ordersConfiguration = $this->module->get(OrdersConfigurationInterface::class); + $this->repository = $repository ?? new BasketSessionRepository(SerializerFactory::create(), $this->module->get(ObjectManagerInterface::class)); + $this->eventDispatcher = $eventDispatcher ?? $this->module->get(EventDispatcherInterface::class); + $this->validator = $validator ?? $this->module->get('inpost.izi.validator'); + } + + /** + * @return int order identifier + */ + public function handleRequest(CreateOrderRequest $request): int + { + $session = $this->getSession($request->getOrderDetails()->getBasketId()); + + if (null !== $orderId = $session->getOrderId()) { + return (int) $orderId; + } + + $cart = $session->getBasket()->getEntity(); + + if (null === $order = $this->getOrderByCart($cart)) { + return $this->createOrder($session, $request); + } + + if ('inpostizi' !== $order->module) { + throw new CannotCreateOrderException($this->module->l('There already exists an order for this basket.', self::TRANSLATION_SOURCE)); + } + + $this->module->getLogger()->warning('Repeated order request for cart #{cartId}. Updating delivery emails.', [ + 'cartId' => $cart->id, + 'orderId' => $order->id, + ]); + + if (null !== $originalRequest = $session->getOrderRequest()) { + $request = $originalRequest->withDeliveryEmails($request->getDelivery()); + } + + $this->finalizeSession($session, $request, (int) $order->id); + $this->saveCarrierModuleData($cart->id, $request->getDelivery()); + + return (int) $order->id; + } + + private function getSession(string $basketId): BasketSession + { + $session = $this->repository->findByBasketId($basketId); + + if (null === $session) { + throw BasketNotFoundException::create(); + } + + return $session; + } + + private function getOrderByCart(\Cart $cart): ?\OrderCore + { + if (is_callable([\Order::class, 'getByCartId'])) { + return \Order::getByCartId($cart->id); + } + + $orderId = \Order::getOrderByCartId($cart->id); + + return $orderId ? new \Order($orderId) : null; + } + + /** + * @param BasketSession $session + */ + private function createOrder(BasketSessionInterface $session, CreateOrderRequest $request): int + { + $cart = $session->getBasket()->getEntity(); + + $deliveryType = $request->getDelivery()->getType(); + $serviceCodes = $request->getDelivery()->getOptionalServiceCodes(); + + $shopId = $session->getShopId() ?? (int) $cart->id_shop; + + $shippingOptions = $this->shippingConfiguration->getShippingOptions($deliveryType, $shopId); + $carrierReferenceId = $shippingOptions->getCarrierMapping(...$serviceCodes)->getReferenceId(); + + if (null === $carrierReferenceId || null === $carrierId = $this->getCarrierId($carrierReferenceId, $shopId)) { + throw new InternalServerErrorException(sprintf('No valid carrier mapping configured for delivery type "%s"', $deliveryType->value)); + } + + $paymentType = $request->getOrderDetails()->getPaymentType(); + + $this->checkPaymentType($paymentType, $shopId); + + $customer = $this->findOrCreateCustomer($cart, $request->getAccountInfo()); + $addresses = $this->findOrCreateAddresses($customer, $request); + + $this->setUpContext($cart, $addresses, $shopId); + $this->updateCart($cart, $carrierId, $addresses); + $this->updateCartMessage($cart, $request); + $this->adjustHandlingCost($shippingOptions, $serviceCodes); + $this->validateCart($cart, $request); + + $orderId = null; + + $this->eventDispatcher->addListener(ValidateOrderEvent::class, $listener = function (ValidateOrderEvent $event) use ($session, $request, $cart, &$orderId) { + if ($event->getOrder()->module !== $this->module->name) { + return; + } + + $orderId = (int) $event->getOrder()->id; + + $this->finalizeSession($session, $request, $orderId); + $this->saveCarrierModuleData($cart->id, $request->getDelivery()); + }); + + try { + $this->module->validateOrder( + $cart->id, + (int) $this->ordersConfiguration->getInitialStatusId($paymentType, $shopId), + $cart->getOrderTotal(), + $this->module->displayName, + null, + [], + null, + false, + $cart->secure_key, + $this->context->shop + ); + + return (int) $this->module->currentOrder; + } catch (\Exception $exception) { + if (null === $orderId) { + throw $exception; + } + + $this->module->getLogger()->error('An exception occurred after creating order #{orderId}: {exception}.', [ + 'orderId' => $orderId, + 'exception' => $exception, + ]); + + return $orderId; + } finally { + $this->eventDispatcher->removeListener(ValidateOrderEvent::class, $listener); + } + } + + private function findOrCreateAddresses(\Customer $customer, CreateOrderRequest $request): array + { + $accountInfo = $request->getAccountInfo(); + + $deliveryAddress = $this->findOrCreateDeliveryAddress($accountInfo, $customer, $request->getDelivery()->getAddress()); + if (null !== $invoiceDetails = $request->getInvoiceDetails()) { + $invoiceAddress = $this->findOrCreateInvoiceAddress($invoiceDetails, $accountInfo, $customer); + } else { + $invoiceAddress = $deliveryAddress; + } + + return [ + 'delivery' => $deliveryAddress, + 'invoice' => $invoiceAddress, + ]; + } + + /** + * @param array{delivery: \AddressCore, invoice: \AddressCore} $addresses + */ + private function updateCart(\Cart $cart, int $carrierId, array $addresses): void + { + $deliveryAddressId = (int) $addresses['delivery']->id; + + $cart->updateAddressId($cart->id_address_delivery, $deliveryAddressId); + $this->setDeliveryOption($cart, [$deliveryAddressId => $carrierId . ',']); + $cart->id_address_invoice = (int) $addresses['invoice']->id; + + if (!$cart->update()) { + throw new InternalServerErrorException('Could not update cart data.'); + } + } + + private function setDeliveryOption(\Cart $cart, array $deliveryOption): void + { + $cart->setDeliveryOption($deliveryOption); + + if ($deliveryOption === $cart->getDeliveryOption(null, true)) { + return; + } + + throw new CannotCreateOrderException($this->module->l('The selected delivery option is not available.', self::TRANSLATION_SOURCE)); + } + + private function getCountryId(string $code): int + { + if (0 >= $countryId = (int) \Country::getByIso(strtoupper($code))) { + throw new CannotCreateOrderException(sprintf($this->module->l('Selected country (%s) is not available.', self::TRANSLATION_SOURCE), $code)); + } + + return $countryId; + } + + private function findOrCreateDeliveryAddress(AccountInfo $accountInfo, \Customer $customer, ?DeliveryAddress $deliveryAddress = null): \AddressCore + { + $address = $this->createAddress(); + + $address->id_customer = $customer->id; + + $this->setRequiredPhoneNumbers($address, $accountInfo); + if (!$address->phone && !$address->phone_mobile) { + $address->phone = $this->formatPhoneNumber($accountInfo->getPhoneNumber()); + } + + if (null !== $deliveryAddress) { + $this->fillWithDeliveryAddressData($address, $deliveryAddress); + } else { + $this->fillWithAccountInfoData($address, $accountInfo); + } + + if ($existingAddress = $this->findExistingAddress($customer, $address)) { + return $existingAddress; + } + + $address->alias = \Tools::substr($address->address1, 0, 32); + + if (true !== $validationResult = $address->validateFields(false, true)) { + throw new CannotCreateOrderException(sprintf($this->module->l('Delivery address is not valid: %s', self::TRANSLATION_SOURCE), $validationResult)); + } + + if (!$address->add()) { + throw new InternalServerErrorException('Could not create delivery address.'); + } + + return $address; + } + + private function fillWithDeliveryAddressData(\AddressCore $address, DeliveryAddress $deliveryAddress): void + { + $name = preg_split('/\s+/', $deliveryAddress->getName(), 2, PREG_SPLIT_NO_EMPTY); + + $address->firstname = $name[0]; + $address->lastname = $name[1] ?? '-'; + + $address->id_country = $this->getCountryId($deliveryAddress->getCountryCode()); + $address->city = $deliveryAddress->getCity(); + $address->postcode = $deliveryAddress->getPostalCode(); + + $this->setAddressFields($address, $deliveryAddress->getAddress(), $deliveryAddress->getAddressDetails()); + } + + private function fillWithAccountInfoData(\AddressCore $address, AccountInfo $accountInfo): void + { + $clientAddress = $accountInfo->getAddress(); + + $address->firstname = $accountInfo->getName(); + $address->lastname = $accountInfo->getSurname(); + + $address->id_country = $this->getCountryId($clientAddress->getCountryCode()); + $address->city = $clientAddress->getCity(); + $address->postcode = $clientAddress->getPostalCode(); + + $this->setAddressFields($address, $clientAddress->getAddress(), $clientAddress->getAddressDetails()); + } + + private function findOrCreateInvoiceAddress(InvoiceDetails $invoiceDetails, AccountInfo $accountInfo, \Customer $customer): \AddressCore + { + $address = $this->createAddress(); + + $this->setRequiredPhoneNumbers($address, $accountInfo); + + $address->id_customer = $customer->id; + $address->firstname = $invoiceDetails->getName() ?? $accountInfo->getName(); + $address->lastname = $invoiceDetails->getSurname() ?? $accountInfo->getSurname(); + $address->id_country = $this->getCountryId($invoiceDetails->getCountryCode()); + $address->city = $invoiceDetails->getCity(); + $address->postcode = $invoiceDetails->getPostalCode(); + $address->address1 = $invoiceDetails->getStreet(); + $address->address2 = $invoiceDetails->getBuilding(); + if ('' !== $flat = (string) $invoiceDetails->getFlat()) { + $address->address2 .= ' / ' . $flat; + } + + if (LegalForm::Company() === $invoiceDetails->getLegalForm()) { + $address->company = $invoiceDetails->getCompanyName(); + if ($prefix = $invoiceDetails->getTaxIdPrefix()) { + $address->vat_number = sprintf('%s %s', $prefix, $invoiceDetails->getTaxId()); + } else { + $address->vat_number = $invoiceDetails->getTaxId(); + } + } + + if ($existingAddress = $this->findExistingAddress($customer, $address, ['phone', 'phone_mobile'])) { + return $existingAddress; + } + + $address->alias = \Tools::substr($address->address1 . ' ' . $address->address2, 0, 32); + + if (true !== $validationResult = $address->validateFields(false, true)) { + throw new CannotCreateOrderException(sprintf($this->module->l('Invoice address is not valid: %s', self::TRANSLATION_SOURCE), $validationResult)); + } + + if (!$address->add()) { + throw new InternalServerErrorException('Could not create invoice address.'); + } + + return $address; + } + + private function createAddress(): \AddressCore + { + return class_exists(\CustomerAddress::class) ? new \CustomerAddress() : new \Address(); + } + + private function setRequiredPhoneNumbers(\AddressCore $address, AccountInfo $accountInfo): void + { + if ([] === $requiredFields = $address->getFieldsRequiredDB()) { + return; + } + + $phoneNumber = $this->formatPhoneNumber($accountInfo->getPhoneNumber()); + + foreach (['phone', 'phone_mobile'] as $field) { + if (in_array($field, $requiredFields, true)) { + $address->{$field} = $phoneNumber; + } + } + } + + private function setAddressFields(\AddressCore $address, string $addressLine, ?AddressDetails $addressDetails): void + { + $requiredFields = $address->getFieldsRequiredDB(); + + if (!in_array('address2', $requiredFields, true)) { + $address->address1 = $addressLine; + + return; + } + + if ( + null === $addressDetails + || in_array($building = (string) $addressDetails->getBuilding(), ['', AddressDetails::BUILDING_NUMBER_PLACEHOLDER], true) + ) { + $address->address1 = $addressLine; + $address->address2 = '-'; + + return; + } + + $address->address1 = $addressDetails->getStreet() ?? '-'; + $address->address2 = $building; + if ('' !== $flat = (string) $addressDetails->getFlat()) { + $address->address2 .= ' / ' . $flat; + } + } + + private function formatPhoneNumber(PhoneNumber $phoneNumber): string + { + return $phoneNumber->getCountryPrefix() . ' ' . $phoneNumber->getPhone(); + } + + private function findExistingAddress(\Customer $customer, \AddressCore $address, array $ignoreFields = []): ?\AddressCore + { + if ($customer->is_guest) { + return null; + } + + if (!$addresses = $customer->getAddresses((int) \Configuration::get('PS_LANG_DEFAULT'))) { + return null; + } + + foreach ($addresses as $data) { + if ($this->isSameAddress($address, $data, $ignoreFields)) { + $existingAddress = $this->createAddress(); + $existingAddress->hydrate($data); + + return $existingAddress; + } + } + + return null; + } + + private function isSameAddress(\AddressCore $address, array $data, array $ignoreFields): bool + { + $comparedFields = array_diff([ + 'firstname', + 'lastname', + 'id_country', + 'city', + 'postcode', + 'address1', + 'address2', + 'company', + 'vat_number', + 'phone', + 'phone_mobile', + ], $ignoreFields); + + foreach ($comparedFields as $field) { + if ($data[$field] != $address->{$field}) { + return false; + } + } + + return true; + } + + private function findOrCreateCustomer(\Cart $cart, AccountInfo $accountInfo): \Customer + { + $customer = new \Customer($cart->id_customer); + + if (!$customer->is_guest && \Validate::isLoadedObject($customer)) { + return $customer; + } + + $customer->email = $accountInfo->getEmail(); + $customer->firstname = $accountInfo->getName(); + $customer->lastname = $accountInfo->getSurname(); + + if ($newCustomer = !\Validate::isLoadedObject($customer)) { + $password = \Tools::passwdGen(8, 'RANDOM'); + + $customer->id_lang = $cart->id_lang; + $customer->passwd = $this->crypto->hash($password); + $customer->is_guest = true; + } + + if (true !== $validationResult = $customer->validateFields(false, true)) { + throw new CannotCreateOrderException(sprintf($this->module->l('Customer data is not valid: %s', self::TRANSLATION_SOURCE), $validationResult)); + } + + if (!$customer->save()) { + throw new InternalServerErrorException($newCustomer ? 'Could not create customer account.' : 'Could not update customer account.'); + } + + $cart->id_customer = $customer->id; + $cart->secure_key = $customer->secure_key; + + return $customer; + } + + private function adjustHandlingCost(ShippingOptions $shippingOptions, array $serviceCodes): void + { + if (0. === $deliveryOptionsCost = $this->getAdditionalDeliveryOptionsCost($shippingOptions, $serviceCodes)) { + return; + } + + $handlingCost = (float) \Configuration::get('PS_SHIPPING_HANDLING'); + \Configuration::set('PS_SHIPPING_HANDLING', $handlingCost + $deliveryOptionsCost); + \Cache::clean('getPackageShippingCost_*'); + \Cart::resetStaticCache(); + } + + /** + * @param ServiceCode[] $serviceCodes + */ + private function getAdditionalDeliveryOptionsCost(ShippingOptions $shippingOptions, array $serviceCodes): float + { + if ([] === $serviceCodes) { + return 0.; + } + + $additionalCostsPln = 0.; + foreach ($serviceCodes as $serviceCode) { + $serviceOptions = $shippingOptions->getServiceOptions($serviceCode); + + if (null === $serviceOptions || null === $additionalCost = $serviceOptions->getAdditionalCost()) { + continue; + } + + $additionalCostsPln += $additionalCost; + } + + if (0. >= $additionalCostsPln) { + return 0.; + } + + $defaultCurrency = \Currency::getDefaultCurrency(); + if ('PLN' === $defaultCurrency->iso_code) { + return $additionalCostsPln; + } + + return \Tools::convertPriceFull($additionalCostsPln, $this->context->currency, $defaultCurrency); + } + + private function updateCartMessage(\Cart $cart, CreateOrderRequest $request): void + { + try { + $this->bus->handle(new UpdateCartMessageCommand($cart, $request)); + } catch (InvalidDataException $e) { + throw new CannotCreateOrderException($this->module->l('Order comments are not valid.', self::TRANSLATION_SOURCE)); + } catch (\Exception $e) { + throw new InternalServerErrorException('Could not save order comments.', 0, $e); + } + } + + private function saveCarrierModuleData(int $cartId, Delivery $delivery): void + { + if (!class_exists(\InPostCartChoiceModel::class)) { + return; + } + + try { + $model = new \InPostCartChoiceModel($cartId); + + if ($newModel = 0 === (int) $model->id) { + $model->id = $cartId; + } + + $deliveryType = $delivery->getType(); + $phoneNumber = $delivery->getPhoneNumber(); + + if (DeliveryType::Apm() === $deliveryType) { + $model->service = 'inpost_locker_standard'; + $model->point = $delivery->getPoint(); + } else { + $model->service = 'inpost_courier_standard'; + } + $model->email = $delivery->getEmail(); + $model->phone = null === $phoneNumber ? null : $phoneNumber->getPhone(); + + $newModel ? $model->add() : $model->update(); + } catch (\Exception $e) { + $this->module->getLogger()->error('Could not save shipment data: {error}', [ + 'error' => $e, + ]); + } + } + + private function validateCart(\Cart $cart, CreateOrderRequest $request): void + { + $products = $cart->getProducts(); + + if ([] === $products) { + throw new CannotCreateOrderException($this->context->getTranslator()->trans('Cart is empty', [], 'Shop.Notifications.Error')); + } + + $this->validateCartRules($cart); + $this->checkMinimalPurchaseAmount($cart); + $this->checkCartTotal($cart, $request); + + foreach ($products as $product) { + if ($product['minimal_quantity'] > $product['cart_quantity']) { + throw new CannotCreateOrderException($this->context->getTranslator()->trans('The minimum purchase order quantity for the product %product% is %quantity%.', ['%product%' => $product['name'], '%quantity%' => $product['minimal_quantity']], 'Shop.Notifications.Error')); + } + + $violations = $this->validator->validate($product, new Unrestricted((int) $product['id_shop'])); + if (count($violations) > 0) { + throw new CannotCreateOrderException($this->context->getTranslator()->trans('This product (%product%) is no longer available.', ['%product%' => $product['name']], 'Shop.Notifications.Error')); + } + } + + if (true === $product = $cart->checkQuantities(true)) { + return; + } + + if ($product['active']) { + throw new CannotCreateOrderException(sprintf($this->module->l('You can\'t proceed with your order, the product is not available in this quantity: %s', self::TRANSLATION_SOURCE), $product['name'])); + } + + throw new CannotCreateOrderException($this->context->getTranslator()->trans('This product (%product%) is no longer available.', ['%product%' => $product['name']], 'Shop.Notifications.Error')); + } + + private function validateCartRules(\Cart $cart): void + { + /** @var \CartRule $cartRule */ + foreach ($cart->getCartRules() as ['obj' => $cartRule]) { + if (!$error = $cartRule->checkValidity($this->context, true)) { + continue; + } + + throw new CannotCreateOrderException(sprintf($this->module->l('Voucher %s is no longer available: %s'), $cartRule->code ?: $cartRule->name, $error)); + } + } + + private function checkMinimalPurchaseAmount(\Cart $cart): void + { + if (0. >= $minimalPurchase = $this->getMinimalPurchaseAmount()) { + return; + } + + $productsTotalExcludingTax = $cart->getOrderTotal(false, \Cart::ONLY_PRODUCTS); + if ($minimalPurchase <= $productsTotalExcludingTax) { + return; + } + + throw new CannotCreateOrderException($this->context->getTranslator()->trans('A minimum shopping cart total of %amount% (tax excl.) is required to validate your order. Current cart total is %total% (tax excl.).', ['%amount%' => $this->formatPrice($minimalPurchase), '%total%' => $this->formatPrice($productsTotalExcludingTax)], 'Shop.Theme.Checkout')); + } + + private function checkCartTotal(\Cart $cart, CreateOrderRequest $request): void + { + $details = $request->getOrderDetails(); + + $orderTotal = $cart->getOrderTotal(); + $basketPrice = $details->getBasketPrice()->getGross(); + $epsilon = $details->getCurrency()->getSmallestUnitAmount() / 2.; + + if (abs($orderTotal - $basketPrice) >= $epsilon) { + throw new CannotCreateOrderException($this->module->l('Basket price has changed. Please review your order.', self::TRANSLATION_SOURCE)); + } + } + + private function getMinimalPurchaseAmount(): float + { + $minimalPurchase = (float) \Tools::convertPrice((float) \Configuration::get('PS_PURCHASE_MINIMUM'), $this->context->currency); + + \Hook::exec('overrideMinimalPurchasePrice', [ + 'minimalPurchase' => &$minimalPurchase, + ]); + + return $minimalPurchase; + } + + private function formatPrice(float $price): string + { + if (!is_callable([\Tools::class, 'getContextLocale'])) { + return \Tools::displayPrice($price, $this->context->currency); + } + + return \Tools::getContextLocale($this->context)->formatPrice($price, 'PLN'); + } + + /** + * @param array{delivery: \AddressCore, invoice: \AddressCore} $addresses + */ + private function setUpContext(\Cart $cart, array $addresses, int $shopId): void + { + if ($currencyId = \Currency::getIdByIsoCode('PLN')) { + $cart->id_currency = $currencyId; + } + + $this->context->cart = $cart; + $this->context->shop = new \Shop($shopId); + $this->context->customer = new \Customer($cart->id_customer); + $this->context->cart->setTaxCalculationMethod(); + $this->context->currency = \Currency::getCurrencyInstance($cart->id_currency); + $this->context->language = new \Language($cart->id_lang); + + $this->context->getTranslator()->setLocale($this->context->language->locale); + + \Shop::setContext(\Shop::CONTEXT_SHOP, $shopId); + + $taxAddress = 'id_address_invoice' === \Configuration::get(PrestaShopConfiguration::TAX_ADDRESS_TYPE) + ? $addresses['invoice'] + : $addresses['delivery']; + + $this->context->country = new \Country($taxAddress->id_country, $cart->id_lang); + + if (!$this->context->country->active) { + throw new CannotCreateOrderException(sprintf($this->module->l('Selected country (%s) is not available.', self::TRANSLATION_SOURCE), $this->context->country->name)); + } + } + + private function getCarrierId(int $referenceId, int $shopId): ?int + { + if (0 >= $referenceId) { + return null; + } + + if (false === $carrier = \Carrier::getCarrierByReference($referenceId)) { + return null; + } + + if (!$carrier->active || !$carrier->isAssociatedToShop($shopId)) { + return null; + } + + return (int) $carrier->id; + } + + private function checkPaymentType(PaymentType $paymentType, int $shopId): void + { + $availablePaymentOptions = $this->ordersConfiguration->getAvailablePaymentOptions($shopId); + + if ([] === $availablePaymentOptions) { + return; + } + + if (in_array($paymentType, $availablePaymentOptions, true)) { + return; + } + + throw new CannotCreateOrderException($this->module->l('The selected payment method is not available.', self::TRANSLATION_SOURCE)); + } + + /** + * @param BasketSession $session + */ + private function finalizeSession(BasketSessionInterface $session, CreateOrderRequest $request, int $orderId): void + { + $orderConfirmationUrl = $this->getOrderConfirmationUrl($session->getBasket()->getEntity(), $orderId); + + $session->finalize((string) $orderId, $orderConfirmationUrl, $request); + $this->repository->persist($session); + } + + private function getOrderConfirmationUrl(\Cart $cart, int $orderId): string + { + return \Context::getContext()->link->getPageLink('order-confirmation', null, $cart->id_lang, [ + 'id_cart' => $cart->id, + 'id_module' => $this->module->id, + 'id_order' => $orderId, + 'key' => $cart->secure_key, + ]); + } +} diff --git a/modules/inpostizi/translations/pl.php b/modules/inpostizi/translations/pl.php new file mode 100644 index 00000000..78ac0ef2 --- /dev/null +++ b/modules/inpostizi/translations/pl.php @@ -0,0 +1,285 @@ +inpostizi_be39a2c31fd39417d193787b0745002d'] = 'InPost Pay'; +$_MODULE['<{inpostizi}prestashop>inpostizi_e92d43479cae605e1a18eb091520f704'] = 'Moduł wymaga PHP w wersji 7.1.3 lub późniejszej.'; +$_MODULE['<{inpostizi}prestashop>inpostizi_3f93d44654423ff8a5fcbbfe8d82f57f'] = 'Nie udało się zaktualizować struktury bazy danych.'; +$_MODULE['<{inpostizi}prestashop>inpostizi_3b838c5f28dad4dc154d5d1f0e279453'] = 'Moduł musi być włączony, aby wyświetlić stronę konfiguracji.'; +$_MODULE['<{inpostizi}prestashop>inpostizi_9ed4a1989f9f332b9d6b34eb21fcaa39'] = 'Czyszczenie cache kontenera Symfony mogło zakończyć się niepowodzeniem. Spróbuj wyczyścić cache ręcznie.'; +$_MODULE['<{inpostizi}prestashop>deliverytype_4d5340b646d9723cdb3bdf42182658d0'] = 'Paczkomat'; +$_MODULE['<{inpostizi}prestashop>deliverytype_5055d1a4444c630d6839f48ab48aef91'] = 'Kurier'; +$_MODULE['<{inpostizi}prestashop>widgetcontroller_782990a55975fc9d54d74f3ffb2c7dad'] = 'Koszyk nie istnieje.'; +$_MODULE['<{inpostizi}prestashop>widgetcontroller_69cab7d3b86365a08719b64843b38c79'] = 'Wystąpił problem z komunikacją z aplikacją mobilną. Spróbuj ponownie później.'; +$_MODULE['<{inpostizi}prestashop>widgetcontroller_50349e727b1f9a9a4ef897aad4f1bbf8'] = 'Twoje zapytanie nie mogło zostać przetworzone.'; +$_MODULE['<{inpostizi}prestashop>widgetcontroller_05e070788d5522addd174a9baa153f9f'] = 'Coś poszło nie tak. Spróbuj ponownie później.'; +$_MODULE['<{inpostizi}prestashop>configurationcontroller_254f642527b45bc260048e30704edb39'] = 'Ustawienia'; +$_MODULE['<{inpostizi}prestashop>configurationcontroller_192bdd5b10a4a1f19a7594b94dd209ba'] = 'Zgody'; +$_MODULE['<{inpostizi}prestashop>configurationcontroller_f98053ea5c75db6df542ec23268c5895'] = 'Ustawienia GUI'; +$_MODULE['<{inpostizi}prestashop>configurationcontroller_43f65a2f8564bcc183502e2b35c9a560'] = 'Cena transportu'; +$_MODULE['<{inpostizi}prestashop>configurationcontroller_db5eb84117d06047c97c9a0191b5fffe'] = 'Support'; +$_MODULE['<{inpostizi}prestashop>hotproductcontroller_6f040e0694f901ebf579d7accff11aa4'] = 'Nie udało się pobrać statusu produktów z API.'; +$_MODULE['<{inpostizi}prestashop>hotproductcontroller_2616299606176e7b6e90b07fafe19602'] = 'Promowane produkty'; +$_MODULE['<{inpostizi}prestashop>hotproductcontroller_13b2dba2063a9eae83d069e6db63f7fa'] = 'Tworzysz promowany produkt w kontekście wielu sklepów. Do Basket App zostaną przekazane dane produktu odpowiadające domyślnemu sklepowi.'; +$_MODULE['<{inpostizi}prestashop>hotproductcontroller_ddcdd64da53f2f1f180648bfa8714453'] = 'Promowany produkt został pomyślnie utworzony i będzie oczekiwał na akceptację.'; +$_MODULE['<{inpostizi}prestashop>hotproductcontroller_c91d6c43a3bc4510df29d5cd02d2eb38'] = 'Wybrany produkt/kombinacja jest już zdefiniowany jako promowany produkt.'; +$_MODULE['<{inpostizi}prestashop>hotproductcontroller_be10a9226a67649778aa479b9d901759'] = 'Nowy promowany produkt'; +$_MODULE['<{inpostizi}prestashop>hotproductcontroller_e1414304361fa2116962f49f42b8301b'] = 'Nie odnaleziono promowanego produktu.'; +$_MODULE['<{inpostizi}prestashop>hotproductcontroller_d0341b1d62d7a4e084d204905dc4f835'] = 'Promowany produkt został pomyślnie zaktualizowany i będzie oczekiwał na akceptację.'; +$_MODULE['<{inpostizi}prestashop>hotproductcontroller_33b7fc7a05090db221b1c50c47c54ff9'] = 'Edytuj promowany produkt: %s'; +$_MODULE['<{inpostizi}prestashop>hotproductcontroller_5a87da48e47632ecbb49fa4c5117af9a'] = 'Nieprawidłowy token CSRF.'; +$_MODULE['<{inpostizi}prestashop>hotproductcontroller_4bb742544c04d37b2f14e792d689ca10'] = 'Promowany produkt został pomyślnie usunięty.'; +$_MODULE['<{inpostizi}prestashop>hotproductcontroller_04d2faff74c9b370f3846e4ab850a7b1'] = 'Promowany produkt został pomyślnie zaimportowany.'; +$_MODULE['<{inpostizi}prestashop>hotproductcontroller_e059c5a70a67395dedc8aa7ccff94830'] = 'Produkt został pomyślnie usunięty w API.'; +$_MODULE['<{inpostizi}prestashop>hotproductcontroller_6cc938294d5484ce78d79c77f9f93c05'] = 'Osiągnięto maksymalną ilość promowanych produktów.'; +$_MODULE['<{inpostizi}prestashop>createshipmentlistener_98943daa39fd0f223f8415b1bf0623ad'] = 'Aby informacja o przesyłce została poprawnie przetworzona przez InPost Pay, adres email musi zgadzać się z danymi otrzymanymi z InPost przy tworzeniu zamówienia (%s).'; +$_MODULE['<{inpostizi}prestashop>advancedconfigurationtype_b2270e0389d0e5acd30d865141272052'] = 'Wyłącz debugowanie'; +$_MODULE['<{inpostizi}prestashop>advancedconfigurationtype_7f77e3d7178f0b1979d13c101a456450'] = 'Włącz debugowanie'; +$_MODULE['<{inpostizi}prestashop>apiconfigurationtype_0ba29c6a1afacf586b03a26162c72274'] = 'Środowisko'; +$_MODULE['<{inpostizi}prestashop>apiconfigurationtype_02a5fb45d7cc2eb4a4913f760b397416'] = 'Wybierz środowisko, na którym chcesz pokazać usługę InPost Pay. Pamiętaj, aby przed przejściem na środowisko produkcyjne upewnić się, że usługa w Twoim sklepie działa prawidłowo.'; +$_MODULE['<{inpostizi}prestashop>apiconfigurationtype_6153daeaf2b3f782e9c859a0b884a2dc'] = 'Merchant client ID jest wartością nadawaną przez InPost. W celu otrzymania wartości dla środowiska sandbox należy wysłać zgłoszenie na integracjapay@inpost.pl. Wartość dla środowiska produkcyjnego można uzyskać w panelu merchanta.'; +$_MODULE['<{inpostizi}prestashop>cartruleoptionstype_4b77241222efee4f0d7dc4871124c458'] = 'Podlega Dyrektywie Omnibus'; +$_MODULE['<{inpostizi}prestashop>cartruleoptionstype_93cba07454f06a4a960172bbd6e2a435'] = 'Tak'; +$_MODULE['<{inpostizi}prestashop>cartruleoptionstype_bafd7322c6e97d25b6299b5d6fe8920b'] = 'Nie'; +$_MODULE['<{inpostizi}prestashop>cartruleoptionstype_441ef43627402a1982dfbccef5318ce8'] = 'Strona szczegółów promocji'; +$_MODULE['<{inpostizi}prestashop>cartruleoptionstype_972f16f707d33a5b7ce488eb107ea57a'] = 'Użyj domyślnej'; +$_MODULE['<{inpostizi}prestashop>cartruleoptionstype_54b9438113c3fa139e3064035cc57b53'] = 'Aby dane promocji zostały przekazane do aplikacji mobilnej, opcja \"%s\" musi zostać zaznaczona dla reguły koszyka.'; +$_MODULE['<{inpostizi}prestashop>cartruleoptionstype_06ab0ff70f691d6dd858798aa89c39c4'] = 'Jeżeli ani wartość domyślna, ani konkretna wartość dla danego rabatu nie są ustawione, dane promocji nie zostaną przekazane do aplikacji mobilnej.'; +$_MODULE['<{inpostizi}prestashop>cartruleoptionstype_d19c0b524c35b6e1a074137b029da288'] = 'Możliwość wyboru domyślnej strony znajduje się na stronie konfiguracji modułu.'; +$_MODULE['<{inpostizi}prestashop>cartruleoptionstype_a7f7058d98537328c1dbabdcae4169c9'] = 'Opcje InPost Pay'; +$_MODULE['<{inpostizi}prestashop>clientcredentialstype_76525f0f34b48475e5ca33f71d296f3b'] = 'Client ID'; +$_MODULE['<{inpostizi}prestashop>clientcredentialstype_50afef9e1a24e0f9e78e1d8156ef70de'] = 'Pamiętaj, że client ID w zależności od wybranego środowiska różni się. Aby uzyskać sandboxowe Client ID skontaktuj się z nami przez formularz kontaktowy. Aby uzyskać produkcyjne Client ID zaloguj się do InPost i uzupełnij dane sklepu.'; +$_MODULE['<{inpostizi}prestashop>clientcredentialstype_1b4739e491387ef5d8a546854308e5fe'] = 'Client secret'; +$_MODULE['<{inpostizi}prestashop>clientcredentialstype_6c85cec035e657aa259dffe971845c11'] = 'Pamiętaj, że Client Secret w zależności od wybranego środowiska różni się. Aby uzyskać sandboxowe Client Secret skontaktuj się z nami przez formularz kontaktowy. Aby uzyskać produkcyjne Client Secret zaloguj się do InPost i uzupełnij dane sklepu.'; +$_MODULE['<{inpostizi}prestashop>consentsconfigurationtype_0329ef5c3f8994cecdac9af09fd73ba5'] = 'Dodaj kolejną zgodę'; +$_MODULE['<{inpostizi}prestashop>consentsconfigurationtype_d0fe770b3ac1a21b9beb946e447c5242'] = 'Maksymalna ilość zgód wynosi %d.'; +$_MODULE['<{inpostizi}prestashop>environmentchoicetype_2652eec977dcb2a5aea85f5bec235b05'] = 'Sandbox'; +$_MODULE['<{inpostizi}prestashop>environmentchoicetype_756d97bb256b8580d4d71ee0c547804e'] = 'Produkcja'; +$_MODULE['<{inpostizi}prestashop>environmentchoicetype_19dd6c1cec504ac854645f352eb03209'] = 'UAT'; +$_MODULE['<{inpostizi}prestashop>generalconfigurationtype_8e906720a71264257a3cc58aa9015402'] = 'wszystkim'; +$_MODULE['<{inpostizi}prestashop>generalconfigurationtype_685686674acfe4bdb5fb224164555bd6'] = 'testerom'; +$_MODULE['<{inpostizi}prestashop>generalconfigurationtype_ac6329ea064b9212be7e442cb46bf68f'] = 'Wyświetla widget'; +$_MODULE['<{inpostizi}prestashop>generalconfigurationtype_917c439021cdab2b538264aba987c53f'] = 'Jeśli wybierzesz \"testerom\" widget będzie widoczny tylko dla osób, które mają go zobaczyć. W celu wyświetlenia widgetu w ym trybie w przeglądarce internetowej wpisz adres Twojego sklepu z dopiskiem \'?showIzi=true\'. Przykład: https://mojsklep.pl?showIzi=true'; +$_MODULE['<{inpostizi}prestashop>generalconfigurationtype_4bef0602617f4fe6bd9e178ebe91d5c7'] = 'Hook do wyświetlania widgetu na stronie potwierdzenia zamówienia'; +$_MODULE['<{inpostizi}prestashop>generalconfigurationtype_935b322389e5f6b6419447b0879ca67b'] = 'Jeśli wybierzesz hook \'%s\' musisz go ręcznie zaimplementować w pliku templates/checkout/order-confirmation.tpl \'{hook h=\"%s\" order=$order}\'.'; +$_MODULE['<{inpostizi}prestashop>generalconfigurationtype_a166db0fa22cc43fd81255c6480f9bff'] = 'Hook do wyświetlania widgetu na karcie produktu'; +$_MODULE['<{inpostizi}prestashop>generalconfigurationtype_e14e979d3bf9677c8f2f9d108297b95d'] = 'Możesz wybrać inny hook, jeśli masz problem z wyświetleniem widgetu InPost Pay w domyślnym hooku.'; +$_MODULE['<{inpostizi}prestashop>generalconfigurationtype_dae272c53fe0f60237e1c5059ecdb4b8'] = 'Hook do wyświetlania widgetu w checkoucie'; +$_MODULE['<{inpostizi}prestashop>generalconfigurationtype_07f0748f41ceeea585873d4cd84166e1'] = 'Jeśli wybierzesz hook \'%s\' musisz go ręcznie zaimplementować w pliku szablonu: \'{hook h=\"%s\"}\'.'; +$_MODULE['<{inpostizi}prestashop>generalconfigurationtype_38e0bba4abb1c099b3c3b9dfeed745c2'] = 'Full page cache w użyciu'; +$_MODULE['<{inpostizi}prestashop>generalconfigurationtype_678e433c080caebffb245f4ec097579d'] = 'Użyj tej opcji jeśli używasz modułu full page cache lub innych narzędzi full page cache typu varnish, lightspeed cache.'; +$_MODULE['<{inpostizi}prestashop>generalconfigurationtype_062a2e2d89c1e5a2f9cc6762d32ef806'] = 'Przesyłaj dane analityczne'; +$_MODULE['<{inpostizi}prestashop>generalconfigurationtype_787a92562f9876f7e2fdb44cfe05dccf'] = 'Użyj tej opcji, jeśli chcesz wysyłać dane analityczne do InPost Pay'; +$_MODULE['<{inpostizi}prestashop>generalconfigurationtype_0d5381fd4e980fb92ffc90a505b4e992'] = 'Konfiguracja zdjęć produktów'; +$_MODULE['<{inpostizi}prestashop>generalconfigurationtype_7a440309fc897f07a1d99a07af29d8db'] = 'Maksymalna liczba produktów sugerowanych'; +$_MODULE['<{inpostizi}prestashop>generalconfigurationtype_2d18889b4f14532c8f33c95a7af033c9'] = 'W celu pokazania produktów sugerowanych należy uzupełnić sekcję Produktów Powiązanych w konfiguracji produktu.'; +$_MODULE['<{inpostizi}prestashop>generalconfigurationtype_958f470d0b1c8fb2b9e62b48e8903299'] = 'bez limitu'; +$_MODULE['<{inpostizi}prestashop>generalconfigurationtype_0d6e57c0a330f299f1b38915319dfdc0'] = 'Domyślna strona szczegółów promocji'; +$_MODULE['<{inpostizi}prestashop>generalconfigurationtype_d2cbf536efce450408feea9f62733c1c'] = 'Strona, której adres powinien zostać przekazany, jeżeli konkretna strona nie została wybrana w konfiguracji wyróżnionego rabatu.'; +$_MODULE['<{inpostizi}prestashop>generalconfigurationtype_06ab0ff70f691d6dd858798aa89c39c4'] = 'Jeżeli ani wartość domyślna, ani konkretna wartość dla danego rabatu nie są ustawione, dane promocji nie zostaną przekazane do aplikacji mobilnej.'; +$_MODULE['<{inpostizi}prestashop>guiconfigurationtype_fb681449a1261a91eba2fad584f43331'] = 'Widget będzie wyświetlany na stronie produktu.'; +$_MODULE['<{inpostizi}prestashop>guiconfigurationtype_0d3260fc94336a6987d9680788310b80'] = 'Widget będzie wyświetlany na stronie koszyka poniżej przycisku realizacji zamówienia.'; +$_MODULE['<{inpostizi}prestashop>guiconfigurationtype_89e511ba0171925a36570b7f342d38a2'] = 'Widget będzie wyświetlany na stronie logowania, pod formularzem logowania.'; +$_MODULE['<{inpostizi}prestashop>guiconfigurationtype_24facc1d99d4510804eb2474c30b5bd6'] = 'Widget będzie wyświetlany na stronie rejestracji, nad formularzem rejestracji.'; +$_MODULE['<{inpostizi}prestashop>guiconfigurationtype_9d7278cdb71e13934fa097ee3d470e26'] = 'Widget będzie wyświetlany na stronie realizacji zamówienia powyżej podsumowania zamówienia.'; +$_MODULE['<{inpostizi}prestashop>guiconfigurationtype_47b43d98eac64ba3744f6a317beb332d'] = 'Widget będzie wyświetlany w podglądzie koszyka. Wymaga użycia niestandardowego hooku - należy zaimplementować w szablonie \"{hook h=\'displayIziCartPreviewButton\'}\".'; +$_MODULE['<{inpostizi}prestashop>guiconfigurationtype_bb54cce5a0fb4e80d1b2dce5a28d6efb'] = 'Strona koszyka'; +$_MODULE['<{inpostizi}prestashop>guiconfigurationtype_e6f28601734051de0cef66878bac1c0e'] = 'Strona produktu'; +$_MODULE['<{inpostizi}prestashop>guiconfigurationtype_024588fa3c938f335a36fec9f17624f4'] = 'Strona logowania'; +$_MODULE['<{inpostizi}prestashop>guiconfigurationtype_6b0a73cbb6db5290d1efe67976ef14c0'] = 'Strona rejestracji'; +$_MODULE['<{inpostizi}prestashop>guiconfigurationtype_30ae44c4afa98b529400daa23dfc0202'] = 'Strona zamówienia'; +$_MODULE['<{inpostizi}prestashop>guiconfigurationtype_3a76dfe4d90c1f5622a12f85901fba21'] = 'Podgląd koszyka'; +$_MODULE['<{inpostizi}prestashop>objectmodelautocompletetype_b402654b91b7b5a76b41566d150ae823'] = 'Ładowanie kolejnych wyników...'; +$_MODULE['<{inpostizi}prestashop>objectmodelautocompletetype_e576c23d915755d83e2d1f47bd9f6c22'] = 'Brak wyników'; +$_MODULE['<{inpostizi}prestashop>objectmodelautocompletetype_2397bd5fd0c284abbd5d2ca6aba59d78'] = 'Brak większej ilości wyników'; +$_MODULE['<{inpostizi}prestashop>ordersconfigurationtype_fd282be0a539046c5c22736ef41f4350'] = 'Status zamówienia utworzonego przez InPost Pay'; +$_MODULE['<{inpostizi}prestashop>ordersconfigurationtype_32e0295e9c28454d52b4f4f3643b46e7'] = 'Status zamówienia utworzonego przez InPost Pay (płatność za pobraniem)'; +$_MODULE['<{inpostizi}prestashop>ordersconfigurationtype_05a5fcc9d88a553b80b2633aa4556d58'] = 'Status dla zamówienia opłaconego przez InPost Pay'; +$_MODULE['<{inpostizi}prestashop>ordersconfigurationtype_33af8066d3c83110d4bd897f687cedd2'] = 'Statusy zamówień'; +$_MODULE['<{inpostizi}prestashop>ordersconfigurationtype_8cd8b0f7c9d02973566d70ae96bcf976'] = 'Włącz wszystkie dostępne metody płatności'; +$_MODULE['<{inpostizi}prestashop>ordersconfigurationtype_50b5c5b98ff1773e551421f082fed261'] = 'Metody płatności zostały określone na umowie o obsługę bramki płatniczej.'; +$_MODULE['<{inpostizi}prestashop>ordersconfigurationtype_e1bbdbf5c497c484c061c4c2a0359e58'] = 'Płatność przy odbiorze będzie dostępna tylko wtedy, kiedy posiadasz podpisaną umowę z InPost na świadczenie tej usługi w Twoim sklepie.'; +$_MODULE['<{inpostizi}prestashop>ordersconfigurationtype_e8051555e4af6e673fbe93a79a86671f'] = 'Włączone metody płatności'; +$_MODULE['<{inpostizi}prestashop>ordersconfigurationtype_e9adbbd1cd7eff7e1a588c31f79521d5'] = 'POS ID'; +$_MODULE['<{inpostizi}prestashop>ordersconfigurationtype_d3188981fad68a05eec51ac57e69feca'] = 'W przypadku środowiska sandbox skontaktuj się z InPost. W przypadku środowiska produkcyjnego - zaloguj się do InPost i pobierz POS ID.'; +$_MODULE['<{inpostizi}prestashop>ordersconfigurationtype_f13673c7e261fd8d00915c9cb768ad95'] = 'Komentarz zamówienia'; +$_MODULE['<{inpostizi}prestashop>productconfigurationtype_dc69b3498766f84bb8aa3316146bdd53'] = 'Typ zdjęcia na liście produktów'; +$_MODULE['<{inpostizi}prestashop>productconfigurationtype_47b35415c84af2e3bb76cacfa921a23a'] = 'Ten format zdjęcia będzie wyświetlany na liście produktów w aplikacji.'; +$_MODULE['<{inpostizi}prestashop>productconfigurationtype_fe6d529783e5c4708e3ee5c627af8c25'] = 'Typ zdjęcia w szczegółach produktu'; +$_MODULE['<{inpostizi}prestashop>productconfigurationtype_8a285266cbcb8bfa7bcb6e98b0603ece'] = 'Ten format zdjęcia będzie wyświetlany na szczegółach poszczególnego produktu w aplikacji.'; +$_MODULE['<{inpostizi}prestashop>productconfigurationtype_46fb7288f3d200a582083cacc0718c8c'] = 'Type zdjecia duży'; +$_MODULE['<{inpostizi}prestashop>productconfigurationtype_abc274de1ccce773f2a8d7f8844d48b9'] = 'Ten typ zdjęcia będzie wyświetlany na powiększone zdjęcie produktu w aplikacji.'; +$_MODULE['<{inpostizi}prestashop>shippingconfigurationtype_5055d1a4444c630d6839f48ab48aef91'] = 'Kurier'; +$_MODULE['<{inpostizi}prestashop>shippingconfigurationtype_39b6fda48dbec22f92c82517456047c2'] = 'Paczkomat'; +$_MODULE['<{inpostizi}prestashop>consentlinktype_29ee5d1ebcc033234938a5234f1f2075'] = 'Identyfikator'; +$_MODULE['<{inpostizi}prestashop>consentlinktype_15397633d9996be70ea072e86b0a29d1'] = 'Unikalny identyfikator linku. W opisie zgody fragmenty powstałe przez dodanie do ID przedrostka \"#\" zostaną zastąpione odpowiadającymi linkami.'; +$_MODULE['<{inpostizi}prestashop>consentlinktype_63a11faa3a692d4e00fa8e03bbe8a0d6'] = 'Tekst linku'; +$_MODULE['<{inpostizi}prestashop>consentlinktype_2db61728ba1f53c41ea03ead40643667'] = 'W przypadku, gdy puste, w aplikacji zostanie wyświetlony tekst \"link\".'; +$_MODULE['<{inpostizi}prestashop>consentrequirementchoicetype_ebb061953c0454b2c8ee7b0ac615ebcd'] = 'Opcjonalny'; +$_MODULE['<{inpostizi}prestashop>consentrequirementchoicetype_699af6edd09943554fa87ce0488e68a0'] = 'Zawsze wymagany'; +$_MODULE['<{inpostizi}prestashop>consentrequirementchoicetype_f080d3c0cdd358db818ef31f8eec2221'] = 'Wymagany raz'; +$_MODULE['<{inpostizi}prestashop>consenttype_aa7249797c954c5b027c7978037e0297'] = 'Dodaj kolejny link'; +$_MODULE['<{inpostizi}prestashop>consenttype_1b5f9c459802455a52d8d6b17c85a85e'] = 'Opis wyświetlany w aplikacji'; +$_MODULE['<{inpostizi}prestashop>consenttype_c971d24687581caa9dd0d976ff822e28'] = 'Dodaj opis, który ma być wyświetlany przy zgodzie w aplikacji InPost mobile.'; +$_MODULE['<{inpostizi}prestashop>consenttype_43504b2f9834bf1dafdc2d192aec1265'] = 'W przypadku pustej wartości w języku innym niż domyślny dla sklepu, użyta zostanie wartość w języku domyślnym.'; +$_MODULE['<{inpostizi}prestashop>consenttype_38961305dcd1f3ee7eda56a706772341'] = 'Aby sterować pozycją poszczególnych linków, użyj identyfikatorów linków poprzedzonych przedrostkiem \"#\".'; +$_MODULE['<{inpostizi}prestashop>consenttype_48bcdc549fe43533852290c14e2ad896'] = 'Czy wymagane'; +$_MODULE['<{inpostizi}prestashop>consenttype_aaa742bec1ae41dd20f5b6ceb1067644'] = 'Określ czy zgoda jest wymagana czy opcjonalna.'; +$_MODULE['<{inpostizi}prestashop>consenttype_bd908db5ccb07777ced8023dffc802f4'] = 'Linki'; +$_MODULE['<{inpostizi}prestashop>consenttype_578f2be92e5c72e5eee6db5a021923cb'] = 'Adres zgody'; +$_MODULE['<{inpostizi}prestashop>consenttype_3ac31477514fd44550783de974bd074c'] = 'Określa stronę, na którą zostanie przekierowany Twój klient w celu, który kliknie na daną zgodę w aplikacji InPost mobile.'; +$_MODULE['<{inpostizi}prestashop>messageoptionstype_510e3417f3b9f45ab725ed761aba7cdd'] = 'Doklej dodatkową zawartość do komentarza klienta w przypadku dostawy do Paczkomatu'; +$_MODULE['<{inpostizi}prestashop>messageoptionstype_c59e73ddbd9accb6cceb84986bfb9633'] = 'Format wiadomości'; +$_MODULE['<{inpostizi}prestashop>messageoptionstype_5a584bae80c34e6ee9b177aa167eb865'] = 'Dostępne parametry'; +$_MODULE['<{inpostizi}prestashop>messageoptionstype_feba3b9ea8ec801f50840a9b309c7d03'] = 'Wartość wyrażeń zawartych w podwójnych nawiasach klamrowych jest obliczana (np. `{{ is_pww ? \"tak\" : \"nie\" }}` wypisze \"tak\" jeżeli klient wybrał usługę Paczka w Weekend lub \"nie\" w przeciwnym wypadku).'; +$_MODULE['<{inpostizi}prestashop>messageoptionstype_144e4eb4e0963dde914bedb08eebf335'] = 'Bardziej szczegółowe informacje można znaleźć w instrukcji modułu.'; +$_MODULE['<{inpostizi}prestashop>combinationbyattributeschoicetype_52c6dba9feba73a163b3d71faf11322b'] = 'Kombinacja produktu z wybranymi atrybutami nie istnieje.'; +$_MODULE['<{inpostizi}prestashop>productrestrictionstype_94e0f2332e0b60a918077db74b15cd80'] = 'Nie zezwalaj na zamówienia'; +$_MODULE['<{inpostizi}prestashop>productrestrictionstype_5f0f1ca46799330f5e8c4285f5f17676'] = 'Jeżeli włączone, złożenie zamówienia przez aplikację mobilną nie będzie możliwe w przypadku, gdy koszyk zawiera produkt spełniający którykolwiek z poniższych warunków.'; +$_MODULE['<{inpostizi}prestashop>productrestrictionstype_3d19aea4e2d382e85165bbf71cef1e05'] = 'Typ produktu'; +$_MODULE['<{inpostizi}prestashop>productrestrictionstype_713432cf48c93262b4771c212040118d'] = 'Widget nie zostanie wyświetlony jeżeli kombinacja produktu ma atrybuty pochodzące z wybranych grup.'; +$_MODULE['<{inpostizi}prestashop>productrestrictionstype_ec64be38e5581c107b00cbffbada2973'] = 'Widget nie zostanie wyświetlony jeżeli produkt ma jakąkolwiek z wybranych cech.'; +$_MODULE['<{inpostizi}prestashop>carriermappingstype_cbbd3439308c58e0773f34e9f9954a9b'] = 'Mapowanie przewoźnika'; +$_MODULE['<{inpostizi}prestashop>serviceoptionstype_a25eac9fbba5c5aace5aa74ac02fabae'] = 'Dodatkowy koszt'; +$_MODULE['<{inpostizi}prestashop>serviceoptionstype_4927147ff7bc490186490bda85714ca4'] = 'Wartość netto kwoty do doliczenia do ceny przewoźnika w przypadku, gdy usługa zostanie wybrana.'; +$_MODULE['<{inpostizi}prestashop>serviceoptionstype_d488a56a2004d96fe1b69419ef7e2893'] = 'Koszt nie zostanie doliczony w przypadku, gdy opcja \"%s\" nie jest włączona dla przewoźnika.'; +$_MODULE['<{inpostizi}prestashop>timeofweekrangetype_1432596d084873bee8df589b0b01448c'] = 'Dostępna od'; +$_MODULE['<{inpostizi}prestashop>timeofweekrangetype_62628cfd1cdd77d02c32812f048bada3'] = 'włącznie'; +$_MODULE['<{inpostizi}prestashop>timeofweekrangetype_0a0b945c925ac8eb94a4ad328fc54840'] = 'Dostępna do'; +$_MODULE['<{inpostizi}prestashop>timeofweekrangetype_a4293995cfbfa9ce60ce71ade2ff75f7'] = 'wyłącznie'; +$_MODULE['<{inpostizi}prestashop>weekdaychoicetype_6f8522e0610541f1ef215a22ffa66ff6'] = 'Poniedziałek'; +$_MODULE['<{inpostizi}prestashop>weekdaychoicetype_5792315f09a5d54fb7e3d066672b507f'] = 'Wtorek'; +$_MODULE['<{inpostizi}prestashop>weekdaychoicetype_796c163589f295373e171842f37265d5'] = 'Środa'; +$_MODULE['<{inpostizi}prestashop>weekdaychoicetype_78ae6f0cd191d25147e252dc54768238'] = 'Czwartek'; +$_MODULE['<{inpostizi}prestashop>weekdaychoicetype_c33b138a163847cdb6caeeb7c9a126b4'] = 'Piątek'; +$_MODULE['<{inpostizi}prestashop>weekdaychoicetype_8b7051187b9191cdcdae6ed5a10e5adc'] = 'Sobota'; +$_MODULE['<{inpostizi}prestashop>weekdaychoicetype_9d1a0949c39e66a0cd65240bc0ac9177'] = 'Niedziela'; +$_MODULE['<{inpostizi}prestashop>htmlstylestype_9d768809cb57b85ec5e4e4693839692e'] = 'Margines górny'; +$_MODULE['<{inpostizi}prestashop>htmlstylestype_af1c33bd64cf61f9245b75131e2e06f1'] = 'Margines lewy'; +$_MODULE['<{inpostizi}prestashop>htmlstylestype_08d4382960e54a51b0225995753fb95c'] = 'Margines prawy'; +$_MODULE['<{inpostizi}prestashop>htmlstylestype_c9254e027bb4bc070f417493b43f9dc1'] = 'Margines dolny'; +$_MODULE['<{inpostizi}prestashop>htmlstylestype_945d5e233cf7d6240f6b783b36a374ff'] = 'Do lewej'; +$_MODULE['<{inpostizi}prestashop>htmlstylestype_4f1f6016fc9f3f2353c0cc7c67b292bd'] = 'Wyśrodkowany'; +$_MODULE['<{inpostizi}prestashop>htmlstylestype_92b09c7c48c520c3c55e497875da437c'] = 'Do prawej'; +$_MODULE['<{inpostizi}prestashop>productpagedisplayconfigurationtype_c890c8109a222c4a83edcf88b641d8e8'] = 'Ograniczenia produktów'; +$_MODULE['<{inpostizi}prestashop>productpagedisplayconfigurationtype_a7f58278351a242f444b55362f65cb6c'] = 'Widget nie zostanie wyświetlony na stronach produktów spełniających którykolwiek z poniższych warunków.'; +$_MODULE['<{inpostizi}prestashop>widgetconfigurationtype_a9ded1e5ce5d75814730bb4caaf49419'] = 'Tło'; +$_MODULE['<{inpostizi}prestashop>widgetconfigurationtype_c6252ce8f855c90f0e5379b07da7b7c9'] = 'Określa czy widget znajduje się na jasnym czy ciemnym tle w Twoim sklepie. Ustawienie ma wpływ na kolor czcionki, zadbaj o to, aby była widoczna.'; +$_MODULE['<{inpostizi}prestashop>widgetconfigurationtype_9914a0ce04a7b7b6a8e39bec55064b82'] = 'Jasne'; +$_MODULE['<{inpostizi}prestashop>widgetconfigurationtype_a18366b217ebf811ad1886e4f4f865b2'] = 'Ciemne'; +$_MODULE['<{inpostizi}prestashop>widgetconfigurationtype_492f18b60811bf85ce118c0c6a1a5c4a'] = 'Wariant'; +$_MODULE['<{inpostizi}prestashop>widgetconfigurationtype_bd89a3ed71a0de371f29ffc02807e2e9'] = 'Widget dostępny jest w 2 wariantach kolorystycznych. Wybierz ten, bardziej odpowiadający kolorystyce Twojego sklepu.'; +$_MODULE['<{inpostizi}prestashop>widgetconfigurationtype_d3058eb8a80e6516dd3ab5eaaebc318a'] = 'Styl ramki'; +$_MODULE['<{inpostizi}prestashop>widgetconfigurationtype_6f6cb72d544962fa333e2e34ce64f719'] = 'Rozmiar'; +$_MODULE['<{inpostizi}prestashop>widgetconfigurationtype_477964da3b7a710156efc82fc4bc72e2'] = 'Maksymalna szerokość'; +$_MODULE['<{inpostizi}prestashop>widgetconfigurationtype_a63706eecfa33f67b315f978392ad7ae'] = 'Wyrównanie'; +$_MODULE['<{inpostizi}prestashop>widgetconfigurationtype_59948a1a9743ccef9725e835fc5d8464'] = 'Określa orientację widżetu w dostępnej dla niego przestrzeni. Jeśli szablon przydziela wąską przestrzeń dla widżetu, ustawienie to nie wpłynie na jego wygląd.'; +$_MODULE['<{inpostizi}prestashop>widgetdisplayconfigurationtype_86754577897acfb25deb69039d49d9a7'] = 'Wyświetlany'; +$_MODULE['<{inpostizi}prestashop>widgetdisplayconfigurationtype_27bc9eb9abc57172f8620ed18e376ea8'] = 'Aby zwiększyć konwersje, zalecamy wyświetlać widget InPost Pay na stronie koszyka jak i karcie produktu'; +$_MODULE['<{inpostizi}prestashop>widgetframestylechoicetype_bef2481eda40bd121482f1814d72314f'] = 'Zaokrąglone'; +$_MODULE['<{inpostizi}prestashop>widgetframestylechoicetype_b7f41fc1412ad2ee75e9b2635d3b9d5c'] = 'Okrągłe'; +$_MODULE['<{inpostizi}prestashop>widgetframestylechoicetype_7545c5d3ad246a683a197a2903a4d5e6'] = 'Kwadratowe'; +$_MODULE['<{inpostizi}prestashop>widgetvariantchoicetype_51e6cd92b6c45f9affdc158ecca2b8b8'] = 'Żółty'; +$_MODULE['<{inpostizi}prestashop>widgetvariantchoicetype_e90dfb84e30edf611e326eeb04d680de'] = 'Czarny'; +$_MODULE['<{inpostizi}prestashop>cachestatuschecker_86b62594677b99f4b3de1790dc6e3b37'] = 'Cache kontenera Symfony jest przestarzały.'; +$_MODULE['<{inpostizi}prestashop>configurationstatuschecker_341b3a9691b47feef924ec0e1db4856c'] = 'Konfiguracja nie jest kompletna - zweryfikuj i prześlij formularz w zakładce \"ustawienia\".'; +$_MODULE['<{inpostizi}prestashop>configurationstatuschecker_d64fff9bf53342093407f32d61b278b1'] = 'Dane dostępowe do API nie zostały skonfigurowane.'; +$_MODULE['<{inpostizi}prestashop>configurationstatuschecker_0befa43bc12a10af2a8897889570b8ee'] = 'Problem z dostępem do API: %s'; +$_MODULE['<{inpostizi}prestashop>configurationstatuschecker_a7030a339317fbdb2af045820a465f79'] = 'Merchant client ID nie zostało skonfigurowane. Widget InPost Pay nie będzie wyświetlany.'; +$_MODULE['<{inpostizi}prestashop>deliveryoptionsstatuschecker_3194a4d8bc60ba3e66ddd94f10f32889'] = 'Nie jest dostępna żadna opcja dostawy.'; +$_MODULE['<{inpostizi}prestashop>actionfrontcontrollersetmedia_05e070788d5522addd174a9baa153f9f'] = 'Coś poszło nie tak. Spróbuj ponownie później.'; +$_MODULE['<{inpostizi}prestashop>actiongetpaymentoptions_a9e819aadeb05ca1df49418d8ef8faf2'] = 'Płać z InPost Pay'; +$_MODULE['<{inpostizi}prestashop>hotproductvalidator_c0674df384ee0721d5bf7a6152a1608f'] = 'Produkt lub kombinacja nie istnieje.'; +$_MODULE['<{inpostizi}prestashop>hotproductvalidator_3419fcc80e54c5ce62164714961f5d15'] = 'Produkt nie jest aktywny lub nie jest dostępny na sprzedaż.'; +$_MODULE['<{inpostizi}prestashop>hotproductvalidator_3825f0da01ae0a7c0a1ca0d1eb4e14dc'] = 'Kod EAN jest wymagany.'; +$_MODULE['<{inpostizi}prestashop>createhotproducttype_072bf7386ad6d32a8213bf1299f799fc'] = 'Szukaj produktu po nazwie lub kodzie...'; +$_MODULE['<{inpostizi}prestashop>updatehotproducttype_1432596d084873bee8df589b0b01448c'] = 'Dostępny od'; +$_MODULE['<{inpostizi}prestashop>updatehotproducttype_0a0b945c925ac8eb94a4ad328fc54840'] = 'Dostępny do'; +$_MODULE['<{inpostizi}prestashop>productsquantityeventhandler_6db1f42edb02275ddc7077ae731fa407'] = 'Tego produktu nie ma w Twoim koszyku.'; +$_MODULE['<{inpostizi}prestashop>productsquantityeventhandler_e241dce2751f423f20aa8a1dabda0ead'] = 'Nie można zaktualizować ilości produktu.'; +$_MODULE['<{inpostizi}prestashop>productsquantityeventhandler_b9980de9208115d9102e3ed27df896ec'] = 'Może usunąć produkt z koszyka.'; +$_MODULE['<{inpostizi}prestashop>promocodeseventhandler_e97e27bc6f85e0dc4079ce3aa1920c36'] = 'Kod rabatowy został aktywowany.'; +$_MODULE['<{inpostizi}prestashop>promocodeseventhandler_fa71ed2acd5e96f8be1fa8609fb4bbbf'] = 'Nie można dodać kuponu do koszyka.'; +$_MODULE['<{inpostizi}prestashop>parametersextractor_df605260012435cbbab42f0e5c64fcbd'] = 'kod użytej metody płatności'; +$_MODULE['<{inpostizi}prestashop>parametersextractor_c470ae3599816022017be07ddb38a878'] = 'numer Paczkomatu'; +$_MODULE['<{inpostizi}prestashop>parametersextractor_aa38035ab7c137762ca1191f50548cac'] = 'kody wybranych usług dodatkowych'; +$_MODULE['<{inpostizi}prestashop>parametersextractor_7b8cd96c630904984468818de476a6d1'] = 'jeżeli została wybrana Paczka w Weekend'; +$_MODULE['<{inpostizi}prestashop>parametersextractor_390053862349a908e1fb52ac06f22645'] = 'jeżeli została wybrana płatność za pobraniem'; +$_MODULE['<{inpostizi}prestashop>producttype_fafdcf7e6355f6539aeabcea6f3460f4'] = 'Standardowe produkty'; +$_MODULE['<{inpostizi}prestashop>producttype_101ca77dec0df7d2f721467aef2a03d8'] = 'Produkty z kombinacjami'; +$_MODULE['<{inpostizi}prestashop>producttype_ba08c35a9d8f80fd99614f57e7f4c03c'] = 'Dostosowywalne produkty'; +$_MODULE['<{inpostizi}prestashop>producttype_c6ca748854a782c9150f972d4e4c9074'] = 'Zestawy produktów'; +$_MODULE['<{inpostizi}prestashop>producttype_53d0ca486dc7ee72cac2c538297ad2a3'] = 'Produkty wirtualne'; +$_MODULE['<{inpostizi}prestashop>paymenttypetranslator_e7f9e382dc50889098cbe56f2554c77b'] = 'Karta kredytowa'; +$_MODULE['<{inpostizi}prestashop>paymenttypetranslator_5c56966215f9c0850e2caeadc446dda8'] = 'Zapamiętana karta kredytowa'; +$_MODULE['<{inpostizi}prestashop>paymenttypetranslator_a69a8e250d7465b3357efed7e4251deb'] = 'Google Pay'; +$_MODULE['<{inpostizi}prestashop>paymenttypetranslator_697abb9a95cfe2c76cc3b71312ea176a'] = 'Apple Pay'; +$_MODULE['<{inpostizi}prestashop>paymenttypetranslator_91d1fc4990d4fff923bde3c6c70b7e16'] = 'Kod BLIK'; +$_MODULE['<{inpostizi}prestashop>paymenttypetranslator_805e2c8aa66f9379bf7b68f7355a02c3'] = 'Zapamiętane konto BLIK'; +$_MODULE['<{inpostizi}prestashop>paymenttypetranslator_52a0d2a64ff310050981ded93e43169a'] = 'Pay by Link'; +$_MODULE['<{inpostizi}prestashop>paymenttypetranslator_bf12eb9565712f0d19eebc65ee067ffb'] = 'Limit zakupowy'; +$_MODULE['<{inpostizi}prestashop>paymenttypetranslator_db687137cec26f4c0a93e2d8f520e43e'] = 'Płatność odroczona'; +$_MODULE['<{inpostizi}prestashop>paymenttypetranslator_469ded86e4fce0e962b4d91cc7d426ce'] = 'Płatność przy odbiorze'; +$_MODULE['<{inpostizi}prestashop>servicenametranslator_469ded86e4fce0e962b4d91cc7d426ce'] = 'Płatność za pobraniem'; +$_MODULE['<{inpostizi}prestashop>servicenametranslator_7e0df2de796409e39b5ee6060c457be7'] = 'Paczka w Weekend'; +$_MODULE['<{inpostizi}prestashop>inpostapicredentialsvalidator_e221b0fd3e879543e6fd834e514505f7'] = 'Nieprawidłowe dane klienta.'; +$_MODULE['<{inpostizi}prestashop>inpostapicredentialsvalidator_235c2a9074a916290e7079c69bf197b5'] = 'Nie można połączyć się z serwerem autoryzacji.'; +$_MODULE['<{inpostizi}prestashop>inpostapicredentialsvalidator_b57a7d1ed0c1f93b304b8b8e7fc41898'] = 'Nie można zweryfikować danych klienta.'; +$_MODULE['<{inpostizi}prestashop>inpostapicredentialsvalidator_ca6b295039b53a17b2175aded3b6e0d2'] = 'Przyznany token dostępu nie posiada wszystkich wymaganych uprawnień. W celu rozwiązania tego problemu, skontaktuj się z pomocą techniczną.'; +$_MODULE['<{inpostizi}prestashop>processablemessageformatvalidator_845c2f5006926ea73bf2a45bfbb2fd65'] = 'Nieporawny format wiadomości. %s'; +$_MODULE['<{inpostizi}prestashop>descriptionusesidplaceholdersvalidator_b9852caf28662a7f1cbd9e886be563ea'] = 'Niewykorzystane ID: %s.'; +$_MODULE['<{inpostizi}prestashop>descriptionusesidplaceholdersvalidator_e8dbbd9330744ebbaa10984344507bd1'] = 'Powielone ID: %s.'; +$_MODULE['<{inpostizi}prestashop>uniqueidentifiersvalidator_67cec2d8f419fa9592403c6314275492'] = 'Identyfikatory powinny być unikalne.'; +$_MODULE['<{inpostizi}prestashop>size_4c7c5ab57475a86d54c99e99e8cbb5e7'] = 'Bardzo mały'; +$_MODULE['<{inpostizi}prestashop>size_2660064e68655415da2628c2ae2f7592'] = 'Mały'; +$_MODULE['<{inpostizi}prestashop>size_87f8a6ab85c9ced3702b4ea641ad4bb5'] = 'Średni'; +$_MODULE['<{inpostizi}prestashop>size_3a69b34ce86dacb205936a8094f6c743'] = 'Duży'; +$_MODULE['<{inpostizi}prestashop>size_e6d401dcbb3d4b9f64c12fb1ecf266a6'] = 'Bardzo duży'; +$_MODULE['<{inpostizi}prestashop>create_be503ee264d6b76ab103af34b1a92c58'] = 'Dla tego koszyka zostało już utworzone zamówienie.'; +$_MODULE['<{inpostizi}prestashop>create_d1363ee7cb061a5b383fd7a829dd9e5b'] = 'Wybrana opcja dostawy nie jest dostępna.'; +$_MODULE['<{inpostizi}prestashop>create_381065e4a69c2def0b193f3276663936'] = 'Wybrany kraj (%s) nie jest dostępny.'; +$_MODULE['<{inpostizi}prestashop>create_adfdd411bc1770971d1d4d05458e8701'] = 'Adres dostawy nie jest poprawny: %s'; +$_MODULE['<{inpostizi}prestashop>create_5da7b3dc1a91973a4cf0f9fd3201ce0a'] = 'Adres rozliczeniowy nie jest poprawny: %s'; +$_MODULE['<{inpostizi}prestashop>create_a7bf47f259b0e675d565b57d5bead1b4'] = 'Dane klienta nie są poprawne: %s'; +$_MODULE['<{inpostizi}prestashop>create_f609bb09ff74c0ba85a8ad6491985fec'] = 'Uwagi do zamówienia nie są poprawne.'; +$_MODULE['<{inpostizi}prestashop>create_aa5c35a3fda1aae28e338de76d2849f4'] = 'Nie możesz złożyć zamówienia, produkt nie jest dostępny w tej ilości: %s'; +$_MODULE['<{inpostizi}prestashop>create_cda059f021a57b6dbc4304efa95b40ab'] = 'Kupon rabatowy %s nie jest już dostępny: %s'; +$_MODULE['<{inpostizi}prestashop>create_6e0a1f3186d8fc17ec4cfb51ce31153b'] = 'Cena koszyka uległa zmianie. Zweryfikuj swoje zamówienie.'; +$_MODULE['<{inpostizi}prestashop>create_17ffb24862c7db18f577ee8328688a9d'] = 'Wybrana metoda płatności nie jest dostępna.'; +$_MODULE['<{inpostizi}prestashop>admin_template_translations_afc2a4a7af3445e015c1aeb370e9b6b7'] = 'Wybierz język do tłumaczenia '; +$_MODULE['<{inpostizi}prestashop>admin_template_translations_dd1ba1872df91985ed1ca4cde2dfe669'] = 'Aktualności'; +$_MODULE['<{inpostizi}prestashop>admin_template_translations_c9083ac6d8937ca4bdb37f3bfcd7f7c5'] = 'Link do modułu'; +$_MODULE['<{inpostizi}prestashop>admin_template_translations_4797e332b1a0c3def5c2e7b63a279b0f'] = 'Kontakt i wsparcie'; +$_MODULE['<{inpostizi}prestashop>admin_template_translations_4a64728c3fc91ae5ef42a86526a5d3ba'] = 'Pomoc techniczna - skorzystaj z formularza kontaktowego '; +$_MODULE['<{inpostizi}prestashop>admin_template_translations_4e74d5318241a6e29dc0482dd98471fb'] = 'Skontaktuj się z przedstawicielem handlowym '; +$_MODULE['<{inpostizi}prestashop>admin_template_translations_1fe917b01f9a3f87fa2d7d3b7643fac1'] = 'FAQ'; +$_MODULE['<{inpostizi}prestashop>admin_template_translations_83b9cd6e2835cc74d06c8cb7a037b8cd'] = 'Status wtyczki'; +$_MODULE['<{inpostizi}prestashop>admin_template_translations_c6219a54f70f66674046336175bb9491'] = 'Pobierz dane wtyczki i log'; +$_MODULE['<{inpostizi}prestashop>admin_template_translations_e9be76a44ba1c2521b80bc4a126e017a'] = 'Przydatne linki'; +$_MODULE['<{inpostizi}prestashop>admin_template_translations_87f4ea988928e7dd998d582fd221678f'] = 'Przewodnik Merchanta - jak poprawnie wyświetlić InPost Pay w Twoim sklepie internetowym'; +$_MODULE['<{inpostizi}prestashop>admin_template_translations_541127e34a74cf1eb876350223c574a7'] = 'Instrukcja konfiguracji wtyczki InPost Pay'; +$_MODULE['<{inpostizi}prestashop>admin_template_translations_3747ade9aba396fea40b3f9e0bdc6989'] = 'Instrukcja obsługi zwrotów'; +$_MODULE['<{inpostizi}prestashop>admin_template_translations_b886efdff22fe2cb1c6bd9af9e3757aa'] = 'Panel merchanta - podgląd transakcji i obsługa zwrotów'; +$_MODULE['<{inpostizi}prestashop>admin_template_translations_9c15669ba55d94ca08b6c4cfaab10a13'] = 'Podgląd przycisku'; +$_MODULE['<{inpostizi}prestashop>admin_template_translations_86ba1a30ad954063b3832e6df9056093'] = 'Aby podgląd został wyświetlony, poprawne merchant client ID musi zostać uzupełnione w zakładce \"Konfiguracja\".'; +$_MODULE['<{inpostizi}prestashop>admin_template_translations_df4069cdb9787753503ca5dae66919c0'] = 'Pamiętaj by kliknąć przycisk \'Zapisz\' gdy już skonfigurujesz wygląd przycisku'; +$_MODULE['<{inpostizi}prestashop>admin_template_translations_0b1d93d523e523fdcf4d395a2a5645d6'] = 'Konfiguracja wtyczki InPost Pay'; +$_MODULE['<{inpostizi}prestashop>admin_template_translations_65d508ca1dd3c13922c433eb6e26fd2f'] = 'Zadbaj o poprawną ekspozycję usługi InPost Pay, dzięki czemu:'; +$_MODULE['<{inpostizi}prestashop>admin_template_translations_4cdb65c6277c5987c5a8b440224865ef'] = 'Kupujący szybko rozpoznają, że dokonując zakupu w Twoim sklepie mogą skorzystać z usługi szybkiego i bezpiecznego zakupu oraz dostawy przez firmę, której usługi znają i do której mają zaufanie, co bezpośrednio przyczynia się do decyzji zakupowych Klientów.'; +$_MODULE['<{inpostizi}prestashop>admin_template_translations_3194d0bc6d7b9e032ffa2d46ab7bf741'] = 'Przy prawidłowym umiejscowieniu widgetu pomagasz swoim Klientom zauważyć usługę InPost Pay, dzięki której będą mogli sfinalizować zakupy na Twojej stronie bez konieczności podawania swoich danych, co jest szczególnie istotne w przypadku Klientów, którzy preferują anonimowe zakupy bez logowania.'; +$_MODULE['<{inpostizi}prestashop>admin_template_translations_708b8fd7e8a0fd84d8ee5550b9543e10'] = 'Zapoznaj się z dedykowanym Przewodnikiem Merchanta, dotyczącym poprawnej implementacji usługi InPost Pay. Gromadzi on w jednym miejscu informacje w zakresie aktualnego brandingu InPost Pay, wytyczne odnośnie implementacji wizualnej widgetu oraz dobre praktyki, warte stosowania przy budowaniu pozytywnego user experience wśród Kupujących online. Przewodnik Merchanta znajdziesz w sekcji \"przydatne linki\".'; +$_MODULE['<{inpostizi}prestashop>admin_template_translations_6db0e5922026df9fc64ea03d8c76d945'] = 'Konfiguracja tego widgetu nie jest prawidłowa, proszę poprawić wartości wpisane w formularzu.'; +$_MODULE['<{inpostizi}prestashop>admin_template_translations_7565f5b67d49eec396df536862f2752d'] = 'Zaznacz wszystkie opcje'; +$_MODULE['<{inpostizi}prestashop>admin_template_translations_d143a28bae5fa7d0f79a0c4eb86d68b7'] = 'Wybierz opcje z listy'; +$_MODULE['<{inpostizi}prestashop>admin_template_translations_936ccdb97115e9f35a11d35e3d5b5cad'] = 'Kliknij tutaj,'; +$_MODULE['<{inpostizi}prestashop>admin_template_translations_c73d2e0758341318a79deeb36a40a72c'] = 'aby przejść do naszej dokumentacji i zapoznać się z pełną instrukcją konfiguracji formatu wiadomości.'; +$_MODULE['<{inpostizi}prestashop>admin_template_translations_ec53a8c4f07baed5d8825072c89799be'] = 'Status'; +$_MODULE['<{inpostizi}prestashop>admin_template_translations_4d3d769b812b6faa6b76e1a8abaece2d'] = 'Aktywny'; +$_MODULE['<{inpostizi}prestashop>admin_template_translations_73a1b88236287a552fd7b9e4401f333c'] = 'Oczekuje na akceptację'; +$_MODULE['<{inpostizi}prestashop>admin_template_translations_6a628e226d3bd61caba557cd9fd9e5b6'] = 'Nie odnaleziony w API'; +$_MODULE['<{inpostizi}prestashop>admin_template_translations_79e41f293980da78b8ed7b49ad5462b5'] = 'Możliwy import'; +$_MODULE['<{inpostizi}prestashop>admin_template_translations_e9e569ef5153bfd6b14a155352d56301'] = 'Produkt nie istnieje lub nie jest dostępny na sprzedaż'; +$_MODULE['<{inpostizi}prestashop>admin_template_translations_405e6a003a719605addb982f394d2fbe'] = 'Nie odnaleziono produktów.'; +$_MODULE['<{inpostizi}prestashop>order_details_6310f29293c902c64db619c29179d99a'] = 'Wysyłka'; +$_MODULE['<{inpostizi}prestashop>order_details_4d5340b646d9723cdb3bdf42182658d0'] = 'Paczkomat'; +$_MODULE['<{inpostizi}prestashop>order_details_bfa7e2e76dfa5492ab926b3c8d5d5e65'] = 'Klient prosi o wystawienie faktury VAT'; diff --git a/modules/inpostizi/upgrade/AssetsRemoverTrait.php b/modules/inpostizi/upgrade/AssetsRemoverTrait.php new file mode 100644 index 00000000..7478ba63 --- /dev/null +++ b/modules/inpostizi/upgrade/AssetsRemoverTrait.php @@ -0,0 +1,19 @@ +removeFiles(array_map(static function (string $path): string { + return 'views/' . $path; + }, $paths)); + } +} diff --git a/modules/inpostizi/upgrade/CacheClearer.php b/modules/inpostizi/upgrade/CacheClearer.php new file mode 100644 index 00000000..1540babd --- /dev/null +++ b/modules/inpostizi/upgrade/CacheClearer.php @@ -0,0 +1,87 @@ +filesystem = new Filesystem(); + } + + public static function getInstance(): self + { + if (!isset(self::$instance)) { + self::$instance = new self(); + } + + return self::$instance; + } + + public function clear(): void + { + if ($this->registered) { + return; + } + + $this->registered = true; + $this->doClear(); + } + + private function doClear(): void + { + if (\Tools::version_compare(_PS_VERSION_, '1.7.1')) { + register_shutdown_function(function () { + $this->removeCacheDirectory('prod'); + $this->removeCacheDirectory('dev'); + }); + + return; + } + + if (\Tools::version_compare(_PS_VERSION_, '8.0.0')) { + \Tools::clearSf2Cache('prod'); + \Tools::clearSf2Cache('dev'); + + return; + } + + if ('prod' !== _PS_ENV_) { + register_shutdown_function(function () { + $this->removeCacheDirectory('prod'); + }); + } + + /* @see \Module::$_INSTANCE if not empty when dumping container metadata on PHP versions before 8.1, might cause the script to exceed memory limit + * in {@see \Symfony\Component\Config\Resource\ReflectionClassResource::generateSignature()} due to https://bugs.php.net/bug.php?id=80821 */ + if (80100 > PHP_VERSION_ID) { + register_shutdown_function([\Module::class, 'resetStaticCache']); + } + + \Tools::clearSf2Cache(); + } + + private function removeCacheDirectory(string $env): void + { + $dir = sprintf('%s/%s', dirname(_PS_CACHE_DIR_), $env); + + $this->filesystem->remove($dir); + } + + private function __clone() + { + } +} diff --git a/modules/inpostizi/upgrade/ConfigUpdaterTrait.php b/modules/inpostizi/upgrade/ConfigUpdaterTrait.php new file mode 100644 index 00000000..6a168a88 --- /dev/null +++ b/modules/inpostizi/upgrade/ConfigUpdaterTrait.php @@ -0,0 +1,53 @@ +select('c.*') + ->from('configuration', 'c') + ->where(sprintf('c.name IN ("%s")', implode('","', $keys))); + + return $this->db->executeS($sql) ?: []; + } + + private function groupConfigValuesByShop(array $data): array + { + $dataByShopGroup = []; + + foreach ($data as $row) { + $dataByShopGroup[(int) $row['id_shop_group']][(int) $row['id_shop']][$row['name']] = $row['value']; + } + + return $dataByShopGroup; + } + + private function setJsonConfigValues(string $key, array $dataByShopGroup): bool + { + foreach ($dataByShopGroup as $shopGroupId => $dataByShop) { + foreach ($dataByShop as $shopId => $data) { + $value = json_encode($data); + if (!\Configuration::updateValue($key, $value, false, $shopGroupId, $shopId)) { + return false; + } + } + } + + return true; + } + + private function deleteConfigurationByKeys(array $keys): bool + { + return $this->db->delete('configuration', 'name IN ("' . implode('","', $keys) . '")'); + } +} diff --git a/modules/inpostizi/upgrade/FileRemoverTrait.php b/modules/inpostizi/upgrade/FileRemoverTrait.php new file mode 100644 index 00000000..6c4c8625 --- /dev/null +++ b/modules/inpostizi/upgrade/FileRemoverTrait.php @@ -0,0 +1,54 @@ +module->getLocalPath(), '/'); + + $files = array_map(static function (string $path) use ($basePath): string { + return sprintf('%s/%s', $basePath, $path); + }, $paths); + + $this->getFileSystem()->remove($files); + + return true; + } + + private function removeClasses(array $classes): bool + { + $paths = array_map(static function (string $class): string { + $class = str_replace(self::$namespacePrefix, '', $class); + + return 'src/' . str_replace('\\', DIRECTORY_SEPARATOR, $class) . '.php'; + }, $classes); + + return $this->removeFiles($paths); + } + + private function getFileSystem(): Filesystem + { + return $this->filesystem ?? $this->filesystem = new Filesystem(); + } +} diff --git a/modules/inpostizi/upgrade/upgrade-1.10.0.php b/modules/inpostizi/upgrade/upgrade-1.10.0.php new file mode 100644 index 00000000..1a5f9b06 --- /dev/null +++ b/modules/inpostizi/upgrade/upgrade-1.10.0.php @@ -0,0 +1,75 @@ +module = $module; + } + + public function upgrade(): bool + { + CacheClearer::getInstance()->clear(); + + return $this->registerHooks() + && $this->initCodPaymentOrderStateConfig() + && $this->removeStaleAssets(self::STALE_ASSETS); + } + + private function registerHooks(): bool + { + $hooks = [ + ActionObjectOrderUpdateBefore::HOOK_NAME, + ActionObjectOrderUpdateAfter::HOOK_NAME, + ]; + + return $this->module->registerHook($hooks); + } + + private function initCodPaymentOrderStateConfig(): bool + { + if (0 >= $orderStateId = $this->getCodPaymentOrderStateId()) { + return true; + } + + return \Configuration::updateGlobalValue('INPOST_PAY_COD_OS_ID', $orderStateId); + } + + private function getCodPaymentOrderStateId(): int + { + $orderStateId = (int) Configuration::get('PS_OS_COD_VALIDATION'); + + if (Validate::isLoadedObject(new OrderState($orderStateId))) { + return $orderStateId; + } + + return (int) Configuration::get('INPOST_PAY_INITIAL_OS_ID'); + } +} + +/** + * @param InPostIzi $module + */ +function upgrade_module_1_10_0(Module $module): bool +{ + return (new InPostIziUpdater_1_10_0($module))->upgrade(); +} diff --git a/modules/inpostizi/upgrade/upgrade-1.11.0.php b/modules/inpostizi/upgrade/upgrade-1.11.0.php new file mode 100644 index 00000000..611feee1 --- /dev/null +++ b/modules/inpostizi/upgrade/upgrade-1.11.0.php @@ -0,0 +1,65 @@ +module = $module; + $this->installer = $installer; + } + + public function upgrade(): bool + { + CacheClearer::getInstance()->clear(); + $this->installer->install($this->module); + + return $this->registerHooks(); + } + + private function registerHooks(): bool + { + return $this->module->registerHook([ + ActionAdminCartRuleSaveAfter::HOOK_NAME, + ActionAdminControllerSetMedia::HOOK_NAME, + DisplayBackOfficeHeader::HOOK_NAME, + ]); + } +} + +/** + * @param InPostIzi $module + */ +function upgrade_module_1_11_0(Module $module): bool +{ + $db = Db::getInstance(); + $dbInstaller = new DatabaseInstaller(new Configuration($db), [ + new Version_1_11_0(new Connection($db)), + ]); + + return (new InPostIziUpdater_1_11_0($module, $dbInstaller))->upgrade(); +} diff --git a/modules/inpostizi/upgrade/upgrade-1.3.15.php b/modules/inpostizi/upgrade/upgrade-1.3.15.php new file mode 100644 index 00000000..a8d28ec6 --- /dev/null +++ b/modules/inpostizi/upgrade/upgrade-1.3.15.php @@ -0,0 +1,30 @@ +id_reference)) { + return false; + } + } + + return $module->registerHook('displayPaymentReturn') + && $module->registerHook('actionAjaxDieCartControllerDisplayAjaxUpdateBefore') + && $module->unregisterHook('actionPresentCart') + && $module->unregisterHook('actionCartUpdateQuantityBefore'); +} diff --git a/modules/inpostizi/upgrade/upgrade-1.4.0.php b/modules/inpostizi/upgrade/upgrade-1.4.0.php new file mode 100644 index 00000000..e64b67b4 --- /dev/null +++ b/modules/inpostizi/upgrade/upgrade-1.4.0.php @@ -0,0 +1,41 @@ +delete('configuration', 'name LIKE "INPOST_PAY_status_translation_%"'); + $db->execute(sprintf(' + UPDATE `%sconfiguration` + SET `value` = (`value` = "dark") + WHERE `name` IN ("INPOST_PAY_background_cart", "INPOST_PAY_background_details") + ', _DB_PREFIX_)); + $db->execute(sprintf(' + UPDATE `%sconfiguration` + SET `value` = IF(`value` = "yellow", "%s", "%s") + WHERE `name` IN ("INPOST_PAY_variant_cart", "INPOST_PAY_variant_details") + ', _DB_PREFIX_, Variant::Primary()->value, Variant::Secondary()->value)); + + $dbInstaller->install($module); + + return $module->registerHook('actionObjectCartDeleteBefore') + && $module->registerHook('actionObjectInPostShipmentModelUpdateBefore') + && \Configuration::updateGlobalValue('INPOST_PAY_INITIAL_OS_ID', \Configuration::get('PS_OS_BANKWIRE')); +} diff --git a/modules/inpostizi/upgrade/upgrade-1.4.1.php b/modules/inpostizi/upgrade/upgrade-1.4.1.php new file mode 100644 index 00000000..e001746a --- /dev/null +++ b/modules/inpostizi/upgrade/upgrade-1.4.1.php @@ -0,0 +1,59 @@ +select('c.*, cl.*') + ->from('configuration', 'c') + ->innerJoin('configuration_lang', 'cl', 'cl.id_configuration = c.id_configuration') + ->where('c.name LIKE "INPOST_PAY_OS_DESCRIPTION_%"'); + + if ($data = $db->executeS($sql)) { + $configIds = []; + + $mappings = []; + foreach ($data as $row) { + $configId = (int) $row['id_configuration']; + $configIds[$configId] = $configId; + + if ('' === trim($row['value'])) { + continue; + } + + $osId = (int) filter_var($row['name'], FILTER_SANITIZE_NUMBER_INT); + $mappings[(int) $row['id_shop_group']][(int) $row['id_shop']][(int) $row['id_lang']][$osId] = $row['value']; + } + + foreach ($mappings as $shopGroupId => $mappingsByShop) { + foreach ($mappingsByShop as $shopId => $mappingsByLang) { + Configuration::updateValue('INPOST_PAY_OS_DESCRIPTION_MAP', array_map('json_encode', $mappingsByLang), false, $shopGroupId, $shopId); + } + } + + $db->delete('configuration', 'id_configuration IN (' . implode(',', $configIds) . ')'); + $db->delete('configuration_lang', 'id_configuration IN (' . implode(',', $configIds) . ')'); + } + + Configuration::updateGlobalValue('INPOST_PAY_THANK_YOU_DISPLAY', DisplayPaymentReturn::getHookName()); + + return $module->unregisterHook('displayFooterProduct') + && $module->unregisterHook(DisplayShoppingCart::HOOK_NAME) + && $module->unregisterHook(DisplayShoppingCartFooter::HOOK_NAME) + && $module->registerHook(HookExecutor::getHooksToInstall(_PS_VERSION_)); +} diff --git a/modules/inpostizi/upgrade/upgrade-1.5.0.php b/modules/inpostizi/upgrade/upgrade-1.5.0.php new file mode 100644 index 00000000..e139cd64 --- /dev/null +++ b/modules/inpostizi/upgrade/upgrade-1.5.0.php @@ -0,0 +1,274 @@ + 'darkMode', + 'INPOST_PAY_variant_cart' => 'variant', + 'INPOST_PAY_frame_style_cart' => 'frameStyle', + 'INPOST_PAY_min_width_cart' => 'minWidthPx', + 'INPOST_PAY_max_width_cart' => 'maxWidthPx', + ]; + + private const PRODUCT_WIDGET_CONFIG_MAP = [ + 'INPOST_PAY_alignment_details' => 'alignment', + 'INPOST_PAY_background_details' => 'darkMode', + 'INPOST_PAY_variant_details' => 'variant', + 'INPOST_PAY_frame_style_details' => 'frameStyle', + 'INPOST_PAY_min_width_details' => 'minWidthPx', + 'INPOST_PAY_max_width_details' => 'maxWidthPx', + ]; + + private const CART_STYLES_CONFIG_MAP = [ + 'INPOST_PAY_margin_cart_up' => 'marginTop', + 'INPOST_PAY_margin_cart_left' => 'marginLeft', + 'INPOST_PAY_margin_cart_right' => 'marginRight', + 'INPOST_PAY_margin_cart_down' => 'marginBottom', + 'INPOST_PAY_alignment_cart' => 'alignment', + ]; + + private const PRODUCT_STYLES_CONFIG_MAP = [ + 'INPOST_PAY_alignment_details' => 'alignment', + ]; + + /** + * @var Db + */ + private $db; + + public function __construct(Db $db) + { + $this->db = $db; + } + + public function upgrade(): bool + { + CacheClearer::getInstance()->clear(); + + return $this->updateEnabledConfigValue() + && $this->updateConsentStructure() + && $this->updateWidgetConfigStructure(); + } + + private function updateEnabledConfigValue(): bool + { + return $this->db->execute(sprintf(' + UPDATE `%sconfiguration` + SET `value` = (`value` = 2) + WHERE `name` = "INPOST_PAY_show_izi" + ', _DB_PREFIX_)); + } + + private function updateConsentStructure(): bool + { + $consentsByShopGroup = []; + $configIds = []; + + $languageIds = Language::getLanguages(false, false, true); + + $requirementTypes = [ + 'additional' => ConsentRequirementType::Optional(), + 'required' => ConsentRequirementType::RequiredAlways(), + 'required_once' => ConsentRequirementType::RequiredOnce(), + ]; + + foreach ($requirementTypes as $key => $requirementType) { + $cmsIdsKey = sprintf('INPOST_PAY_terms_options_%s', $key); + $textKey = sprintf('%s_text', $cmsIdsKey); + + $sql = (new DbQuery()) + ->select('c.*') + ->from('configuration', 'c') + ->where(sprintf('c.name IN ("%s", "%s")', $cmsIdsKey, $textKey)); + + $data = $this->db->executeS($sql); + + if ([] === $data) { + continue; + } + + $dataByShopGroup = $this->groupConfigValuesByShop($data, $configIds); + + foreach ($dataByShopGroup as $shopGroupId => $dataByShop) { + foreach ($dataByShop as $shopId => $data) { + $cmsPageIds = explode(',', $data[$cmsIdsKey]); + $text = trim($data[$textKey]); + if ('' === $text) { + continue; + } + + $descriptions = []; + foreach ($languageIds as $languageId) { + $descriptions[$languageId] = $text; + } + + $dateUpdated = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $data['date_upd']); + + foreach ($cmsPageIds as $index => $cmsPageId) { + if (0 >= $cmsPageId = (int) $cmsPageId) { + continue; + } + + $consentsByShopGroup[$shopGroupId][$shopId][] = new Consent( + new ConsentLink((string) $index, $cmsPageId), + $descriptions, + $requirementType, + [], + $dateUpdated + ); + } + } + } + } + + if (!$this->setJsonConfigValues('INPOST_PAY_CONSENTS', $consentsByShopGroup)) { + return false; + } + + if ([] === $configIds) { + return true; + } + + return $this->db->delete('configuration', 'id_configuration IN (' . implode(',', $configIds) . ')'); + } + + private function updateWidgetConfigStructure(): bool + { + return $this->updateCartWidgetConfigStructure() + && $this->updateProductWidgetConfigStructure(); + } + + private function updateCartWidgetConfigStructure(): bool + { + $configs = $this->getWidgetConfigs(self::CART_WIDGET_CONFIG_MAP, BindingPlace::BasketSummary()); + $styles = $this->getWidgetStyles(self::CART_STYLES_CONFIG_MAP); + + return $this->setJsonConfigValues('INPOST_PAY_CART_WIDGET_CONFIG', $configs) + && $this->setJsonConfigValues('INPOST_PAY_CART_HTML_STYLES', $styles) + && $this->deleteConfigurationByKeys(array_keys(array_merge(self::CART_WIDGET_CONFIG_MAP, self::CART_STYLES_CONFIG_MAP))); + } + + private function updateProductWidgetConfigStructure(): bool + { + $configs = $this->getWidgetConfigs(self::PRODUCT_WIDGET_CONFIG_MAP, BindingPlace::ProductCard()); + $styles = $this->getWidgetStyles(self::PRODUCT_STYLES_CONFIG_MAP); + + return $this->setJsonConfigValues('INPOST_PAY_PRODUCT_CARD_WIDGET_CONFIG', $configs) + && $this->setJsonConfigValues('INPOST_PAY_PRODUCT_HTML_STYLES', $styles) + && $this->deleteConfigurationByKeys(array_keys(self::PRODUCT_WIDGET_CONFIG_MAP)); + } + + private function getWidgetConfigs(array $map, BindingPlace $bindingPlace): array + { + if ([] === $data = $this->getConfigDataByKeys(array_keys($map))) { + return []; + } + + $dataByShopGroup = $this->groupConfigValuesByShop($data); + + $configs = []; + + foreach ($dataByShopGroup as $shopGroupId => $dataByShop) { + foreach ($dataByShop as $shopId => $data) { + $config = []; + foreach ($map as $key => $value) { + $config[$value] = $data[$key] ?? null; + } + + $maxWidth = $this->getWidgetWidth((int) $config['maxWidthPx']); + + $configs[$shopGroupId][$shopId] = (new WidgetConfiguration($bindingPlace)) + ->setVariant(Variant::tryFrom($config['variant']) ?? Variant::Secondary()) + ->setDarkMode((bool) $config['darkMode']) + ->setFrameStyle(FrameStyle::tryFrom($config['frameStyle'])) + ->setMaxWidthPx($maxWidth); + } + } + + return $configs; + } + + private function getWidgetStyles(array $map): array + { + if ([] === $data = $this->getConfigDataByKeys(array_keys($map))) { + return []; + } + + $dataByShopGroup = $this->groupConfigValuesByShop($data); + + $styles = []; + + foreach ($dataByShopGroup as $shopGroupId => $dataByShop) { + foreach ($dataByShop as $shopId => $data) { + $config = []; + foreach ($map as $key => $value) { + $config[$value] = $data[$key] ?? null; + } + + $justifyContent = HtmlStyles::getJustifyContentStyleByAlignment($config['alignment'] ?? null); + + $styles[$shopGroupId][$shopId] = (new HtmlStyles()) + ->setMarginTop($config['marginTop'] ?? null) + ->setMarginLeft($config['marginLeft'] ?? null) + ->setMarginRight($config['marginRight'] ?? null) + ->setMarginBottom($config['marginBottom'] ?? null) + ->setJustifyContent($justifyContent); + } + } + + return $styles; + } + + private function groupConfigValuesByShop(array $data, array &$configIds = []): array + { + $dataByShopGroup = []; + + foreach ($data as $row) { + $dateUpdated = $dataByShopGroup[(int) $row['id_shop_group']][(int) $row['id_shop']]['date_upd'] ?? null; + + $dataByShopGroup[(int) $row['id_shop_group']][(int) $row['id_shop']][$row['name']] = $row['value']; + if (null === $dateUpdated || $row['date_upd'] < $dateUpdated) { + $dataByShopGroup[(int) $row['id_shop_group']][(int) $row['id_shop']]['date_upd'] = $row['date_upd']; + } + + $configIds[] = $row['id_configuration']; + } + + return $dataByShopGroup; + } + + private function getWidgetWidth(int $width): ?int + { + return WidgetConfiguration::WIDTH_MIN_PX <= $width && WidgetConfiguration::WIDTH_MAX_PX >= $width ? $width : null; + } +} + +/** + * @param InPostIzi $module + * + * @return bool + */ +function upgrade_module_1_5_0(Module $module) +{ + return (new InPostIziUpdater_1_5_0(Db::getInstance()))->upgrade(); +} diff --git a/modules/inpostizi/upgrade/upgrade-1.5.5.php b/modules/inpostizi/upgrade/upgrade-1.5.5.php new file mode 100644 index 00000000..b39d1cd7 --- /dev/null +++ b/modules/inpostizi/upgrade/upgrade-1.5.5.php @@ -0,0 +1,212 @@ + 'mappingId', + 'INPOST_PAY_payment_apm_cod' => 'services.COD.cost', + 'INPOST_PAY_payment_apm_pww' => 'services.PWW.cost', + 'INPOST_PAY_payment_apm_pww_from_day' => 'services.PWW.availability.start.day', + 'INPOST_PAY_payment_apm_pww_from_time' => 'services.PWW.availability.start.time', + 'INPOST_PAY_payment_apm_pww_to_day' => 'services.PWW.availability.end.day', + 'INPOST_PAY_payment_apm_pww_to_time' => 'services.PWW.availability.end.time', + ]; + + private const COURIER_CONFIG_MAP = [ + 'INPOST_PAY_payment_courier' => 'mappingId', + 'INPOST_PAY_payment_courier_cod' => 'services.COD.cost', + ]; + + private const UNMAPPED_CONFIG_KEYS = [ + 'INPOST_PAY_payment_apm_cod_from_day', + 'INPOST_PAY_payment_apm_cod_from_time', + 'INPOST_PAY_payment_apm_cod_to_day', + 'INPOST_PAY_payment_apm_cod_to_time', + 'INPOST_PAY_payment_courier_cod_from_day', + 'INPOST_PAY_payment_courier_cod_to_day', + 'INPOST_PAY_payment_courier_cod_from_time', + 'INPOST_PAY_payment_courier_cod_to_time', + 'INPOST_PAY_payment_courier_pww', + 'INPOST_PAY_payment_courier_pww_from_day', + 'INPOST_PAY_payment_courier_pww_to_day', + 'INPOST_PAY_payment_courier_pww_from_time', + 'INPOST_PAY_payment_courier_pww_to_time', + ]; + + /** + * @var Db + */ + private $db; + + /** + * @var Module + */ + private $module; + + public function __construct(Db $db, Module $module) + { + $this->db = $db; + $this->module = $module; + } + + public function upgrade(): bool + { + CacheClearer::getInstance()->clear(); + + if (!$this->updateShippingConfigStructure()) { + return false; + } + + $this->module->unregisterHook(ActionGetPaymentOptions::HOOK_NAME); + + return true; + } + + private function updateShippingConfigStructure(): bool + { + return $this->updateApmShippingConfigStructure() + && $this->updateCourierShippingConfigStructure() + && $this->deleteConfigurationByKeys(self::UNMAPPED_CONFIG_KEYS); + } + + private function updateApmShippingConfigStructure(): bool + { + $configs = $this->getShippingOptionsConfigs(self::APM_CONFIG_MAP, DeliveryType::Apm()); + + return $this->setJsonConfigValues('INPOST_PAY_APM_SHIPPING_OPTIONS', $configs) + && $this->deleteConfigurationByKeys(array_keys(self::APM_CONFIG_MAP)); + } + + private function updateCourierShippingConfigStructure(): bool + { + $configs = $this->getShippingOptionsConfigs(self::COURIER_CONFIG_MAP, DeliveryType::Courier()); + + return $this->setJsonConfigValues('INPOST_PAY_COURIER_SHIPPING_OPTIONS', $configs) + && $this->deleteConfigurationByKeys(array_keys(self::COURIER_CONFIG_MAP)); + } + + private function getShippingOptionsConfigs(array $map, DeliveryType $deliveryType): array + { + if ([] === $data = $this->getConfigDataByKeys(array_keys($map))) { + return []; + } + + $shippingOptions = []; + $dataByShopGroup = $this->groupConfigValuesByShop($data); + + foreach ($dataByShopGroup as $shopGroupId => $dataByShop) { + foreach ($dataByShop as $shopId => $data) { + $config = $this->reorganizeData($data, $map); + + $shippingOptions[$shopGroupId][$shopId] = new ShippingOptions( + $this->getCarrierMappings($config['mappingId'], $deliveryType), + $this->getOptionalServices($config['services'], $deliveryType) + ); + } + } + + return $shippingOptions; + } + + private function getCarrierMappings(?int $referenceId, DeliveryType $deliveryType): array + { + $mappings = []; + + foreach (ServiceCode::getAvailableCombinations($deliveryType) as $serviceCodes) { + $mappings[] = new CarrierMapping($referenceId, $serviceCodes); + } + + return $mappings; + } + + private function getOptionalServices(array $config, DeliveryType $deliveryType): array + { + $services = []; + + foreach ($deliveryType->getAvailableServiceCodes() as $serviceCode) { + $key = $serviceCode->value; + $services[] = $this->getServiceOptions($config[$key], $serviceCode); + } + + return $services; + } + + private function getServiceOptions(array $config, ServiceCode $serviceCode): ServiceOptions + { + return new ServiceOptions( + $serviceCode, + isset($config['cost']) ? (float) $config['cost'] : null, + $serviceCode->isAvailabilityTimeDependent() ? $this->getTimeRange($config['availability']) : null + ); + } + + private function getTimeRange(array $config): TimeOfWeekRange + { + return new TimeOfWeekRange( + $this->getTimeOfWeek($config['start']), + $this->getTimeOfWeek($config['end']) + ); + } + + private function getTimeOfWeek(array $config): TimeOfWeek + { + $weekDay = isset($config['day']) ? WeekDay::tryFrom($config['day'] + 1) : null; + $time = isset($config['time']) + ? DateTimeImmutable::createFromFormat('G', (int) $config['time']) + : null; + + return new TimeOfWeek($weekDay, $time); + } + + private function reorganizeData(array $data, array $map): array + { + $config = []; + + foreach ($map as $key => $path) { + $value = $data[$key] ?? null; + + if (false === strpos($path, '.')) { + $config[$path] = $value; + } else { + $path = explode('.', $path); + while ($part = array_pop($path)) { + $value = [$part => $value]; + } + $config = array_merge_recursive($config, $value); + } + } + + return $config; + } +} + +/** + * @param InPostIzi $module + * + * @return bool + */ +function upgrade_module_1_5_5(Module $module) +{ + return (new InPostIziUpdater_1_5_5(Db::getInstance(), $module))->upgrade(); +} diff --git a/modules/inpostizi/upgrade/upgrade-1.5.7.php b/modules/inpostizi/upgrade/upgrade-1.5.7.php new file mode 100644 index 00000000..a4db6d3c --- /dev/null +++ b/modules/inpostizi/upgrade/upgrade-1.5.7.php @@ -0,0 +1,19 @@ +db = $db; + $this->module = $module; + } + + public function upgrade(): bool + { + CacheClearer::getInstance()->clear(); + + return $this->registerHooks() + && $this->updateAvailablePaymentOptionsConfig() + && $this->removeStaleAssets([ + 'js/prestashopizi.js', + ]); + } + + private function registerHooks(): bool + { + $productCardHook = $this->getDefaultProductCardHook(); + + if (!Configuration::updateGlobalValue('INPOST_PAY_PRODUCT_CARD_DISPLAY_HOOK', $productCardHook)) { + return false; + } + + return $this->module->unregisterHook('actionCartSave') + && $this->module->registerHook($this->getHooksToInstall()); + } + + private function getDefaultProductCardHook(): string + { + if ( + DisplayProductActions::getVersionRange()->contains(_PS_VERSION_) + && !$this->module->isRegisteredInHook(DisplayProductAdditionalInfo::HOOK_NAME) + ) { + return DisplayProductActions::HOOK_NAME; + } + + return DisplayProductAdditionalInfo::HOOK_NAME; + } + + private function updateAvailablePaymentOptionsConfig(): bool + { + $map = [ + 'INPOST_PAY_payment_inpost' => [PaymentType::CashOnDelivery()], + 'INPOST_PAY_payment_aion' => [ + PaymentType::Card(), + PaymentType::CardToken(), + PaymentType::GooglePay(), + PaymentType::ApplePay(), + PaymentType::BlikCode(), + PaymentType::BlikToken(), + PaymentType::PayByLink(), + PaymentType::ShoppingLimit(), + ], + ]; + + $configs = $this->getAvailablePaymentOptionsConfigs($map); + + return $this->setJsonConfigValues('INPOST_PAY_AVAILABLE_PAYMENT_OPTIONS', $configs) + && $this->deleteConfigurationByKeys(array_keys($map)); + } + + private function getAvailablePaymentOptionsConfigs(array $map): array + { + if ([] === $data = $this->getConfigDataByKeys(array_keys($map))) { + return []; + } + + $paymentOptions = []; + $dataByShopGroup = $this->groupConfigValuesByShop($data); + + foreach ($dataByShopGroup as $shopGroupId => $dataByShop) { + foreach ($dataByShop as $shopId => $data) { + if ([] === $data = array_filter($data)) { + continue; + } + + $types = []; + + foreach ($data as $key => $ignored) { + $types[] = $map[$key]; + } + + $paymentOptions[$shopGroupId][$shopId] = array_merge(...$types); + } + } + + return $paymentOptions; + } + + private function getHooksToInstall(): array + { + $hooks = [ + DisplayCustomerLoginFormAfter::HOOK_NAME, + DisplayCustomerAccountFormTop::HOOK_NAME, + DisplayCheckoutSummaryTop::HOOK_NAME, + DisplayIziCartPreviewButton::HOOK_NAME, + DisplayIziCheckoutButton::HOOK_NAME, + ActionCartUpdateAfter::HOOK_NAME, + ActionOrderStatusPostUpdate::HOOK_NAME, + ]; + + if (DisplayAdminOrderLeft::getVersionRange()->contains(_PS_VERSION_)) { + $hooks[] = DisplayAdminOrderLeft::HOOK_NAME; + } + + return $hooks; + } +} + +/** + * @param InPostIzi $module + */ +function upgrade_module_1_6_0(Module $module): bool +{ + return (new InPostIziUpdater_1_6_0(Db::getInstance(), $module))->upgrade(); +} diff --git a/modules/inpostizi/upgrade/upgrade-1.7.0.php b/modules/inpostizi/upgrade/upgrade-1.7.0.php new file mode 100644 index 00000000..5b8dfbc6 --- /dev/null +++ b/modules/inpostizi/upgrade/upgrade-1.7.0.php @@ -0,0 +1,83 @@ +db = $db; + $this->module = $module; + } + + public function upgrade(): bool + { + CacheClearer::getInstance()->clear(); + + return $this->fixAvailablePaymentOptionsConfig() + && $this->removeStaleAssets(self::STALE_ASSETS); + } + + private function fixAvailablePaymentOptionsConfig(): bool + { + $configs = $this->getAvailablePaymentOptionsConfigs(); + + return $this->setJsonConfigValues('INPOST_PAY_AVAILABLE_PAYMENT_OPTIONS', $configs); + } + + private function getAvailablePaymentOptionsConfigs(): array + { + if ([] === $data = $this->getConfigDataByKeys(['INPOST_PAY_AVAILABLE_PAYMENT_OPTIONS'])) { + return []; + } + + $configs = []; + $dataByShopGroup = $this->groupConfigValuesByShop($data); + + foreach ($dataByShopGroup as $shopGroupId => $dataByShop) { + foreach ($dataByShop as $shopId => $data) { + if (null === $value = $data['INPOST_PAY_AVAILABLE_PAYMENT_OPTIONS']) { + continue; + } + + $data = json_decode($value, true); + + if (!is_array($data) || $data === $config = array_values($data)) { + continue; + } + + $configs[$shopGroupId][$shopId] = $config; + } + } + + return $configs; + } +} + +/** + * @param InPostIzi $module + */ +function upgrade_module_1_7_0(Module $module): bool +{ + $db = Db::getInstance(); + + return (new InPostIziUpdater_1_7_0($db, $module))->upgrade(); +} diff --git a/modules/inpostizi/upgrade/upgrade-1.7.1.php b/modules/inpostizi/upgrade/upgrade-1.7.1.php new file mode 100644 index 00000000..76e02d35 --- /dev/null +++ b/modules/inpostizi/upgrade/upgrade-1.7.1.php @@ -0,0 +1,19 @@ +clear(); + + return true; +} diff --git a/modules/inpostizi/upgrade/upgrade-1.8.0.php b/modules/inpostizi/upgrade/upgrade-1.8.0.php new file mode 100644 index 00000000..2371c8e8 --- /dev/null +++ b/modules/inpostizi/upgrade/upgrade-1.8.0.php @@ -0,0 +1,19 @@ +clear(); + + return true; +} diff --git a/modules/inpostizi/upgrade/upgrade-1.9.0.php b/modules/inpostizi/upgrade/upgrade-1.9.0.php new file mode 100644 index 00000000..537f846d --- /dev/null +++ b/modules/inpostizi/upgrade/upgrade-1.9.0.php @@ -0,0 +1,60 @@ +module = $module; + $this->installer = $installer; + } + + public function upgrade(): bool + { + CacheClearer::getInstance()->clear(); + $this->installer->install($this->module); + + return $this->removeStaleAssets(self::STALE_ASSETS); + } +} + +/** + * @param InPostIzi $module + */ +function upgrade_module_1_9_0(Module $module): bool +{ + $db = Db::getInstance(); + $dbInstaller = new DatabaseInstaller(new Configuration($db), [ + new Version_1_9_0(new Connection($db)), + ]); + + return (new InPostIziUpdater_1_9_0($module, $dbInstaller))->upgrade(); +} diff --git a/modules/inpostizi/upgrade/upgrade-2.0.0.php b/modules/inpostizi/upgrade/upgrade-2.0.0.php new file mode 100644 index 00000000..a3271232 --- /dev/null +++ b/modules/inpostizi/upgrade/upgrade-2.0.0.php @@ -0,0 +1,230 @@ +module = $module; + $this->installer = $installer; + $this->db = $db; + } + + public function upgrade(): bool + { + CacheClearer::getInstance()->clear(); + $this->installer->install($this->module); + + return $this->updateGuiConfiguration() + && $this->removeStaleAssets(self::STALE_ASSETS) + && $this->removeClasses(self::CLASSES_TO_REMOVE) + && $this->removeFiles(self::FILES_TO_REMOVE); + } + + private function updateGuiConfiguration(): bool + { + $result = true; + + foreach ($this->getConfigurableBindingPlaces() as $bindingPlace) { + $result &= $this->updateStylesConfig($bindingPlace); + } + + return (bool) $result; + } + + /** + * Method may not exist if the previous version of the file was already included (e.g., during recompilation of the container) + * before unpacking a new version of the module. + */ + private function getConfigurableBindingPlaces(): array + { + if (method_exists(GuiConfiguration::class, 'getConfigurableBindingPlaces')) { + return GuiConfiguration::getConfigurableBindingPlaces(); + } + + return [ + BindingPlace::BasketSummary(), + BindingPlace::ProductCard(), + BindingPlace::LoginPage(), + BindingPlace::RegisterFormPage(), + BindingPlace::CheckoutPage(), + BindingPlace::MiniCartPage(), + BindingPlace::OrderCreate(), + ]; + } + + private function updateStylesConfig(BindingPlace $bindingPlace): bool + { + $data = $this->getConfigDataByKeys([ + $widgetConfigKey = self::getWidgetConfigKey($bindingPlace), + $stylesConfigKey = self::getHtmlStylesConfigKey($bindingPlace), + ]); + + if ([] === $data) { + return true; + } + + $dataByShopGroup = $this->groupConfigValuesByShop($data); + + $newWidgetConfigs = []; + $newStyles = []; + + foreach ($dataByShopGroup as $shopGroupId => $dataByShop) { + foreach ($dataByShop as $shopId => $data) { + if (null === $widgetConfig = $data[$widgetConfigKey] ?? null) { + continue; + } + + $widgetConfig = json_decode($widgetConfig, true); + + if (!is_array($widgetConfig) || !isset($widgetConfig['alignment'])) { + continue; + } + + if (isset($data[$stylesConfigKey])) { + $stylesConfig = json_decode($data[$stylesConfigKey], true) ?? []; + } else { + $stylesConfig = []; + } + + $stylesConfig['justifyContent'] = HtmlStyles::getJustifyContentStyleByAlignment($widgetConfig['alignment']); + unset($widgetConfig['alignment'], $widgetConfig['basket'], $widgetConfig['minWidthPx']); + + $newStyles[$shopGroupId][$shopId] = $stylesConfig; + $newWidgetConfigs[$shopGroupId][$shopId] = $widgetConfig; + } + } + + return $this->setJsonConfigValues($widgetConfigKey, $newWidgetConfigs) + && $this->setJsonConfigValues($stylesConfigKey, $newStyles); + } + + private static function getWidgetConfigKey(BindingPlace $bindingPlace): string + { + if (BindingPlace::BasketSummary() === $bindingPlace) { + return 'INPOST_PAY_CART_WIDGET_CONFIG'; + } + + return 'INPOST_PAY_' . $bindingPlace->value . '_WIDGET_CONFIG'; + } + + private static function getHtmlStylesConfigKey(BindingPlace $bindingPlace): string + { + if (BindingPlace::BasketSummary() === $bindingPlace) { + return 'INPOST_PAY_CART_HTML_STYLES'; + } + + if (BindingPlace::ProductCard() === $bindingPlace) { + return 'INPOST_PAY_PRODUCT_HTML_STYLES'; + } + + return 'INPOST_PAY_' . $bindingPlace->value . '_HTML_STYLES'; + } +} + +/** + * @param InPostIzi $module + */ +function upgrade_module_2_0_0(Module $module): bool +{ + if (Tools::version_compare(_PS_VERSION_, '1.7.1')) { + try { + $module->uninstall(); + } finally { + return false; + } + } + + $db = Db::getInstance(); + $dbInstaller = new DatabaseInstaller(new Configuration($db), [ + new Version_2_0_0(new Connection($db)), + ]); + + return (new InPostIziUpdater_2_0_0($module, $dbInstaller, $db))->upgrade(); +} diff --git a/modules/inpostizi/upgrade/upgrade-2.0.2.php b/modules/inpostizi/upgrade/upgrade-2.0.2.php new file mode 100644 index 00000000..8fefb6ad --- /dev/null +++ b/modules/inpostizi/upgrade/upgrade-2.0.2.php @@ -0,0 +1,36 @@ +module = $module; + } + + public function upgrade(): bool + { + return $this->removeStaleAssets(self::STALE_ASSETS); + } +} + +/** + * @param InPostIzi $module + */ +function upgrade_module_2_0_2(Module $module): bool +{ + return (new InPostIziUpdater_2_0_2($module))->upgrade(); +} diff --git a/modules/inpostizi/upgrade/upgrade-2.1.0.php b/modules/inpostizi/upgrade/upgrade-2.1.0.php new file mode 100644 index 00000000..f1618e21 --- /dev/null +++ b/modules/inpostizi/upgrade/upgrade-2.1.0.php @@ -0,0 +1,91 @@ +module = $module; + $this->installer = $installer; + $this->db = $db; + } + + public static function create(Module $module): self + { + $db = Db::getInstance(); + $dbInstaller = new DatabaseInstaller(new Configuration($db), [ + new Version_2_1_0(new Connection($db)), + ]); + + return new self($module, $dbInstaller, $db); + } + + public function upgrade(): bool + { + CacheClearer::getInstance()->clear(); + $this->installer->install($this->module); + + return $this->registerHooks() + && $this->renameCartRulesConfigKey(); + } + + private function registerHooks(): bool + { + return $this->module->registerHook([ + ProductHooks\ActionProductDeleteBefore::HOOK_NAME, + ProductHooks\ActionProductDeleteAfter::HOOK_NAME, + ProductHooks\ActionProductUpdateAfter::HOOK_NAME, + ProductHooks\ActionCombinationDeleteBefore::HOOK_NAME, + ProductHooks\ActionCombinationDeleteAfter::HOOK_NAME, + ProductHooks\ActionCombinationUpdateAfter::HOOK_NAME, + ProductHooks\ActionImageAddAfter::HOOK_NAME, + ProductHooks\ActionImageDeleteAfter::HOOK_NAME, + ProductHooks\ActionSpecificPriceAddAfter::HOOK_NAME, + ProductHooks\ActionSpecificPriceUpdateAfter::HOOK_NAME, + ProductHooks\ActionSpecificPriceDeleteAfter::HOOK_NAME, + ProductHooks\ActionUpdateQuantity::HOOK_NAME, + ]); + } + + private function renameCartRulesConfigKey(): bool + { + return $this->db->update('configuration', [ + 'name' => 'INPOST_PAY_HAS_OMNIBUS_CART_RULES', + ], 'name = "INPOST_PAY_OMNIBUS_CART_RULE_ID"'); + } +} + +/** + * @param InPostIzi $module + */ +function upgrade_module_2_1_0(Module $module): bool +{ + return InPostIziUpdater_2_1_0::create($module)->upgrade(); +} diff --git a/modules/inpostizi/upgrade/upgrade-2.2.0.php b/modules/inpostizi/upgrade/upgrade-2.2.0.php new file mode 100644 index 00000000..dfe89b8c --- /dev/null +++ b/modules/inpostizi/upgrade/upgrade-2.2.0.php @@ -0,0 +1,73 @@ +module = $module; + $this->installer = $installer; + } + + public static function create(Module $module): self + { + $db = Db::getInstance(); + + $dbInstaller = new DatabaseInstaller(new Configuration($db), [ + new Version_2_2_0(new Connection($db)), + ]); + + return new self($module, $dbInstaller); + } + + public function upgrade(): bool + { + CacheClearer::getInstance()->clear(); + $this->installer->install($this->module); + + return $this->registerHooks(); + } + + private function registerHooks(): bool + { + return $this->module->registerHook([ + ActionEmailSendBefore::HOOK_NAME, + ActionAdminInPostConfirmedShipmentsControllerAfter::HOOK_NAME, + ActionAdminInPostConfirmedShipmentsControllerBefore::HOOK_NAME, + DisplayHeader::HOOK_NAME, + ]); + } +} + +/** + * @param InPostIzi $module + */ +function upgrade_module_2_2_0(Module $module): bool +{ + return InPostIziUpdater_2_2_0::create($module)->upgrade(); +} diff --git a/modules/inpostizi/upgrade/upgrade-2.2.2.php b/modules/inpostizi/upgrade/upgrade-2.2.2.php new file mode 100644 index 00000000..239caa0e --- /dev/null +++ b/modules/inpostizi/upgrade/upgrade-2.2.2.php @@ -0,0 +1,119 @@ +clear(); + + $this->db = $db; + $this->client = $client; + } + + public static function create(Module $module): self + { + $db = Db::getInstance(); + + try { + $client = $module->get(BasketAppClientInterface::class); + } catch (ServiceNotFoundException $e) { + $client = null; + } + + return new self($db, $client); + } + + public function upgrade(): bool + { + return $this->updatePaymentOptionsConfig(); + } + + private function updatePaymentOptionsConfig(): bool + { + if (null === $this->client || !$clientId = \Configuration::get('INPOST_PAY_client_id')) { + return true; + } + + try { + $availableTypes = $this->client->getAvailablePaymentOptions()->getPaymentTypes(); + } catch (\Exception $e) { + return true; + } + + $data = $this->getConfigDataByKeys([ + 'INPOST_PAY_client_id', + 'INPOST_PAY_AVAILABLE_PAYMENT_OPTIONS', + ]); + $dataByShopGroup = $this->groupConfigValuesByShop($data); + + foreach ($dataByShopGroup as $shopGroupId => $dataByShop) { + foreach ($dataByShop as $shopId => $data) { + if (!isset($data['INPOST_PAY_AVAILABLE_PAYMENT_OPTIONS'])) { + continue; + } + + if ($clientId !== $this->resolveClientId($dataByShopGroup, $shopGroupId, $shopId)) { + continue; + } + + $enabledTypes = $this->decodePaymentTypesList($data['INPOST_PAY_AVAILABLE_PAYMENT_OPTIONS']); + $value = [] === array_udiff($availableTypes, $enabledTypes, [Enum::class, 'compareValues']); + + if (!\Configuration::updateValue('INPOST_PAY_ENABLE_ALL_PAYMENT_OPTIONS', (int) $value, false, $shopGroupId, $shopId)) { + return false; + } + } + } + + return true; + } + + private function resolveClientId(array $config, int $shopGroupId, int $shopId): ?string + { + return $config[$shopGroupId][$shopId]['INPOST_PAY_client_id'] + ?? $config[$shopGroupId][0]['INPOST_PAY_client_id'] + ?? $config[0][0]['INPOST_PAY_client_id'] + ?? null; + } + + private function decodePaymentTypesList(string $value): array + { + $data = json_decode($value, true); + + if (!is_array($data)) { + return []; + } + + return array_filter(array_map([PaymentType::class, 'tryFrom'], $data)); + } +} + +/** + * @param InPostIzi $module + */ +function upgrade_module_2_2_2(Module $module): bool +{ + return InPostIziUpdater_2_2_2::create($module)->upgrade(); +} diff --git a/modules/inpostizi/vendor/.htaccess b/modules/inpostizi/vendor/.htaccess new file mode 100644 index 00000000..3de9e400 --- /dev/null +++ b/modules/inpostizi/vendor/.htaccess @@ -0,0 +1,10 @@ +# Apache 2.2 + + Order deny,allow + Deny from all + + +# Apache 2.4 + + Require all denied + diff --git a/modules/inpostizi/vendor/autoload.php b/modules/inpostizi/vendor/autoload.php new file mode 100644 index 00000000..9f0b58dc --- /dev/null +++ b/modules/inpostizi/vendor/autoload.php @@ -0,0 +1,25 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + /** @var \Closure(string):void */ + private static $includeFile; + + /** @var ?string */ + private $vendorDir; + + // PSR-4 + /** + * @var array[] + * @psalm-var array> + */ + private $prefixLengthsPsr4 = array(); + /** + * @var array[] + * @psalm-var array> + */ + private $prefixDirsPsr4 = array(); + /** + * @var array[] + * @psalm-var array + */ + private $fallbackDirsPsr4 = array(); + + // PSR-0 + /** + * @var array[] + * @psalm-var array> + */ + private $prefixesPsr0 = array(); + /** + * @var array[] + * @psalm-var array + */ + private $fallbackDirsPsr0 = array(); + + /** @var bool */ + private $useIncludePath = false; + + /** + * @var string[] + * @psalm-var array + */ + private $classMap = array(); + + /** @var bool */ + private $classMapAuthoritative = false; + + /** + * @var bool[] + * @psalm-var array + */ + private $missingClasses = array(); + + /** @var ?string */ + private $apcuPrefix; + + /** + * @var self[] + */ + private static $registeredLoaders = array(); + + /** + * @param ?string $vendorDir + */ + public function __construct($vendorDir = null) + { + $this->vendorDir = $vendorDir; + self::initializeIncludeClosure(); + } + + /** + * @return string[] + */ + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); + } + + return array(); + } + + /** + * @return array[] + * @psalm-return array> + */ + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + /** + * @return array[] + * @psalm-return array + */ + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + /** + * @return array[] + * @psalm-return array + */ + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + /** + * @return string[] Array of classname => path + * @psalm-return array + */ + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param string[] $classMap Class to filename map + * @psalm-param array $classMap + * + * @return void + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param string[]|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + * + * @return void + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param string[]|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param string[]|string $paths The PSR-0 base directories + * + * @return void + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param string[]|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + * + * @return void + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + * + * @return void + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + * + * @return void + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + * + * @return void + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + + if (null === $this->vendorDir) { + return; + } + + if ($prepend) { + self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; + } else { + unset(self::$registeredLoaders[$this->vendorDir]); + self::$registeredLoaders[$this->vendorDir] = $this; + } + } + + /** + * Unregisters this instance as an autoloader. + * + * @return void + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + + if (null !== $this->vendorDir) { + unset(self::$registeredLoaders[$this->vendorDir]); + } + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return true|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + $includeFile = self::$includeFile; + $includeFile($file); + + return true; + } + + return null; + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + /** + * Returns the currently registered loaders indexed by their corresponding vendor directories. + * + * @return self[] + */ + public static function getRegisteredLoaders() + { + return self::$registeredLoaders; + } + + /** + * @param string $class + * @param string $ext + * @return string|false + */ + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } + + /** + * @return void + */ + private static function initializeIncludeClosure() + { + if (self::$includeFile !== null) { + return; + } + + /** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + */ + self::$includeFile = \Closure::bind(static function($file) { + include $file; + }, null, null); + } +} diff --git a/modules/inpostizi/vendor/composer/InstalledVersions.php b/modules/inpostizi/vendor/composer/InstalledVersions.php new file mode 100644 index 00000000..c6b54af7 --- /dev/null +++ b/modules/inpostizi/vendor/composer/InstalledVersions.php @@ -0,0 +1,352 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Autoload\ClassLoader; +use Composer\Semver\VersionParser; + +/** + * This class is copied in every Composer installed project and available to all + * + * See also https://getcomposer.org/doc/07-runtime.md#installed-versions + * + * To require its presence, you can require `composer-runtime-api ^2.0` + * + * @final + */ +class InstalledVersions +{ + /** + * @var mixed[]|null + * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null + */ + private static $installed; + + /** + * @var bool|null + */ + private static $canGetVendors; + + /** + * @var array[] + * @psalm-var array}> + */ + private static $installedByVendor = array(); + + /** + * Returns a list of all package names which are present, either by being installed, replaced or provided + * + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackages() + { + $packages = array(); + foreach (self::getInstalled() as $installed) { + $packages[] = array_keys($installed['versions']); + } + + if (1 === \count($packages)) { + return $packages[0]; + } + + return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); + } + + /** + * Returns a list of all package names with a specific type e.g. 'library' + * + * @param string $type + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackagesByType($type) + { + $packagesByType = array(); + + foreach (self::getInstalled() as $installed) { + foreach ($installed['versions'] as $name => $package) { + if (isset($package['type']) && $package['type'] === $type) { + $packagesByType[] = $name; + } + } + } + + return $packagesByType; + } + + /** + * Checks whether the given package is installed + * + * This also returns true if the package name is provided or replaced by another package + * + * @param string $packageName + * @param bool $includeDevRequirements + * @return bool + */ + public static function isInstalled($packageName, $includeDevRequirements = true) + { + foreach (self::getInstalled() as $installed) { + if (isset($installed['versions'][$packageName])) { + return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']); + } + } + + return false; + } + + /** + * Checks whether the given package satisfies a version constraint + * + * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: + * + * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') + * + * @param VersionParser $parser Install composer/semver to have access to this class and functionality + * @param string $packageName + * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package + * @return bool + */ + public static function satisfies(VersionParser $parser, $packageName, $constraint) + { + $constraint = $parser->parseConstraints($constraint); + $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + + return $provided->matches($constraint); + } + + /** + * Returns a version constraint representing all the range(s) which are installed for a given package + * + * It is easier to use this via isInstalled() with the $constraint argument if you need to check + * whether a given version of a package is installed, and not just whether it exists + * + * @param string $packageName + * @return string Version constraint usable with composer/semver + */ + public static function getVersionRanges($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + $ranges = array(); + if (isset($installed['versions'][$packageName]['pretty_version'])) { + $ranges[] = $installed['versions'][$packageName]['pretty_version']; + } + if (array_key_exists('aliases', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); + } + if (array_key_exists('replaced', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); + } + if (array_key_exists('provided', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); + } + + return implode(' || ', $ranges); + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['version'])) { + return null; + } + + return $installed['versions'][$packageName]['version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getPrettyVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['pretty_version'])) { + return null; + } + + return $installed['versions'][$packageName]['pretty_version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference + */ + public static function getReference($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['reference'])) { + return null; + } + + return $installed['versions'][$packageName]['reference']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. + */ + public static function getInstallPath($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @return array + * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} + */ + public static function getRootPackage() + { + $installed = self::getInstalled(); + + return $installed[0]['root']; + } + + /** + * Returns the raw installed.php data for custom implementations + * + * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. + * @return array[] + * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} + */ + public static function getRawData() + { + @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = include __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + + return self::$installed; + } + + /** + * Returns the raw data of all installed.php which are currently loaded for custom implementations + * + * @return array[] + * @psalm-return list}> + */ + public static function getAllRawData() + { + return self::getInstalled(); + } + + /** + * Lets you reload the static array from another file + * + * This is only useful for complex integrations in which a project needs to use + * this class but then also needs to execute another project's autoloader in process, + * and wants to ensure both projects have access to their version of installed.php. + * + * A typical case would be PHPUnit, where it would need to make sure it reads all + * the data it needs from this class, then call reload() with + * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure + * the project in which it runs can then also use this class safely, without + * interference between PHPUnit's dependencies and the project's dependencies. + * + * @param array[] $data A vendor/composer/installed.php data set + * @return void + * + * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data + */ + public static function reload($data) + { + self::$installed = $data; + self::$installedByVendor = array(); + } + + /** + * @return array[] + * @psalm-return list}> + */ + private static function getInstalled() + { + if (null === self::$canGetVendors) { + self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); + } + + $installed = array(); + + if (self::$canGetVendors) { + foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + if (isset(self::$installedByVendor[$vendorDir])) { + $installed[] = self::$installedByVendor[$vendorDir]; + } elseif (is_file($vendorDir.'/composer/installed.php')) { + $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php'; + if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { + self::$installed = $installed[count($installed) - 1]; + } + } + } + } + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = require __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + $installed[] = self::$installed; + + return $installed; + } +} diff --git a/modules/inpostizi/vendor/composer/LICENSE b/modules/inpostizi/vendor/composer/LICENSE new file mode 100644 index 00000000..f27399a0 --- /dev/null +++ b/modules/inpostizi/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/modules/inpostizi/vendor/composer/autoload_classmap.php b/modules/inpostizi/vendor/composer/autoload_classmap.php new file mode 100644 index 00000000..223f3850 --- /dev/null +++ b/modules/inpostizi/vendor/composer/autoload_classmap.php @@ -0,0 +1,897 @@ + $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', + 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', + 'Http\\Message\\MessageFactory' => $vendorDir . '/php-http/message-factory/src/MessageFactory.php', + 'Http\\Message\\RequestFactory' => $vendorDir . '/php-http/message-factory/src/RequestFactory.php', + 'Http\\Message\\ResponseFactory' => $vendorDir . '/php-http/message-factory/src/ResponseFactory.php', + 'Http\\Message\\StreamFactory' => $vendorDir . '/php-http/message-factory/src/StreamFactory.php', + 'Http\\Message\\UriFactory' => $vendorDir . '/php-http/message-factory/src/UriFactory.php', + 'MyCLabs\\Enum\\Enum' => $vendorDir . '/myclabs/php-enum/src/Enum.php', + 'MyCLabs\\Enum\\PHPUnit\\Comparator' => $vendorDir . '/myclabs/php-enum/src/PHPUnit/Comparator.php', + 'Nyholm\\Psr7\\Factory\\HttplugFactory' => $vendorDir . '/nyholm/psr7/src/Factory/HttplugFactory.php', + 'Nyholm\\Psr7\\Factory\\Psr17Factory' => $vendorDir . '/nyholm/psr7/src/Factory/Psr17Factory.php', + 'Nyholm\\Psr7\\MessageTrait' => $vendorDir . '/nyholm/psr7/src/MessageTrait.php', + 'Nyholm\\Psr7\\Request' => $vendorDir . '/nyholm/psr7/src/Request.php', + 'Nyholm\\Psr7\\RequestTrait' => $vendorDir . '/nyholm/psr7/src/RequestTrait.php', + 'Nyholm\\Psr7\\Response' => $vendorDir . '/nyholm/psr7/src/Response.php', + 'Nyholm\\Psr7\\ServerRequest' => $vendorDir . '/nyholm/psr7/src/ServerRequest.php', + 'Nyholm\\Psr7\\Stream' => $vendorDir . '/nyholm/psr7/src/Stream.php', + 'Nyholm\\Psr7\\UploadedFile' => $vendorDir . '/nyholm/psr7/src/UploadedFile.php', + 'Nyholm\\Psr7\\Uri' => $vendorDir . '/nyholm/psr7/src/Uri.php', + 'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', + 'Psr\\Clock\\ClockInterface' => $vendorDir . '/psr/clock/src/ClockInterface.php', + 'Psr\\Container\\ContainerExceptionInterface' => $vendorDir . '/psr/container/src/ContainerExceptionInterface.php', + 'Psr\\Container\\ContainerInterface' => $vendorDir . '/psr/container/src/ContainerInterface.php', + 'Psr\\Container\\NotFoundExceptionInterface' => $vendorDir . '/psr/container/src/NotFoundExceptionInterface.php', + 'Psr\\Http\\Client\\ClientExceptionInterface' => $vendorDir . '/psr/http-client/src/ClientExceptionInterface.php', + 'Psr\\Http\\Client\\ClientInterface' => $vendorDir . '/psr/http-client/src/ClientInterface.php', + 'Psr\\Http\\Client\\NetworkExceptionInterface' => $vendorDir . '/psr/http-client/src/NetworkExceptionInterface.php', + 'Psr\\Http\\Client\\RequestExceptionInterface' => $vendorDir . '/psr/http-client/src/RequestExceptionInterface.php', + 'Psr\\Http\\Message\\MessageInterface' => $vendorDir . '/psr/http-message/src/MessageInterface.php', + 'Psr\\Http\\Message\\RequestFactoryInterface' => $vendorDir . '/psr/http-factory/src/RequestFactoryInterface.php', + 'Psr\\Http\\Message\\RequestInterface' => $vendorDir . '/psr/http-message/src/RequestInterface.php', + 'Psr\\Http\\Message\\ResponseFactoryInterface' => $vendorDir . '/psr/http-factory/src/ResponseFactoryInterface.php', + 'Psr\\Http\\Message\\ResponseInterface' => $vendorDir . '/psr/http-message/src/ResponseInterface.php', + 'Psr\\Http\\Message\\ServerRequestFactoryInterface' => $vendorDir . '/psr/http-factory/src/ServerRequestFactoryInterface.php', + 'Psr\\Http\\Message\\ServerRequestInterface' => $vendorDir . '/psr/http-message/src/ServerRequestInterface.php', + 'Psr\\Http\\Message\\StreamFactoryInterface' => $vendorDir . '/psr/http-factory/src/StreamFactoryInterface.php', + 'Psr\\Http\\Message\\StreamInterface' => $vendorDir . '/psr/http-message/src/StreamInterface.php', + 'Psr\\Http\\Message\\UploadedFileFactoryInterface' => $vendorDir . '/psr/http-factory/src/UploadedFileFactoryInterface.php', + 'Psr\\Http\\Message\\UploadedFileInterface' => $vendorDir . '/psr/http-message/src/UploadedFileInterface.php', + 'Psr\\Http\\Message\\UriFactoryInterface' => $vendorDir . '/psr/http-factory/src/UriFactoryInterface.php', + 'Psr\\Http\\Message\\UriInterface' => $vendorDir . '/psr/http-message/src/UriInterface.php', + 'Psr\\SimpleCache\\CacheException' => $vendorDir . '/psr/simple-cache/src/CacheException.php', + 'Psr\\SimpleCache\\CacheInterface' => $vendorDir . '/psr/simple-cache/src/CacheInterface.php', + 'Psr\\SimpleCache\\InvalidArgumentException' => $vendorDir . '/psr/simple-cache/src/InvalidArgumentException.php', + 'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', + 'Symfony\\Contracts\\Service\\ResetInterface' => $vendorDir . '/symfony/service-contracts/ResetInterface.php', + 'Symfony\\Contracts\\Service\\ServiceLocatorTrait' => $vendorDir . '/symfony/service-contracts/ServiceLocatorTrait.php', + 'Symfony\\Contracts\\Service\\ServiceProviderInterface' => $vendorDir . '/symfony/service-contracts/ServiceProviderInterface.php', + 'Symfony\\Contracts\\Service\\ServiceSubscriberInterface' => $vendorDir . '/symfony/service-contracts/ServiceSubscriberInterface.php', + 'Symfony\\Contracts\\Service\\ServiceSubscriberTrait' => $vendorDir . '/symfony/service-contracts/ServiceSubscriberTrait.php', + 'Symfony\\Contracts\\Service\\Test\\ServiceLocatorTest' => $vendorDir . '/symfony/service-contracts/Test/ServiceLocatorTest.php', + 'Symfony\\Polyfill\\Mbstring\\Mbstring' => $vendorDir . '/symfony/polyfill-mbstring/Mbstring.php', + 'Symfony\\Polyfill\\Php80\\Php80' => $vendorDir . '/symfony/polyfill-php80/Php80.php', + 'Symfony\\Polyfill\\Php80\\PhpToken' => $vendorDir . '/symfony/polyfill-php80/PhpToken.php', + 'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', + 'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', + 'ZipStream\\Bigint' => $vendorDir . '/maennchen/zipstream-php/src/Bigint.php', + 'ZipStream\\DeflateStream' => $vendorDir . '/maennchen/zipstream-php/src/DeflateStream.php', + 'ZipStream\\Exception' => $vendorDir . '/maennchen/zipstream-php/src/Exception.php', + 'ZipStream\\Exception\\EncodingException' => $vendorDir . '/maennchen/zipstream-php/src/Exception/EncodingException.php', + 'ZipStream\\Exception\\FileNotFoundException' => $vendorDir . '/maennchen/zipstream-php/src/Exception/FileNotFoundException.php', + 'ZipStream\\Exception\\FileNotReadableException' => $vendorDir . '/maennchen/zipstream-php/src/Exception/FileNotReadableException.php', + 'ZipStream\\Exception\\IncompatibleOptionsException' => $vendorDir . '/maennchen/zipstream-php/src/Exception/IncompatibleOptionsException.php', + 'ZipStream\\Exception\\OverflowException' => $vendorDir . '/maennchen/zipstream-php/src/Exception/OverflowException.php', + 'ZipStream\\Exception\\StreamNotReadableException' => $vendorDir . '/maennchen/zipstream-php/src/Exception/StreamNotReadableException.php', + 'ZipStream\\File' => $vendorDir . '/maennchen/zipstream-php/src/File.php', + 'ZipStream\\Option\\Archive' => $vendorDir . '/maennchen/zipstream-php/src/Option/Archive.php', + 'ZipStream\\Option\\File' => $vendorDir . '/maennchen/zipstream-php/src/Option/File.php', + 'ZipStream\\Option\\Method' => $vendorDir . '/maennchen/zipstream-php/src/Option/Method.php', + 'ZipStream\\Option\\Version' => $vendorDir . '/maennchen/zipstream-php/src/Option/Version.php', + 'ZipStream\\Stream' => $vendorDir . '/maennchen/zipstream-php/src/Stream.php', + 'ZipStream\\ZipStream' => $vendorDir . '/maennchen/zipstream-php/src/ZipStream.php', + 'izi\\prestashop\\AdminKernel' => $baseDir . '/src/AdminKernel.php', + 'izi\\prestashop\\Analytics\\BasketAnalytics' => $baseDir . '/src/Analytics/BasketAnalytics.php', + 'izi\\prestashop\\Analytics\\BasketAnalyticsInterface' => $baseDir . '/src/Analytics/BasketAnalyticsInterface.php', + 'izi\\prestashop\\Analytics\\BasketAnalyticsParams' => $baseDir . '/src/Analytics/BasketAnalyticsParams.php', + 'izi\\prestashop\\Analytics\\BasketAnalyticsRepository' => $baseDir . '/src/Analytics/BasketAnalyticsRepository.php', + 'izi\\prestashop\\Analytics\\BasketAnalyticsRepositoryInterface' => $baseDir . '/src/Analytics/BasketAnalyticsRepositoryInterface.php', + 'izi\\prestashop\\Analytics\\Command\\UpdateCartAnalyticsCommand' => $baseDir . '/src/Analytics/Command/UpdateCartAnalyticsCommand.php', + 'izi\\prestashop\\Analytics\\Cookie\\CookieEraserInterface' => $baseDir . '/src/Analytics/Cookie/CookieEraserInterface.php', + 'izi\\prestashop\\Analytics\\Cookie\\CookieExtractorInterface' => $baseDir . '/src/Analytics/Cookie/CookieExtractorInterface.php', + 'izi\\prestashop\\Analytics\\Cookie\\CookiePersisterInterface' => $baseDir . '/src/Analytics/Cookie/CookiePersisterInterface.php', + 'izi\\prestashop\\Analytics\\Cookie\\Executor\\CookieEraseExecutor' => $baseDir . '/src/Analytics/Cookie/Executor/CookieEraseExecutor.php', + 'izi\\prestashop\\Analytics\\Cookie\\Executor\\CookiePersisterExecutor' => $baseDir . '/src/Analytics/Cookie/Executor/CookiePersisterExecutor.php', + 'izi\\prestashop\\Analytics\\Cookie\\FacebookClickIdCookie' => $baseDir . '/src/Analytics/Cookie/FacebookClickIdCookie.php', + 'izi\\prestashop\\Analytics\\Cookie\\Factory\\CookieFactory' => $baseDir . '/src/Analytics/Cookie/Factory/CookieFactory.php', + 'izi\\prestashop\\Analytics\\Cookie\\Factory\\CookieFactoryInterface' => $baseDir . '/src/Analytics/Cookie/Factory/CookieFactoryInterface.php', + 'izi\\prestashop\\Analytics\\Cookie\\GoogleClickIdCookie' => $baseDir . '/src/Analytics/Cookie/GoogleClickIdCookie.php', + 'izi\\prestashop\\Analytics\\Cookie\\GoogleClientIdCookie' => $baseDir . '/src/Analytics/Cookie/GoogleClientIdCookie.php', + 'izi\\prestashop\\Analytics\\Cookie\\Repository\\CookieRepository' => $baseDir . '/src/Analytics/Cookie/Repository/CookieRepository.php', + 'izi\\prestashop\\Analytics\\Cookie\\Repository\\CookieRepositoryInterface' => $baseDir . '/src/Analytics/Cookie/Repository/CookieRepositoryInterface.php', + 'izi\\prestashop\\Analytics\\EventListener\\UpdateBasketAnalyticsListener' => $baseDir . '/src/Analytics/EventListener/UpdateBasketAnalyticsListener.php', + 'izi\\prestashop\\Analytics\\Factory\\BasketAnalyticsFactory' => $baseDir . '/src/Analytics/Factory/BasketAnalyticsFactory.php', + 'izi\\prestashop\\Analytics\\Factory\\BasketAnalyticsFactoryInterface' => $baseDir . '/src/Analytics/Factory/BasketAnalyticsFactoryInterface.php', + 'izi\\prestashop\\Analytics\\Handler\\UpdateCartAnalyticsHandler' => $baseDir . '/src/Analytics/Handler/UpdateCartAnalyticsHandler.php', + 'izi\\prestashop\\Analytics\\Handler\\UpdateCartAnalyticsHandlerInterface' => $baseDir . '/src/Analytics/Handler/UpdateCartAnalyticsHandlerInterface.php', + 'izi\\prestashop\\BasketApp\\AuthorizationProviderFactory' => $baseDir . '/src/BasketApp/AuthorizationProviderFactory.php', + 'izi\\prestashop\\BasketApp\\BasketAppClient' => $baseDir . '/src/BasketApp/BasketAppClient.php', + 'izi\\prestashop\\BasketApp\\BasketAppClientFactory' => $baseDir . '/src/BasketApp/BasketAppClientFactory.php', + 'izi\\prestashop\\BasketApp\\BasketAppClientInterface' => $baseDir . '/src/BasketApp/BasketAppClientInterface.php', + 'izi\\prestashop\\BasketApp\\Basket\\BasketsApiClientInterface' => $baseDir . '/src/BasketApp/Basket/BasketsApiClientInterface.php', + 'izi\\prestashop\\BasketApp\\Basket\\Request\\Basket' => $baseDir . '/src/BasketApp/Basket/Request/Basket.php', + 'izi\\prestashop\\BasketApp\\Basket\\Response\\BasketBindingKeyResponse' => $baseDir . '/src/BasketApp/Basket/Response/BasketBindingKeyResponse.php', + 'izi\\prestashop\\BasketApp\\Basket\\Response\\BasketBindingResponse' => $baseDir . '/src/BasketApp/Basket/Response/BasketBindingResponse.php', + 'izi\\prestashop\\BasketApp\\Basket\\Response\\ClientDetails' => $baseDir . '/src/BasketApp/Basket/Response/ClientDetails.php', + 'izi\\prestashop\\BasketApp\\Basket\\Response\\UpdateBasketResponse' => $baseDir . '/src/BasketApp/Basket/Response/UpdateBasketResponse.php', + 'izi\\prestashop\\BasketApp\\Exception\\BadRequestException' => $baseDir . '/src/BasketApp/Exception/BadRequestException.php', + 'izi\\prestashop\\BasketApp\\Exception\\BasketAlreadyBoundException' => $baseDir . '/src/BasketApp/Exception/BasketAlreadyBoundException.php', + 'izi\\prestashop\\BasketApp\\Exception\\BasketAppException' => $baseDir . '/src/BasketApp/Exception/BasketAppException.php', + 'izi\\prestashop\\BasketApp\\Exception\\BasketExpiredException' => $baseDir . '/src/BasketApp/Exception/BasketExpiredException.php', + 'izi\\prestashop\\BasketApp\\Exception\\BasketNotBoundException' => $baseDir . '/src/BasketApp/Exception/BasketNotBoundException.php', + 'izi\\prestashop\\BasketApp\\Exception\\BasketNotFoundException' => $baseDir . '/src/BasketApp/Exception/BasketNotFoundException.php', + 'izi\\prestashop\\BasketApp\\Exception\\CannotChangeOrderStatusException' => $baseDir . '/src/BasketApp/Exception/CannotChangeOrderStatusException.php', + 'izi\\prestashop\\BasketApp\\Exception\\ForbiddenException' => $baseDir . '/src/BasketApp/Exception/ForbiddenException.php', + 'izi\\prestashop\\BasketApp\\Exception\\InternalServerErrorException' => $baseDir . '/src/BasketApp/Exception/InternalServerErrorException.php', + 'izi\\prestashop\\BasketApp\\Exception\\MalformedRequestException' => $baseDir . '/src/BasketApp/Exception/MalformedRequestException.php', + 'izi\\prestashop\\BasketApp\\Exception\\MerchantDisabledException' => $baseDir . '/src/BasketApp/Exception/MerchantDisabledException.php', + 'izi\\prestashop\\BasketApp\\Exception\\OrderNotFoundException' => $baseDir . '/src/BasketApp/Exception/OrderNotFoundException.php', + 'izi\\prestashop\\BasketApp\\Exception\\PhoneBindingUnavailableException' => $baseDir . '/src/BasketApp/Exception/PhoneBindingUnavailableException.php', + 'izi\\prestashop\\BasketApp\\Exception\\PublicKeyNotFoundException' => $baseDir . '/src/BasketApp/Exception/PublicKeyNotFoundException.php', + 'izi\\prestashop\\BasketApp\\Exception\\ResourceNotFoundException' => $baseDir . '/src/BasketApp/Exception/ResourceNotFoundException.php', + 'izi\\prestashop\\BasketApp\\Exception\\UnauthorizedException' => $baseDir . '/src/BasketApp/Exception/UnauthorizedException.php', + 'izi\\prestashop\\BasketApp\\Order\\OrdersApiClientInterface' => $baseDir . '/src/BasketApp/Order/OrdersApiClientInterface.php', + 'izi\\prestashop\\BasketApp\\Order\\Request\\Delivery' => $baseDir . '/src/BasketApp/Order/Request/Delivery.php', + 'izi\\prestashop\\BasketApp\\Order\\Request\\OrderEvent' => $baseDir . '/src/BasketApp/Order/Request/OrderEvent.php', + 'izi\\prestashop\\BasketApp\\Order\\Request\\OrderEventData' => $baseDir . '/src/BasketApp/Order/Request/OrderEventData.php', + 'izi\\prestashop\\BasketApp\\PaginationPage' => $baseDir . '/src/BasketApp/PaginationPage.php', + 'izi\\prestashop\\BasketApp\\Payment\\PaymentsApiClientInterface' => $baseDir . '/src/BasketApp/Payment/PaymentsApiClientInterface.php', + 'izi\\prestashop\\BasketApp\\Payment\\Response\\AvailablePaymentOptions' => $baseDir . '/src/BasketApp/Payment/Response/AvailablePaymentOptions.php', + 'izi\\prestashop\\BasketApp\\Product\\Exception\\MaxProductLimitReachedException' => $baseDir . '/src/BasketApp/Product/Exception/MaxProductLimitReachedException.php', + 'izi\\prestashop\\BasketApp\\Product\\Exception\\ProductExistsException' => $baseDir . '/src/BasketApp/Product/Exception/ProductExistsException.php', + 'izi\\prestashop\\BasketApp\\Product\\Exception\\ProductNotFoundException' => $baseDir . '/src/BasketApp/Product/Exception/ProductNotFoundException.php', + 'izi\\prestashop\\BasketApp\\Product\\ProductsApiClientInterface' => $baseDir . '/src/BasketApp/Product/ProductsApiClientInterface.php', + 'izi\\prestashop\\BasketApp\\Product\\Request\\CreateProductsRequest' => $baseDir . '/src/BasketApp/Product/Request/CreateProductsRequest.php', + 'izi\\prestashop\\BasketApp\\Product\\Response\\CreateProductsResponse' => $baseDir . '/src/BasketApp/Product/Response/CreateProductsResponse.php', + 'izi\\prestashop\\BasketApp\\Product\\Response\\Product' => $baseDir . '/src/BasketApp/Product/Response/Product.php', + 'izi\\prestashop\\BasketApp\\Product\\Response\\ProductId' => $baseDir . '/src/BasketApp/Product/Response/ProductId.php', + 'izi\\prestashop\\BasketApp\\Product\\Response\\Status' => $baseDir . '/src/BasketApp/Product/Response/Status.php', + 'izi\\prestashop\\BasketApp\\Signature\\Response\\PublicKey' => $baseDir . '/src/BasketApp/Signature/Response/PublicKey.php', + 'izi\\prestashop\\BasketApp\\Signature\\Response\\SigningKey' => $baseDir . '/src/BasketApp/Signature/Response/SigningKey.php', + 'izi\\prestashop\\BasketApp\\Signature\\Response\\SigningKeys' => $baseDir . '/src/BasketApp/Signature/Response/SigningKeys.php', + 'izi\\prestashop\\BasketApp\\Signature\\SigningKeysApiClientInterface' => $baseDir . '/src/BasketApp/Signature/SigningKeysApiClientInterface.php', + 'izi\\prestashop\\Builder\\Basket\\AbstractBasketBuilder' => $baseDir . '/src/Builder/Basket/AbstractBasketBuilder.php', + 'izi\\prestashop\\Builder\\Basket\\BasketAppRequestBuilder' => $baseDir . '/src/Builder/Basket/BasketAppRequestBuilder.php', + 'izi\\prestashop\\Builder\\Basket\\BasketAppRequestBuilderInterface' => $baseDir . '/src/Builder/Basket/BasketAppRequestBuilderInterface.php', + 'izi\\prestashop\\Builder\\Basket\\BasketBuilderFactory' => $baseDir . '/src/Builder/Basket/BasketBuilderFactory.php', + 'izi\\prestashop\\Builder\\Basket\\BasketBuilderFactoryInterface' => $baseDir . '/src/Builder/Basket/BasketBuilderFactoryInterface.php', + 'izi\\prestashop\\Builder\\Basket\\BasketBuilderInterface' => $baseDir . '/src/Builder/Basket/BasketBuilderInterface.php', + 'izi\\prestashop\\Builder\\Basket\\DeliveryFactory' => $baseDir . '/src/Builder/Basket/DeliveryFactory.php', + 'izi\\prestashop\\Builder\\Basket\\MerchantApiResponseBuilder' => $baseDir . '/src/Builder/Basket/MerchantApiResponseBuilder.php', + 'izi\\prestashop\\Builder\\Basket\\MerchantApiResponseBuilderInterface' => $baseDir . '/src/Builder/Basket/MerchantApiResponseBuilderInterface.php', + 'izi\\prestashop\\Builder\\Basket\\ProductDeliveryFactory' => $baseDir . '/src/Builder/Basket/ProductDeliveryFactory.php', + 'izi\\prestashop\\Builder\\Order\\OrderEventBuilder' => $baseDir . '/src/Builder/Order/OrderEventBuilder.php', + 'izi\\prestashop\\Builder\\Order\\OrderEventBuilderFactory' => $baseDir . '/src/Builder/Order/OrderEventBuilderFactory.php', + 'izi\\prestashop\\Builder\\Order\\OrderEventBuilderFactoryInterface' => $baseDir . '/src/Builder/Order/OrderEventBuilderFactoryInterface.php', + 'izi\\prestashop\\Builder\\Order\\OrderEventBuilderInterface' => $baseDir . '/src/Builder/Order/OrderEventBuilderInterface.php', + 'izi\\prestashop\\Builder\\Order\\OrderStatusDescriptionProvider' => $baseDir . '/src/Builder/Order/OrderStatusDescriptionProvider.php', + 'izi\\prestashop\\Builder\\PriceFactory' => $baseDir . '/src/Builder/PriceFactory.php', + 'izi\\prestashop\\CacheClearer\\BindingKeysCacheClearer' => $baseDir . '/src/CacheClearer/BindingKeysCacheClearer.php', + 'izi\\prestashop\\CacheClearer\\CacheClearerInterface' => $baseDir . '/src/CacheClearer/CacheClearerInterface.php', + 'izi\\prestashop\\CacheClearer\\ChainCacheClearer' => $baseDir . '/src/CacheClearer/ChainCacheClearer.php', + 'izi\\prestashop\\CacheClearer\\Psr16CacheClearer' => $baseDir . '/src/CacheClearer/Psr16CacheClearer.php', + 'izi\\prestashop\\Cache\\ConfigurationCache' => $baseDir . '/src/Cache/ConfigurationCache.php', + 'izi\\prestashop\\Cache\\Exception\\InvalidArgumentException' => $baseDir . '/src/Cache/Exception/InvalidArgumentException.php', + 'izi\\prestashop\\Cache\\Exception\\RuntimeException' => $baseDir . '/src/Cache/Exception/RuntimeException.php', + 'izi\\prestashop\\Cart\\Exception\\ProductAlreadyInCartException' => $baseDir . '/src/Cart/Exception/ProductAlreadyInCartException.php', + 'izi\\prestashop\\Cart\\Util\\ProductHelper' => $baseDir . '/src/Cart/Util/ProductHelper.php', + 'izi\\prestashop\\Clock\\SystemClock' => $baseDir . '/src/Clock/SystemClock.php', + 'izi\\prestashop\\CommandBus' => $baseDir . '/src/CommandBus.php', + 'izi\\prestashop\\CommandBusInterface' => $baseDir . '/src/CommandBusInterface.php', + 'izi\\prestashop\\Command\\Config\\CheckStatusCommand' => $baseDir . '/src/Command/Config/CheckStatusCommand.php', + 'izi\\prestashop\\Command\\Config\\DownloadModuleDataCommand' => $baseDir . '/src/Command/Config/DownloadModuleDataCommand.php', + 'izi\\prestashop\\Command\\Config\\UpdateAdvancedConfigurationCommand' => $baseDir . '/src/Command/Config/UpdateAdvancedConfigurationCommand.php', + 'izi\\prestashop\\Command\\Config\\UpdateCartRuleOptionsCommand' => $baseDir . '/src/Command/Config/UpdateCartRuleOptionsCommand.php', + 'izi\\prestashop\\Command\\Config\\UpdateConsentsConfigurationCommand' => $baseDir . '/src/Command/Config/UpdateConsentsConfigurationCommand.php', + 'izi\\prestashop\\Command\\Config\\UpdateGeneralConfigurationCommand' => $baseDir . '/src/Command/Config/UpdateGeneralConfigurationCommand.php', + 'izi\\prestashop\\Command\\Config\\UpdateGeneralConfigurationCommandFactory' => $baseDir . '/src/Command/Config/UpdateGeneralConfigurationCommandFactory.php', + 'izi\\prestashop\\Command\\Config\\UpdateGuiConfigurationCommand' => $baseDir . '/src/Command/Config/UpdateGuiConfigurationCommand.php', + 'izi\\prestashop\\Command\\Config\\UpdateShippingConfigurationCommand' => $baseDir . '/src/Command/Config/UpdateShippingConfigurationCommand.php', + 'izi\\prestashop\\Command\\GetBasketBindingKeyCommand' => $baseDir . '/src/Command/GetBasketBindingKeyCommand.php', + 'izi\\prestashop\\Command\\GetOrderConfirmationUrlCommand' => $baseDir . '/src/Command/GetOrderConfirmationUrlCommand.php', + 'izi\\prestashop\\Command\\UnbindBasketCommand' => $baseDir . '/src/Command/UnbindBasketCommand.php', + 'izi\\prestashop\\Command\\UpdateBasketCommand' => $baseDir . '/src/Command/UpdateBasketCommand.php', + 'izi\\prestashop\\Command\\UpdateOrderAddressDeliveryCommand' => $baseDir . '/src/Command/UpdateOrderAddressDeliveryCommand.php', + 'izi\\prestashop\\Command\\UpdateOrderStatusCommand' => $baseDir . '/src/Command/UpdateOrderStatusCommand.php', + 'izi\\prestashop\\Command\\UpdateOrderTrackingNumbersCommand' => $baseDir . '/src/Command/UpdateOrderTrackingNumbersCommand.php', + 'izi\\prestashop\\Common\\Basket\\AvailablePromotion' => $baseDir . '/src/Common/Basket/AvailablePromotion.php', + 'izi\\prestashop\\Common\\Basket\\Consent' => $baseDir . '/src/Common/Basket/Consent.php', + 'izi\\prestashop\\Common\\Basket\\ConsentLink' => $baseDir . '/src/Common/Basket/ConsentLink.php', + 'izi\\prestashop\\Common\\Basket\\ConsentRequirementType' => $baseDir . '/src/Common/Basket/ConsentRequirementType.php', + 'izi\\prestashop\\Common\\Basket\\DeliveryOption' => $baseDir . '/src/Common/Basket/DeliveryOption.php', + 'izi\\prestashop\\Common\\Basket\\Notice' => $baseDir . '/src/Common/Basket/Notice.php', + 'izi\\prestashop\\Common\\Basket\\NoticeType' => $baseDir . '/src/Common/Basket/NoticeType.php', + 'izi\\prestashop\\Common\\Basket\\Product' => $baseDir . '/src/Common/Basket/Product.php', + 'izi\\prestashop\\Common\\Basket\\PromoDetails' => $baseDir . '/src/Common/Basket/PromoDetails.php', + 'izi\\prestashop\\Common\\Basket\\PromotionType' => $baseDir . '/src/Common/Basket/PromotionType.php', + 'izi\\prestashop\\Common\\Basket\\Quantity' => $baseDir . '/src/Common/Basket/Quantity.php', + 'izi\\prestashop\\Common\\Basket\\Summary' => $baseDir . '/src/Common/Basket/Summary.php', + 'izi\\prestashop\\Common\\BindingPlace' => $baseDir . '/src/Common/BindingPlace.php', + 'izi\\prestashop\\Common\\Currency' => $baseDir . '/src/Common/Currency.php', + 'izi\\prestashop\\Common\\Customer\\AccountInfo' => $baseDir . '/src/Common/Customer/AccountInfo.php', + 'izi\\prestashop\\Common\\Customer\\ClientAddress' => $baseDir . '/src/Common/Customer/ClientAddress.php', + 'izi\\prestashop\\Common\\Customer\\InvoiceDetails' => $baseDir . '/src/Common/Customer/InvoiceDetails.php', + 'izi\\prestashop\\Common\\Customer\\LegalForm' => $baseDir . '/src/Common/Customer/LegalForm.php', + 'izi\\prestashop\\Common\\Delivery\\DeliveryType' => $baseDir . '/src/Common/Delivery/DeliveryType.php', + 'izi\\prestashop\\Common\\Delivery\\OptionalService' => $baseDir . '/src/Common/Delivery/OptionalService.php', + 'izi\\prestashop\\Common\\Delivery\\ServiceCode' => $baseDir . '/src/Common/Delivery/ServiceCode.php', + 'izi\\prestashop\\Common\\Dimensions' => $baseDir . '/src/Common/Dimensions.php', + 'izi\\prestashop\\Common\\Error\\Error' => $baseDir . '/src/Common/Error/Error.php', + 'izi\\prestashop\\Common\\HotProduct\\IdentifiableProduct' => $baseDir . '/src/Common/HotProduct/IdentifiableProduct.php', + 'izi\\prestashop\\Common\\HotProduct\\Product' => $baseDir . '/src/Common/HotProduct/Product.php', + 'izi\\prestashop\\Common\\HotProduct\\ProductAvailability' => $baseDir . '/src/Common/HotProduct/ProductAvailability.php', + 'izi\\prestashop\\Common\\HotProduct\\ProductTrait' => $baseDir . '/src/Common/HotProduct/ProductTrait.php', + 'izi\\prestashop\\Common\\HotProduct\\Quantity' => $baseDir . '/src/Common/HotProduct/Quantity.php', + 'izi\\prestashop\\Common\\Order\\Consent' => $baseDir . '/src/Common/Order/Consent.php', + 'izi\\prestashop\\Common\\Order\\DeliveryAddress' => $baseDir . '/src/Common/Order/DeliveryAddress.php', + 'izi\\prestashop\\Common\\Order\\MerchantOrderStatus' => $baseDir . '/src/Common/Order/MerchantOrderStatus.php', + 'izi\\prestashop\\Common\\Order\\OrderAdditionalParameter' => $baseDir . '/src/Common/Order/OrderAdditionalParameter.php', + 'izi\\prestashop\\Common\\Order\\OrderAdditionalParameters' => $baseDir . '/src/Common/Order/OrderAdditionalParameters.php', + 'izi\\prestashop\\Common\\Order\\Product' => $baseDir . '/src/Common/Order/Product.php', + 'izi\\prestashop\\Common\\Order\\Quantity' => $baseDir . '/src/Common/Order/Quantity.php', + 'izi\\prestashop\\Common\\PaymentType' => $baseDir . '/src/Common/PaymentType.php', + 'izi\\prestashop\\Common\\PhoneNumber' => $baseDir . '/src/Common/PhoneNumber.php', + 'izi\\prestashop\\Common\\Price' => $baseDir . '/src/Common/Price.php', + 'izi\\prestashop\\Common\\PriceAmount' => $baseDir . '/src/Common/PriceAmount.php', + 'izi\\prestashop\\Common\\Product\\DeliveryProduct' => $baseDir . '/src/Common/Product/DeliveryProduct.php', + 'izi\\prestashop\\Common\\Product\\DeliveryRelatedProducts' => $baseDir . '/src/Common/Product/DeliveryRelatedProducts.php', + 'izi\\prestashop\\Common\\Product\\ProductAttribute' => $baseDir . '/src/Common/Product/ProductAttribute.php', + 'izi\\prestashop\\Common\\Product\\ProductImage' => $baseDir . '/src/Common/Product/ProductImage.php', + 'izi\\prestashop\\Common\\Product\\ProductVariant' => $baseDir . '/src/Common/Product/ProductVariant.php', + 'izi\\prestashop\\Common\\PromoCode' => $baseDir . '/src/Common/PromoCode.php', + 'izi\\prestashop\\Common\\QuantityType' => $baseDir . '/src/Common/QuantityType.php', + 'izi\\prestashop\\Common\\Weight' => $baseDir . '/src/Common/Weight.php', + 'izi\\prestashop\\Configuration\\Adapter\\Configuration' => $baseDir . '/src/Configuration/Adapter/Configuration.php', + 'izi\\prestashop\\Configuration\\AdvancedConfiguration' => $baseDir . '/src/Configuration/AdvancedConfiguration.php', + 'izi\\prestashop\\Configuration\\AdvancedConfigurationInterface' => $baseDir . '/src/Configuration/AdvancedConfigurationInterface.php', + 'izi\\prestashop\\Configuration\\ApiConfiguration' => $baseDir . '/src/Configuration/ApiConfiguration.php', + 'izi\\prestashop\\Configuration\\ApiConfigurationInterface' => $baseDir . '/src/Configuration/ApiConfigurationInterface.php', + 'izi\\prestashop\\Configuration\\ConfigurationInterface' => $baseDir . '/src/Configuration/ConfigurationInterface.php', + 'izi\\prestashop\\Configuration\\ConsentsConfiguration' => $baseDir . '/src/Configuration/ConsentsConfiguration.php', + 'izi\\prestashop\\Configuration\\ConsentsConfigurationInterface' => $baseDir . '/src/Configuration/ConsentsConfigurationInterface.php', + 'izi\\prestashop\\Configuration\\DTO\\AdvancedConfiguration' => $baseDir . '/src/Configuration/DTO/AdvancedConfiguration.php', + 'izi\\prestashop\\Configuration\\DTO\\ApiConfiguration' => $baseDir . '/src/Configuration/DTO/ApiConfiguration.php', + 'izi\\prestashop\\Configuration\\DTO\\Consent' => $baseDir . '/src/Configuration/DTO/Consent.php', + 'izi\\prestashop\\Configuration\\DTO\\ConsentLink' => $baseDir . '/src/Configuration/DTO/ConsentLink.php', + 'izi\\prestashop\\Configuration\\DTO\\GeneralConfiguration' => $baseDir . '/src/Configuration/DTO/GeneralConfiguration.php', + 'izi\\prestashop\\Configuration\\DTO\\GuiConfiguration' => $baseDir . '/src/Configuration/DTO/GuiConfiguration.php', + 'izi\\prestashop\\Configuration\\DTO\\HtmlStyles' => $baseDir . '/src/Configuration/DTO/HtmlStyles.php', + 'izi\\prestashop\\Configuration\\DTO\\Order\\MessageOptions' => $baseDir . '/src/Configuration/DTO/Order/MessageOptions.php', + 'izi\\prestashop\\Configuration\\DTO\\OrdersConfiguration' => $baseDir . '/src/Configuration/DTO/OrdersConfiguration.php', + 'izi\\prestashop\\Configuration\\DTO\\ProductConfiguration' => $baseDir . '/src/Configuration/DTO/ProductConfiguration.php', + 'izi\\prestashop\\Configuration\\DTO\\ProductPageDisplayConfiguration' => $baseDir . '/src/Configuration/DTO/ProductPageDisplayConfiguration.php', + 'izi\\prestashop\\Configuration\\DTO\\Product\\ProductRestrictions' => $baseDir . '/src/Configuration/DTO/Product/ProductRestrictions.php', + 'izi\\prestashop\\Configuration\\DTO\\Product\\ProductRestrictionsCache' => $baseDir . '/src/Configuration/DTO/Product/ProductRestrictionsCache.php', + 'izi\\prestashop\\Configuration\\DTO\\ShippingConfiguration' => $baseDir . '/src/Configuration/DTO/ShippingConfiguration.php', + 'izi\\prestashop\\Configuration\\DTO\\Shipping\\CarrierMapping' => $baseDir . '/src/Configuration/DTO/Shipping/CarrierMapping.php', + 'izi\\prestashop\\Configuration\\DTO\\Shipping\\ServiceOptions' => $baseDir . '/src/Configuration/DTO/Shipping/ServiceOptions.php', + 'izi\\prestashop\\Configuration\\DTO\\Shipping\\ShippingOptions' => $baseDir . '/src/Configuration/DTO/Shipping/ShippingOptions.php', + 'izi\\prestashop\\Configuration\\DTO\\Shipping\\TimeOfWeek' => $baseDir . '/src/Configuration/DTO/Shipping/TimeOfWeek.php', + 'izi\\prestashop\\Configuration\\DTO\\Shipping\\TimeOfWeekRange' => $baseDir . '/src/Configuration/DTO/Shipping/TimeOfWeekRange.php', + 'izi\\prestashop\\Configuration\\DTO\\Shipping\\WeekDay' => $baseDir . '/src/Configuration/DTO/Shipping/WeekDay.php', + 'izi\\prestashop\\Configuration\\DTO\\WidgetDisplayConfiguration' => $baseDir . '/src/Configuration/DTO/WidgetDisplayConfiguration.php', + 'izi\\prestashop\\Configuration\\GeneralConfiguration' => $baseDir . '/src/Configuration/GeneralConfiguration.php', + 'izi\\prestashop\\Configuration\\GeneralConfigurationInterface' => $baseDir . '/src/Configuration/GeneralConfigurationInterface.php', + 'izi\\prestashop\\Configuration\\GuiConfiguration' => $baseDir . '/src/Configuration/GuiConfiguration.php', + 'izi\\prestashop\\Configuration\\GuiConfigurationInterface' => $baseDir . '/src/Configuration/GuiConfigurationInterface.php', + 'izi\\prestashop\\Configuration\\Initializer\\AnnotationsConfigInitializer' => $baseDir . '/src/Configuration/Initializer/AnnotationsConfigInitializer.php', + 'izi\\prestashop\\Configuration\\Initializer\\AssetPackageInitializer' => $baseDir . '/src/Configuration/Initializer/AssetPackageInitializer.php', + 'izi\\prestashop\\Configuration\\Initializer\\ConfigurationInitializerInterface' => $baseDir . '/src/Configuration/Initializer/ConfigurationInitializerInterface.php', + 'izi\\prestashop\\Configuration\\Initializer\\TwigConfigInitializer' => $baseDir . '/src/Configuration/Initializer/TwigConfigInitializer.php', + 'izi\\prestashop\\Configuration\\LanguageAwareConfigurationInterface' => $baseDir . '/src/Configuration/LanguageAwareConfigurationInterface.php', + 'izi\\prestashop\\Configuration\\OrdersConfiguration' => $baseDir . '/src/Configuration/OrdersConfiguration.php', + 'izi\\prestashop\\Configuration\\OrdersConfigurationInterface' => $baseDir . '/src/Configuration/OrdersConfigurationInterface.php', + 'izi\\prestashop\\Configuration\\PersistentConfigurationInterface' => $baseDir . '/src/Configuration/PersistentConfigurationInterface.php', + 'izi\\prestashop\\Configuration\\PrestaShopConfiguration' => $baseDir . '/src/Configuration/PrestaShopConfiguration.php', + 'izi\\prestashop\\Configuration\\ProductAwareWidgetDisplayConfigurationInterface' => $baseDir . '/src/Configuration/ProductAwareWidgetDisplayConfigurationInterface.php', + 'izi\\prestashop\\Configuration\\ProductConfiguration' => $baseDir . '/src/Configuration/ProductConfiguration.php', + 'izi\\prestashop\\Configuration\\ProductConfigurationInterface' => $baseDir . '/src/Configuration/ProductConfigurationInterface.php', + 'izi\\prestashop\\Configuration\\ProductPageDisplayConfiguration' => $baseDir . '/src/Configuration/ProductPageDisplayConfiguration.php', + 'izi\\prestashop\\Configuration\\ProductRestrictionsConfigurationInterface' => $baseDir . '/src/Configuration/ProductRestrictionsConfigurationInterface.php', + 'izi\\prestashop\\Configuration\\ProductRestrictionsProviderInterface' => $baseDir . '/src/Configuration/ProductRestrictionsProviderInterface.php', + 'izi\\prestashop\\Configuration\\PromoCodesConfigurationInterface' => $baseDir . '/src/Configuration/PromoCodesConfigurationInterface.php', + 'izi\\prestashop\\Configuration\\ShippingConfiguration' => $baseDir . '/src/Configuration/ShippingConfiguration.php', + 'izi\\prestashop\\Configuration\\ShippingConfigurationInterface' => $baseDir . '/src/Configuration/ShippingConfigurationInterface.php', + 'izi\\prestashop\\Configuration\\ShopAwareConfigurationInterface' => $baseDir . '/src/Configuration/ShopAwareConfigurationInterface.php', + 'izi\\prestashop\\Configuration\\WidgetDisplayConfigurationInterface' => $baseDir . '/src/Configuration/WidgetDisplayConfigurationInterface.php', + 'izi\\prestashop\\ContextManager' => $baseDir . '/src/ContextManager.php', + 'izi\\prestashop\\Controller\\Admin\\AbstractConfigurationController' => $baseDir . '/src/Controller/Admin/AbstractConfigurationController.php', + 'izi\\prestashop\\Controller\\Admin\\AbstractController' => $baseDir . '/src/Controller/Admin/AbstractController.php', + 'izi\\prestashop\\Controller\\Admin\\ConfigurationController' => $baseDir . '/src/Controller/Admin/ConfigurationController.php', + 'izi\\prestashop\\Controller\\Admin\\HotProductController' => $baseDir . '/src/Controller/Admin/HotProductController.php', + 'izi\\prestashop\\Controller\\Api\\AbstractApiController' => $baseDir . '/src/Controller/Api/AbstractApiController.php', + 'izi\\prestashop\\Controller\\Api\\BasketController' => $baseDir . '/src/Controller/Api/BasketController.php', + 'izi\\prestashop\\Controller\\Api\\OrderController' => $baseDir . '/src/Controller/Api/OrderController.php', + 'izi\\prestashop\\Controller\\Api\\ProductController' => $baseDir . '/src/Controller/Api/ProductController.php', + 'izi\\prestashop\\Controller\\WidgetController' => $baseDir . '/src/Controller/WidgetController.php', + 'izi\\prestashop\\Currency\\PriceConverter' => $baseDir . '/src/Currency/PriceConverter.php', + 'izi\\prestashop\\Currency\\PriceConverterInterface' => $baseDir . '/src/Currency/PriceConverterInterface.php', + 'izi\\prestashop\\Database\\Connection' => $baseDir . '/src/Database/Connection.php', + 'izi\\prestashop\\DependencyInjection\\Argument\\ServiceClosureArgument' => $baseDir . '/src/DependencyInjection/Argument/ServiceClosureArgument.php', + 'izi\\prestashop\\DependencyInjection\\Compiler\\AnalyzeServiceReferencesPass' => $baseDir . '/src/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php', + 'izi\\prestashop\\DependencyInjection\\Compiler\\ProvideServiceLocatorFactoriesPass' => $baseDir . '/src/DependencyInjection/Compiler/ProvideServiceLocatorFactoriesPass.php', + 'izi\\prestashop\\DependencyInjection\\Compiler\\TaggedIteratorsCollectorPass' => $baseDir . '/src/DependencyInjection/Compiler/TaggedIteratorsCollectorPass.php', + 'izi\\prestashop\\DependencyInjection\\ContainerBuilder' => $baseDir . '/src/DependencyInjection/ContainerBuilder.php', + 'izi\\prestashop\\DependencyInjection\\ContainerFactory' => $baseDir . '/src/DependencyInjection/ContainerFactory.php', + 'izi\\prestashop\\DependencyInjection\\Dumper\\PhpDumper' => $baseDir . '/src/DependencyInjection/Dumper/PhpDumper.php', + 'izi\\prestashop\\DependencyInjection\\Exception\\ContainerNotFoundException' => $baseDir . '/src/DependencyInjection/Exception/ContainerNotFoundException.php', + 'izi\\prestashop\\DependencyInjection\\ServiceLocator' => $baseDir . '/src/DependencyInjection/ServiceLocator.php', + 'izi\\prestashop\\DependencyInjection\\ServiceSubscriberInterface' => $baseDir . '/src/DependencyInjection/ServiceSubscriberInterface.php', + 'izi\\prestashop\\DependencyInjection\\TypedReference' => $baseDir . '/src/DependencyInjection/TypedReference.php', + 'izi\\prestashop\\Entities\\BasketInterface' => $baseDir . '/src/Entities/BasketInterface.php', + 'izi\\prestashop\\Entities\\BasketSession' => $baseDir . '/src/Entities/BasketSession.php', + 'izi\\prestashop\\Entities\\BasketSessionInterface' => $baseDir . '/src/Entities/BasketSessionInterface.php', + 'izi\\prestashop\\Entities\\Cart' => $baseDir . '/src/Entities/Cart.php', + 'izi\\prestashop\\Entities\\CartProxy' => $baseDir . '/src/Entities/CartProxy.php', + 'izi\\prestashop\\Entities\\SwitchableBasketSessionInterface' => $baseDir . '/src/Entities/SwitchableBasketSessionInterface.php', + 'izi\\prestashop\\Enum\\Enum' => $baseDir . '/src/Enum/Enum.php', + 'izi\\prestashop\\Enum\\IntEnum' => $baseDir . '/src/Enum/IntEnum.php', + 'izi\\prestashop\\Enum\\StringEnum' => $baseDir . '/src/Enum/StringEnum.php', + 'izi\\prestashop\\Environment\\AuthServerUriCollection' => $baseDir . '/src/Environment/AuthServerUriCollection.php', + 'izi\\prestashop\\Environment\\EnvironmentFactory' => $baseDir . '/src/Environment/EnvironmentFactory.php', + 'izi\\prestashop\\Environment\\EnvironmentFactoryInterface' => $baseDir . '/src/Environment/EnvironmentFactoryInterface.php', + 'izi\\prestashop\\Environment\\EnvironmentInterface' => $baseDir . '/src/Environment/EnvironmentInterface.php', + 'izi\\prestashop\\Environment\\EnvironmentType' => $baseDir . '/src/Environment/EnvironmentType.php', + 'izi\\prestashop\\Environment\\ProductionEnvironment' => $baseDir . '/src/Environment/ProductionEnvironment.php', + 'izi\\prestashop\\Environment\\SandboxEnvironment' => $baseDir . '/src/Environment/SandboxEnvironment.php', + 'izi\\prestashop\\EventListener\\CartListener' => $baseDir . '/src/EventListener/CartListener.php', + 'izi\\prestashop\\EventListener\\CreateShipmentListener' => $baseDir . '/src/EventListener/CreateShipmentListener.php', + 'izi\\prestashop\\EventListener\\OrderListener' => $baseDir . '/src/EventListener/OrderListener.php', + 'izi\\prestashop\\EventListener\\ShipmentListener' => $baseDir . '/src/EventListener/ShipmentListener.php', + 'izi\\prestashop\\Event\\Adapter\\EventDispatcher' => $baseDir . '/src/Event/Adapter/EventDispatcher.php', + 'izi\\prestashop\\Event\\CartUpdatedEvent' => $baseDir . '/src/Event/CartUpdatedEvent.php', + 'izi\\prestashop\\Event\\CreateShipmentRequestEvent' => $baseDir . '/src/Event/CreateShipmentRequestEvent.php', + 'izi\\prestashop\\Event\\CreateShipmentRequestProcessedEvent' => $baseDir . '/src/Event/CreateShipmentRequestProcessedEvent.php', + 'izi\\prestashop\\Event\\Event' => $baseDir . '/src/Event/Event.php', + 'izi\\prestashop\\Event\\EventDispatcherFactory' => $baseDir . '/src/Event/EventDispatcherFactory.php', + 'izi\\prestashop\\Event\\EventDispatcherInterface' => $baseDir . '/src/Event/EventDispatcherInterface.php', + 'izi\\prestashop\\Event\\OrderEvent' => $baseDir . '/src/Event/OrderEvent.php', + 'izi\\prestashop\\Event\\OrderStatusUpdatedEvent' => $baseDir . '/src/Event/OrderStatusUpdatedEvent.php', + 'izi\\prestashop\\Event\\ShipmentEvent' => $baseDir . '/src/Event/ShipmentEvent.php', + 'izi\\prestashop\\Event\\ValidateOrderEvent' => $baseDir . '/src/Event/ValidateOrderEvent.php', + 'izi\\prestashop\\Form\\BasketAppClientProvider' => $baseDir . '/src/Form/BasketAppClientProvider.php', + 'izi\\prestashop\\Form\\ChoiceList\\AvailablePaymentOptionChoiceLoader' => $baseDir . '/src/Form/ChoiceList/AvailablePaymentOptionChoiceLoader.php', + 'izi\\prestashop\\Form\\ChoiceList\\CarrierChoiceLoader' => $baseDir . '/src/Form/ChoiceList/CarrierChoiceLoader.php', + 'izi\\prestashop\\Form\\ChoiceList\\LazyChoiceLoader' => $baseDir . '/src/Form/ChoiceList/LazyChoiceLoader.php', + 'izi\\prestashop\\Form\\ChoiceList\\ObjectModelChoiceLoader' => $baseDir . '/src/Form/ChoiceList/ObjectModelChoiceLoader.php', + 'izi\\prestashop\\Form\\ChoiceList\\OrderStateChoiceLoader' => $baseDir . '/src/Form/ChoiceList/OrderStateChoiceLoader.php', + 'izi\\prestashop\\Form\\ChoiceList\\ProductImageTypeChoiceLoader' => $baseDir . '/src/Form/ChoiceList/ProductImageTypeChoiceLoader.php', + 'izi\\prestashop\\Form\\DataMapper\\ClientCredentialsDataMapper' => $baseDir . '/src/Form/DataMapper/ClientCredentialsDataMapper.php', + 'izi\\prestashop\\Form\\DataTransformer\\CombinationToAttributeIdsTransformer' => $baseDir . '/src/Form/DataTransformer/CombinationToAttributeIdsTransformer.php', + 'izi\\prestashop\\Form\\DataTransformer\\DateTimeImmutableToDateTimeTransformer' => $baseDir . '/src/Form/DataTransformer/DateTimeImmutableToDateTimeTransformer.php', + 'izi\\prestashop\\Form\\DataTransformer\\EnumDataTransformer' => $baseDir . '/src/Form/DataTransformer/EnumDataTransformer.php', + 'izi\\prestashop\\Form\\DataTransformer\\ObjectModelCollectionToIdsTransformer' => $baseDir . '/src/Form/DataTransformer/ObjectModelCollectionToIdsTransformer.php', + 'izi\\prestashop\\Form\\DataTransformer\\ObjectModelToIdTransformer' => $baseDir . '/src/Form/DataTransformer/ObjectModelToIdTransformer.php', + 'izi\\prestashop\\Form\\EventListener\\ReindexDataListener' => $baseDir . '/src/Form/EventListener/ReindexDataListener.php', + 'izi\\prestashop\\Form\\Event\\ApiConfigurationValidatedEvent' => $baseDir . '/src/Form/Event/ApiConfigurationValidatedEvent.php', + 'izi\\prestashop\\Form\\Extension\\DependencyInjectionExtension' => $baseDir . '/src/Form/Extension/DependencyInjectionExtension.php', + 'izi\\prestashop\\Form\\FormFactoryFactory' => $baseDir . '/src/Form/FormFactoryFactory.php', + 'izi\\prestashop\\Form\\TypeExtension\\ChoicesAsValuesTypeExtension' => $baseDir . '/src/Form/TypeExtension/ChoicesAsValuesTypeExtension.php', + 'izi\\prestashop\\Form\\TypeExtension\\DatePickerCompatibilityTypeExtension' => $baseDir . '/src/Form/TypeExtension/DatePickerCompatibilityTypeExtension.php', + 'izi\\prestashop\\Form\\TypeExtension\\DateTimeImmutableTimeTypeExtension' => $baseDir . '/src/Form/TypeExtension/DateTimeImmutableTimeTypeExtension.php', + 'izi\\prestashop\\Form\\TypeExtension\\HelpTextExtension' => $baseDir . '/src/Form/TypeExtension/HelpTextExtension.php', + 'izi\\prestashop\\Form\\TypeExtension\\UnitTypeExtension' => $baseDir . '/src/Form/TypeExtension/UnitTypeExtension.php', + 'izi\\prestashop\\Form\\Type\\AdvancedConfigurationType' => $baseDir . '/src/Form/Type/AdvancedConfigurationType.php', + 'izi\\prestashop\\Form\\Type\\ApiConfigurationType' => $baseDir . '/src/Form/Type/ApiConfigurationType.php', + 'izi\\prestashop\\Form\\Type\\CartRuleOptionsType' => $baseDir . '/src/Form/Type/CartRuleOptionsType.php', + 'izi\\prestashop\\Form\\Type\\ClientCredentialsType' => $baseDir . '/src/Form/Type/ClientCredentialsType.php', + 'izi\\prestashop\\Form\\Type\\Compatibility\\CategoryChoiceTreeType' => $baseDir . '/src/Form/Type/Compatibility/CategoryChoiceTreeType.php', + 'izi\\prestashop\\Form\\Type\\Consent\\ConsentLinkType' => $baseDir . '/src/Form/Type/Consent/ConsentLinkType.php', + 'izi\\prestashop\\Form\\Type\\Consent\\ConsentRequirementChoiceType' => $baseDir . '/src/Form/Type/Consent/ConsentRequirementChoiceType.php', + 'izi\\prestashop\\Form\\Type\\Consent\\ConsentType' => $baseDir . '/src/Form/Type/Consent/ConsentType.php', + 'izi\\prestashop\\Form\\Type\\ConsentsConfigurationType' => $baseDir . '/src/Form/Type/ConsentsConfigurationType.php', + 'izi\\prestashop\\Form\\Type\\EnvironmentChoiceType' => $baseDir . '/src/Form/Type/EnvironmentChoiceType.php', + 'izi\\prestashop\\Form\\Type\\GeneralConfigurationType' => $baseDir . '/src/Form/Type/GeneralConfigurationType.php', + 'izi\\prestashop\\Form\\Type\\GuiConfigurationType' => $baseDir . '/src/Form/Type/GuiConfigurationType.php', + 'izi\\prestashop\\Form\\Type\\Image\\ImageTypeChoiceType' => $baseDir . '/src/Form/Type/Image/ImageTypeChoiceType.php', + 'izi\\prestashop\\Form\\Type\\MaskedPasswordType' => $baseDir . '/src/Form/Type/MaskedPasswordType.php', + 'izi\\prestashop\\Form\\Type\\ObjectModelAutocompleteType' => $baseDir . '/src/Form/Type/ObjectModelAutocompleteType.php', + 'izi\\prestashop\\Form\\Type\\ObjectModelType' => $baseDir . '/src/Form/Type/ObjectModelType.php', + 'izi\\prestashop\\Form\\Type\\OrderStateChoiceType' => $baseDir . '/src/Form/Type/OrderStateChoiceType.php', + 'izi\\prestashop\\Form\\Type\\OrderStatusDescriptionMapType' => $baseDir . '/src/Form/Type/OrderStatusDescriptionMapType.php', + 'izi\\prestashop\\Form\\Type\\Order\\AvailablePaymentOptionsChoiceType' => $baseDir . '/src/Form/Type/Order/AvailablePaymentOptionsChoiceType.php', + 'izi\\prestashop\\Form\\Type\\Order\\MessageOptionsType' => $baseDir . '/src/Form/Type/Order/MessageOptionsType.php', + 'izi\\prestashop\\Form\\Type\\OrdersConfigurationType' => $baseDir . '/src/Form/Type/OrdersConfigurationType.php', + 'izi\\prestashop\\Form\\Type\\ProductConfigurationType' => $baseDir . '/src/Form/Type/ProductConfigurationType.php', + 'izi\\prestashop\\Form\\Type\\Product\\CombinationByAttributesChoiceType' => $baseDir . '/src/Form/Type/Product/CombinationByAttributesChoiceType.php', + 'izi\\prestashop\\Form\\Type\\Product\\ProductRestrictionsType' => $baseDir . '/src/Form/Type/Product/ProductRestrictionsType.php', + 'izi\\prestashop\\Form\\Type\\ShippingConfigurationType' => $baseDir . '/src/Form/Type/ShippingConfigurationType.php', + 'izi\\prestashop\\Form\\Type\\Shipping\\CarrierChoiceType' => $baseDir . '/src/Form/Type/Shipping/CarrierChoiceType.php', + 'izi\\prestashop\\Form\\Type\\Shipping\\CarrierMappingType' => $baseDir . '/src/Form/Type/Shipping/CarrierMappingType.php', + 'izi\\prestashop\\Form\\Type\\Shipping\\CarrierMappingsType' => $baseDir . '/src/Form/Type/Shipping/CarrierMappingsType.php', + 'izi\\prestashop\\Form\\Type\\Shipping\\OptionalServicesType' => $baseDir . '/src/Form/Type/Shipping/OptionalServicesType.php', + 'izi\\prestashop\\Form\\Type\\Shipping\\ServiceOptionsType' => $baseDir . '/src/Form/Type/Shipping/ServiceOptionsType.php', + 'izi\\prestashop\\Form\\Type\\Shipping\\ShippingOptionsType' => $baseDir . '/src/Form/Type/Shipping/ShippingOptionsType.php', + 'izi\\prestashop\\Form\\Type\\Shipping\\TimeOfWeekRangeType' => $baseDir . '/src/Form/Type/Shipping/TimeOfWeekRangeType.php', + 'izi\\prestashop\\Form\\Type\\Shipping\\TimeOfWeekType' => $baseDir . '/src/Form/Type/Shipping/TimeOfWeekType.php', + 'izi\\prestashop\\Form\\Type\\Shipping\\WeekDayChoiceType' => $baseDir . '/src/Form/Type/Shipping/WeekDayChoiceType.php', + 'izi\\prestashop\\Form\\Type\\SwitchType' => $baseDir . '/src/Form/Type/SwitchType.php', + 'izi\\prestashop\\Form\\Type\\TranslatableType' => $baseDir . '/src/Form/Type/TranslatableType.php', + 'izi\\prestashop\\Form\\Type\\Widget\\HtmlStylesType' => $baseDir . '/src/Form/Type/Widget/HtmlStylesType.php', + 'izi\\prestashop\\Form\\Type\\Widget\\ProductPageDisplayConfigurationType' => $baseDir . '/src/Form/Type/Widget/ProductPageDisplayConfigurationType.php', + 'izi\\prestashop\\Form\\Type\\Widget\\WidgetConfigurationType' => $baseDir . '/src/Form/Type/Widget/WidgetConfigurationType.php', + 'izi\\prestashop\\Form\\Type\\Widget\\WidgetDisplayConfigurationType' => $baseDir . '/src/Form/Type/Widget/WidgetDisplayConfigurationType.php', + 'izi\\prestashop\\Form\\Type\\Widget\\WidgetFrameStyleChoiceType' => $baseDir . '/src/Form/Type/Widget/WidgetFrameStyleChoiceType.php', + 'izi\\prestashop\\Form\\Type\\Widget\\WidgetSizeChoiceType' => $baseDir . '/src/Form/Type/Widget/WidgetSizeChoiceType.php', + 'izi\\prestashop\\Form\\Type\\Widget\\WidgetVariantChoiceType' => $baseDir . '/src/Form/Type/Widget/WidgetVariantChoiceType.php', + 'izi\\prestashop\\Handler\\CommandHandlerTrait' => $baseDir . '/src/Handler/CommandHandlerTrait.php', + 'izi\\prestashop\\Handler\\Config\\CheckStatusHandler' => $baseDir . '/src/Handler/Config/CheckStatusHandler.php', + 'izi\\prestashop\\Handler\\Config\\CheckStatusHandlerInterface' => $baseDir . '/src/Handler/Config/CheckStatusHandlerInterface.php', + 'izi\\prestashop\\Handler\\Config\\DownloadModuleDataHandler' => $baseDir . '/src/Handler/Config/DownloadModuleDataHandler.php', + 'izi\\prestashop\\Handler\\Config\\DownloadModuleDataHandlerInterface' => $baseDir . '/src/Handler/Config/DownloadModuleDataHandlerInterface.php', + 'izi\\prestashop\\Handler\\Config\\ModuleStatus' => $baseDir . '/src/Handler/Config/ModuleStatus.php', + 'izi\\prestashop\\Handler\\Config\\Status\\CacheStatusChecker' => $baseDir . '/src/Handler/Config/Status/CacheStatusChecker.php', + 'izi\\prestashop\\Handler\\Config\\Status\\ConfigurationStatusChecker' => $baseDir . '/src/Handler/Config/Status/ConfigurationStatusChecker.php', + 'izi\\prestashop\\Handler\\Config\\Status\\DeliveryOptionsStatusChecker' => $baseDir . '/src/Handler/Config/Status/DeliveryOptionsStatusChecker.php', + 'izi\\prestashop\\Handler\\Config\\Status\\StatusCheckerInterface' => $baseDir . '/src/Handler/Config/Status/StatusCheckerInterface.php', + 'izi\\prestashop\\Handler\\Config\\UpdateAdvancedConfigurationHandler' => $baseDir . '/src/Handler/Config/UpdateAdvancedConfigurationHandler.php', + 'izi\\prestashop\\Handler\\Config\\UpdateAdvancedConfigurationHandlerInterface' => $baseDir . '/src/Handler/Config/UpdateAdvancedConfigurationHandlerInterface.php', + 'izi\\prestashop\\Handler\\Config\\UpdateCartRuleOptionsHandler' => $baseDir . '/src/Handler/Config/UpdateCartRuleOptionsHandler.php', + 'izi\\prestashop\\Handler\\Config\\UpdateCartRuleOptionsHandlerInterface' => $baseDir . '/src/Handler/Config/UpdateCartRuleOptionsHandlerInterface.php', + 'izi\\prestashop\\Handler\\Config\\UpdateConsentsConfigurationHandler' => $baseDir . '/src/Handler/Config/UpdateConsentsConfigurationHandler.php', + 'izi\\prestashop\\Handler\\Config\\UpdateConsentsConfigurationHandlerInterface' => $baseDir . '/src/Handler/Config/UpdateConsentsConfigurationHandlerInterface.php', + 'izi\\prestashop\\Handler\\Config\\UpdateGeneralConfigurationHandler' => $baseDir . '/src/Handler/Config/UpdateGeneralConfigurationHandler.php', + 'izi\\prestashop\\Handler\\Config\\UpdateGeneralConfigurationHandlerInterface' => $baseDir . '/src/Handler/Config/UpdateGeneralConfigurationHandlerInterface.php', + 'izi\\prestashop\\Handler\\Config\\UpdateGuiConfigurationHandler' => $baseDir . '/src/Handler/Config/UpdateGuiConfigurationHandler.php', + 'izi\\prestashop\\Handler\\Config\\UpdateGuiConfigurationHandlerInterface' => $baseDir . '/src/Handler/Config/UpdateGuiConfigurationHandlerInterface.php', + 'izi\\prestashop\\Handler\\Config\\UpdateShippingConfigurationHandler' => $baseDir . '/src/Handler/Config/UpdateShippingConfigurationHandler.php', + 'izi\\prestashop\\Handler\\Config\\UpdateShippingConfigurationHandlerInterface' => $baseDir . '/src/Handler/Config/UpdateShippingConfigurationHandlerInterface.php', + 'izi\\prestashop\\Handler\\GetBasketBindingKeyHandler' => $baseDir . '/src/Handler/GetBasketBindingKeyHandler.php', + 'izi\\prestashop\\Handler\\GetBasketBindingKeyHandlerInterface' => $baseDir . '/src/Handler/GetBasketBindingKeyHandlerInterface.php', + 'izi\\prestashop\\Handler\\GetOrderConfirmationUrlHandler' => $baseDir . '/src/Handler/GetOrderConfirmationUrlHandler.php', + 'izi\\prestashop\\Handler\\GetOrderConfirmationUrlHandlerInterface' => $baseDir . '/src/Handler/GetOrderConfirmationUrlHandlerInterface.php', + 'izi\\prestashop\\Handler\\Result\\BasketBindingKey' => $baseDir . '/src/Handler/Result/BasketBindingKey.php', + 'izi\\prestashop\\Handler\\UnbindBasketHandler' => $baseDir . '/src/Handler/UnbindBasketHandler.php', + 'izi\\prestashop\\Handler\\UnbindBasketHandlerInterface' => $baseDir . '/src/Handler/UnbindBasketHandlerInterface.php', + 'izi\\prestashop\\Handler\\UpdateBasketHandler' => $baseDir . '/src/Handler/UpdateBasketHandler.php', + 'izi\\prestashop\\Handler\\UpdateBasketHandlerInterface' => $baseDir . '/src/Handler/UpdateBasketHandlerInterface.php', + 'izi\\prestashop\\Handler\\UpdateOrderAddressDeliveryHandler' => $baseDir . '/src/Handler/UpdateOrderAddressDeliveryHandler.php', + 'izi\\prestashop\\Handler\\UpdateOrderAddressDeliveryHandlerInterface' => $baseDir . '/src/Handler/UpdateOrderAddressDeliveryHandlerInterface.php', + 'izi\\prestashop\\Handler\\UpdateOrderStatusHandler' => $baseDir . '/src/Handler/UpdateOrderStatusHandler.php', + 'izi\\prestashop\\Handler\\UpdateOrderStatusHandlerInterface' => $baseDir . '/src/Handler/UpdateOrderStatusHandlerInterface.php', + 'izi\\prestashop\\Handler\\UpdateOrderTrackingNumbersHandler' => $baseDir . '/src/Handler/UpdateOrderTrackingNumbersHandler.php', + 'izi\\prestashop\\Handler\\UpdateOrderTrackingNumbersHandlerInterface' => $baseDir . '/src/Handler/UpdateOrderTrackingNumbersHandlerInterface.php', + 'izi\\prestashop\\Hook\\Adapter\\HookDispatcher' => $baseDir . '/src/Hook/Adapter/HookDispatcher.php', + 'izi\\prestashop\\Hook\\Admin\\ActionAdminCartRuleSaveAfter' => $baseDir . '/src/Hook/Admin/ActionAdminCartRuleSaveAfter.php', + 'izi\\prestashop\\Hook\\Admin\\ActionAdminControllerSetMedia' => $baseDir . '/src/Hook/Admin/ActionAdminControllerSetMedia.php', + 'izi\\prestashop\\Hook\\Admin\\ActionAdminInPostConfirmedShipmentsControllerAfter' => $baseDir . '/src/Hook/Admin/ActionAdminInPostConfirmedShipmentsControllerAfter.php', + 'izi\\prestashop\\Hook\\Admin\\ActionAdminInPostConfirmedShipmentsControllerBefore' => $baseDir . '/src/Hook/Admin/ActionAdminInPostConfirmedShipmentsControllerBefore.php', + 'izi\\prestashop\\Hook\\Admin\\DisplayAdminOrderLeft' => $baseDir . '/src/Hook/Admin/DisplayAdminOrderLeft.php', + 'izi\\prestashop\\Hook\\Admin\\DisplayAdminOrderSide' => $baseDir . '/src/Hook/Admin/DisplayAdminOrderSide.php', + 'izi\\prestashop\\Hook\\Admin\\DisplayBackOfficeHeader' => $baseDir . '/src/Hook/Admin/DisplayBackOfficeHeader.php', + 'izi\\prestashop\\Hook\\AliasedHookInterface' => $baseDir . '/src/Hook/AliasedHookInterface.php', + 'izi\\prestashop\\Hook\\AssetRegistryUpdaterTrait' => $baseDir . '/src/Hook/AssetRegistryUpdaterTrait.php', + 'izi\\prestashop\\Hook\\Common\\ActionCartDeleteBefore' => $baseDir . '/src/Hook/Common/ActionCartDeleteBefore.php', + 'izi\\prestashop\\Hook\\Common\\ActionCartUpdateAfter' => $baseDir . '/src/Hook/Common/ActionCartUpdateAfter.php', + 'izi\\prestashop\\Hook\\Common\\ActionEmailSendBefore' => $baseDir . '/src/Hook/Common/ActionEmailSendBefore.php', + 'izi\\prestashop\\Hook\\Common\\ActionObjectOrderUpdateAfter' => $baseDir . '/src/Hook/Common/ActionObjectOrderUpdateAfter.php', + 'izi\\prestashop\\Hook\\Common\\ActionObjectOrderUpdateBefore' => $baseDir . '/src/Hook/Common/ActionObjectOrderUpdateBefore.php', + 'izi\\prestashop\\Hook\\Common\\ActionOrderStatusPostUpdate' => $baseDir . '/src/Hook/Common/ActionOrderStatusPostUpdate.php', + 'izi\\prestashop\\Hook\\Common\\ActionShipmentAddAfter' => $baseDir . '/src/Hook/Common/ActionShipmentAddAfter.php', + 'izi\\prestashop\\Hook\\Common\\ActionShipmentUpdateAfter' => $baseDir . '/src/Hook/Common/ActionShipmentUpdateAfter.php', + 'izi\\prestashop\\Hook\\Common\\ActionShipmentUpdateBefore' => $baseDir . '/src/Hook/Common/ActionShipmentUpdateBefore.php', + 'izi\\prestashop\\Hook\\Common\\ActionValidateOrder' => $baseDir . '/src/Hook/Common/ActionValidateOrder.php', + 'izi\\prestashop\\Hook\\Common\\Product\\ActionCombinationDeleteAfter' => $baseDir . '/src/Hook/Common/Product/ActionCombinationDeleteAfter.php', + 'izi\\prestashop\\Hook\\Common\\Product\\ActionCombinationDeleteBefore' => $baseDir . '/src/Hook/Common/Product/ActionCombinationDeleteBefore.php', + 'izi\\prestashop\\Hook\\Common\\Product\\ActionCombinationUpdateAfter' => $baseDir . '/src/Hook/Common/Product/ActionCombinationUpdateAfter.php', + 'izi\\prestashop\\Hook\\Common\\Product\\ActionImageAddAfter' => $baseDir . '/src/Hook/Common/Product/ActionImageAddAfter.php', + 'izi\\prestashop\\Hook\\Common\\Product\\ActionImageDeleteAfter' => $baseDir . '/src/Hook/Common/Product/ActionImageDeleteAfter.php', + 'izi\\prestashop\\Hook\\Common\\Product\\ActionProductDeleteAfter' => $baseDir . '/src/Hook/Common/Product/ActionProductDeleteAfter.php', + 'izi\\prestashop\\Hook\\Common\\Product\\ActionProductDeleteBefore' => $baseDir . '/src/Hook/Common/Product/ActionProductDeleteBefore.php', + 'izi\\prestashop\\Hook\\Common\\Product\\ActionProductUpdateAfter' => $baseDir . '/src/Hook/Common/Product/ActionProductUpdateAfter.php', + 'izi\\prestashop\\Hook\\Common\\Product\\ActionSpecificPriceAddAfter' => $baseDir . '/src/Hook/Common/Product/ActionSpecificPriceAddAfter.php', + 'izi\\prestashop\\Hook\\Common\\Product\\ActionSpecificPriceDeleteAfter' => $baseDir . '/src/Hook/Common/Product/ActionSpecificPriceDeleteAfter.php', + 'izi\\prestashop\\Hook\\Common\\Product\\ActionSpecificPriceUpdateAfter' => $baseDir . '/src/Hook/Common/Product/ActionSpecificPriceUpdateAfter.php', + 'izi\\prestashop\\Hook\\Common\\Product\\ActionUpdateQuantity' => $baseDir . '/src/Hook/Common/Product/ActionUpdateQuantity.php', + 'izi\\prestashop\\Hook\\Exception\\HookExceptionInterface' => $baseDir . '/src/Hook/Exception/HookExceptionInterface.php', + 'izi\\prestashop\\Hook\\Exception\\HookExceptionTrait' => $baseDir . '/src/Hook/Exception/HookExceptionTrait.php', + 'izi\\prestashop\\Hook\\Exception\\HookNotFoundException' => $baseDir . '/src/Hook/Exception/HookNotFoundException.php', + 'izi\\prestashop\\Hook\\Exception\\HookNotImplementedException' => $baseDir . '/src/Hook/Exception/HookNotImplementedException.php', + 'izi\\prestashop\\Hook\\Front\\ActionCartControllerAjaxUpdateResponse' => $baseDir . '/src/Hook/Front/ActionCartControllerAjaxUpdateResponse.php', + 'izi\\prestashop\\Hook\\Front\\ActionFrontControllerSetMedia' => $baseDir . '/src/Hook/Front/ActionFrontControllerSetMedia.php', + 'izi\\prestashop\\Hook\\Front\\ActionGetPaymentOptions' => $baseDir . '/src/Hook/Front/ActionGetPaymentOptions.php', + 'izi\\prestashop\\Hook\\Front\\ButtonWidgetRendererTrait' => $baseDir . '/src/Hook/Front/ButtonWidgetRendererTrait.php', + 'izi\\prestashop\\Hook\\Front\\DisplayCheckoutSummaryTop' => $baseDir . '/src/Hook/Front/DisplayCheckoutSummaryTop.php', + 'izi\\prestashop\\Hook\\Front\\DisplayCustomerAccountFormTop' => $baseDir . '/src/Hook/Front/DisplayCustomerAccountFormTop.php', + 'izi\\prestashop\\Hook\\Front\\DisplayCustomerLoginFormAfter' => $baseDir . '/src/Hook/Front/DisplayCustomerLoginFormAfter.php', + 'izi\\prestashop\\Hook\\Front\\DisplayExpressCheckout' => $baseDir . '/src/Hook/Front/DisplayExpressCheckout.php', + 'izi\\prestashop\\Hook\\Front\\DisplayHeader' => $baseDir . '/src/Hook/Front/DisplayHeader.php', + 'izi\\prestashop\\Hook\\Front\\DisplayIziCartPreviewButton' => $baseDir . '/src/Hook/Front/DisplayIziCartPreviewButton.php', + 'izi\\prestashop\\Hook\\Front\\DisplayIziCheckoutButton' => $baseDir . '/src/Hook/Front/DisplayIziCheckoutButton.php', + 'izi\\prestashop\\Hook\\Front\\DisplayIziThankYou' => $baseDir . '/src/Hook/Front/DisplayIziThankYou.php', + 'izi\\prestashop\\Hook\\Front\\DisplayOrderConfirmation' => $baseDir . '/src/Hook/Front/DisplayOrderConfirmation.php', + 'izi\\prestashop\\Hook\\Front\\DisplayPaymentReturn' => $baseDir . '/src/Hook/Front/DisplayPaymentReturn.php', + 'izi\\prestashop\\Hook\\Front\\DisplayProductActions' => $baseDir . '/src/Hook/Front/DisplayProductActions.php', + 'izi\\prestashop\\Hook\\Front\\DisplayProductAdditionalInfo' => $baseDir . '/src/Hook/Front/DisplayProductAdditionalInfo.php', + 'izi\\prestashop\\Hook\\Front\\DisplayShoppingCart' => $baseDir . '/src/Hook/Front/DisplayShoppingCart.php', + 'izi\\prestashop\\Hook\\Front\\DisplayShoppingCartFooter' => $baseDir . '/src/Hook/Front/DisplayShoppingCartFooter.php', + 'izi\\prestashop\\Hook\\Front\\ProductWidgetRendererTrait' => $baseDir . '/src/Hook/Front/ProductWidgetRendererTrait.php', + 'izi\\prestashop\\Hook\\Front\\ThankYouWidgetRendererTrait' => $baseDir . '/src/Hook/Front/ThankYouWidgetRendererTrait.php', + 'izi\\prestashop\\Hook\\HookDispatcherInterface' => $baseDir . '/src/Hook/HookDispatcherInterface.php', + 'izi\\prestashop\\Hook\\HookExecutor' => $baseDir . '/src/Hook/HookExecutor.php', + 'izi\\prestashop\\Hook\\HookExecutorInterface' => $baseDir . '/src/Hook/HookExecutorInterface.php', + 'izi\\prestashop\\Hook\\HookInterface' => $baseDir . '/src/Hook/HookInterface.php', + 'izi\\prestashop\\Hook\\PrestaShopVersionAwareHookInterface' => $baseDir . '/src/Hook/PrestaShopVersionAwareHookInterface.php', + 'izi\\prestashop\\Hook\\VersionRange' => $baseDir . '/src/Hook/VersionRange.php', + 'izi\\prestashop\\Hook\\Widget' => $baseDir . '/src/Hook/Widget.php', + 'izi\\prestashop\\Hook\\WidgetParametersProvider' => $baseDir . '/src/Hook/WidgetParametersProvider.php', + 'izi\\prestashop\\Hook\\WidgetParametersProviderInterface' => $baseDir . '/src/Hook/WidgetParametersProviderInterface.php', + 'izi\\prestashop\\HotProduct\\EventListener\\UpdateHotProductsListener' => $baseDir . '/src/HotProduct/EventListener/UpdateHotProductsListener.php', + 'izi\\prestashop\\HotProduct\\Exception\\HotProductExceptionInterface' => $baseDir . '/src/HotProduct/Exception/HotProductExceptionInterface.php', + 'izi\\prestashop\\HotProduct\\Exception\\HotProductExistsException' => $baseDir . '/src/HotProduct/Exception/HotProductExistsException.php', + 'izi\\prestashop\\HotProduct\\Exception\\HotProductNotFoundException' => $baseDir . '/src/HotProduct/Exception/HotProductNotFoundException.php', + 'izi\\prestashop\\HotProduct\\Exception\\InvalidProductDataException' => $baseDir . '/src/HotProduct/Exception/InvalidProductDataException.php', + 'izi\\prestashop\\HotProduct\\Form\\CreateHotProductType' => $baseDir . '/src/HotProduct/Form/CreateHotProductType.php', + 'izi\\prestashop\\HotProduct\\Form\\UpdateHotProductType' => $baseDir . '/src/HotProduct/Form/UpdateHotProductType.php', + 'izi\\prestashop\\HotProduct\\HotProduct' => $baseDir . '/src/HotProduct/HotProduct.php', + 'izi\\prestashop\\HotProduct\\HotProductDataMapper' => $baseDir . '/src/HotProduct/HotProductDataMapper.php', + 'izi\\prestashop\\HotProduct\\HotProductDataMapperInterface' => $baseDir . '/src/HotProduct/HotProductDataMapperInterface.php', + 'izi\\prestashop\\HotProduct\\HotProductRepository' => $baseDir . '/src/HotProduct/HotProductRepository.php', + 'izi\\prestashop\\HotProduct\\HotProductRepositoryInterface' => $baseDir . '/src/HotProduct/HotProductRepositoryInterface.php', + 'izi\\prestashop\\HotProduct\\HotProductValidator' => $baseDir . '/src/HotProduct/HotProductValidator.php', + 'izi\\prestashop\\HotProduct\\MessageHandler\\CreateHotProductHandler' => $baseDir . '/src/HotProduct/MessageHandler/CreateHotProductHandler.php', + 'izi\\prestashop\\HotProduct\\MessageHandler\\CreateHotProductHandlerInterface' => $baseDir . '/src/HotProduct/MessageHandler/CreateHotProductHandlerInterface.php', + 'izi\\prestashop\\HotProduct\\MessageHandler\\DeleteHotProductHandler' => $baseDir . '/src/HotProduct/MessageHandler/DeleteHotProductHandler.php', + 'izi\\prestashop\\HotProduct\\MessageHandler\\DeleteHotProductHandlerInterface' => $baseDir . '/src/HotProduct/MessageHandler/DeleteHotProductHandlerInterface.php', + 'izi\\prestashop\\HotProduct\\MessageHandler\\DeleteRemoteProductHandler' => $baseDir . '/src/HotProduct/MessageHandler/DeleteRemoteProductHandler.php', + 'izi\\prestashop\\HotProduct\\MessageHandler\\DeleteRemoteProductHandlerInterface' => $baseDir . '/src/HotProduct/MessageHandler/DeleteRemoteProductHandlerInterface.php', + 'izi\\prestashop\\HotProduct\\MessageHandler\\ImportHotProductHandler' => $baseDir . '/src/HotProduct/MessageHandler/ImportHotProductHandler.php', + 'izi\\prestashop\\HotProduct\\MessageHandler\\ImportHotProductHandlerInterface' => $baseDir . '/src/HotProduct/MessageHandler/ImportHotProductHandlerInterface.php', + 'izi\\prestashop\\HotProduct\\MessageHandler\\UpdateHotProductHandler' => $baseDir . '/src/HotProduct/MessageHandler/UpdateHotProductHandler.php', + 'izi\\prestashop\\HotProduct\\MessageHandler\\UpdateHotProductHandlerInterface' => $baseDir . '/src/HotProduct/MessageHandler/UpdateHotProductHandlerInterface.php', + 'izi\\prestashop\\HotProduct\\Message\\CreateHotProductCommand' => $baseDir . '/src/HotProduct/Message/CreateHotProductCommand.php', + 'izi\\prestashop\\HotProduct\\Message\\DeleteHotProductCommand' => $baseDir . '/src/HotProduct/Message/DeleteHotProductCommand.php', + 'izi\\prestashop\\HotProduct\\Message\\DeleteRemoteProductCommand' => $baseDir . '/src/HotProduct/Message/DeleteRemoteProductCommand.php', + 'izi\\prestashop\\HotProduct\\Message\\ImportHotProductCommand' => $baseDir . '/src/HotProduct/Message/ImportHotProductCommand.php', + 'izi\\prestashop\\HotProduct\\Message\\UpdateHotProductCommand' => $baseDir . '/src/HotProduct/Message/UpdateHotProductCommand.php', + 'izi\\prestashop\\HotProduct\\View\\HotProductListView' => $baseDir . '/src/HotProduct/View/HotProductListView.php', + 'izi\\prestashop\\HotProduct\\View\\HotProductView' => $baseDir . '/src/HotProduct/View/HotProductView.php', + 'izi\\prestashop\\HotProduct\\View\\HotProductViewDataFactory' => $baseDir . '/src/HotProduct/View/HotProductViewDataFactory.php', + 'izi\\prestashop\\HttpKernel\\ServiceParamConverter' => $baseDir . '/src/HttpKernel/ServiceParamConverter.php', + 'izi\\prestashop\\Http\\Client\\Adapter\\Guzzle5Adapter' => $baseDir . '/src/Http/Client/Adapter/Guzzle5Adapter.php', + 'izi\\prestashop\\Http\\Client\\Adapter\\NetworkException' => $baseDir . '/src/Http/Client/Adapter/NetworkException.php', + 'izi\\prestashop\\Http\\Client\\Adapter\\RequestException' => $baseDir . '/src/Http/Client/Adapter/RequestException.php', + 'izi\\prestashop\\Http\\Client\\AuthorizingClient' => $baseDir . '/src/Http/Client/AuthorizingClient.php', + 'izi\\prestashop\\Http\\Client\\Factory\\ClientFactoryInterface' => $baseDir . '/src/Http/Client/Factory/ClientFactoryInterface.php', + 'izi\\prestashop\\Http\\Client\\Factory\\GuzzleClientFactory' => $baseDir . '/src/Http/Client/Factory/GuzzleClientFactory.php', + 'izi\\prestashop\\Http\\Client\\LoggingClient' => $baseDir . '/src/Http/Client/LoggingClient.php', + 'izi\\prestashop\\Http\\Client\\ModuleVersionInfoProvidingClient' => $baseDir . '/src/Http/Client/ModuleVersionInfoProvidingClient.php', + 'izi\\prestashop\\Http\\Exception\\ClientException' => $baseDir . '/src/Http/Exception/ClientException.php', + 'izi\\prestashop\\Http\\Exception\\HttpExceptionInterface' => $baseDir . '/src/Http/Exception/HttpExceptionInterface.php', + 'izi\\prestashop\\Http\\Exception\\HttpExceptionTrait' => $baseDir . '/src/Http/Exception/HttpExceptionTrait.php', + 'izi\\prestashop\\Http\\Exception\\RedirectionException' => $baseDir . '/src/Http/Exception/RedirectionException.php', + 'izi\\prestashop\\Http\\Exception\\ServerException' => $baseDir . '/src/Http/Exception/ServerException.php', + 'izi\\prestashop\\Http\\Response\\EventStreamResponse' => $baseDir . '/src/Http/Response/EventStreamResponse.php', + 'izi\\prestashop\\Http\\Response\\ServerSentEvent' => $baseDir . '/src/Http/Response/ServerSentEvent.php', + 'izi\\prestashop\\Http\\Response\\ServerSentEventBuilder' => $baseDir . '/src/Http/Response/ServerSentEventBuilder.php', + 'izi\\prestashop\\Http\\Util\\UriResolver' => $baseDir . '/src/Http/Util/UriResolver.php', + 'izi\\prestashop\\Installer\\DatabaseInstaller' => $baseDir . '/src/Installer/DatabaseInstaller.php', + 'izi\\prestashop\\Installer\\DatabaseMigrationInterface' => $baseDir . '/src/Installer/DatabaseMigrationInterface.php', + 'izi\\prestashop\\Installer\\Database\\AbstractMigration' => $baseDir . '/src/Installer/Database/AbstractMigration.php', + 'izi\\prestashop\\Installer\\Database\\Version_1_0_0' => $baseDir . '/src/Installer/Database/Version_1_0_0.php', + 'izi\\prestashop\\Installer\\Database\\Version_1_11_0' => $baseDir . '/src/Installer/Database/Version_1_11_0.php', + 'izi\\prestashop\\Installer\\Database\\Version_1_4_0' => $baseDir . '/src/Installer/Database/Version_1_4_0.php', + 'izi\\prestashop\\Installer\\Database\\Version_1_9_0' => $baseDir . '/src/Installer/Database/Version_1_9_0.php', + 'izi\\prestashop\\Installer\\Database\\Version_2_0_0' => $baseDir . '/src/Installer/Database/Version_2_0_0.php', + 'izi\\prestashop\\Installer\\Database\\Version_2_1_0' => $baseDir . '/src/Installer/Database/Version_2_1_0.php', + 'izi\\prestashop\\Installer\\Database\\Version_2_2_0' => $baseDir . '/src/Installer/Database/Version_2_2_0.php', + 'izi\\prestashop\\Log\\Handler\\AbstractHandlerFactory' => $baseDir . '/src/Log/Handler/AbstractHandlerFactory.php', + 'izi\\prestashop\\Log\\Handler\\HandlerFactoryInterface' => $baseDir . '/src/Log/Handler/HandlerFactoryInterface.php', + 'izi\\prestashop\\Log\\Handler\\RotatingFileHandlerFactory' => $baseDir . '/src/Log/Handler/RotatingFileHandlerFactory.php', + 'izi\\prestashop\\Log\\LoggerFactoryInterface' => $baseDir . '/src/Log/LoggerFactoryInterface.php', + 'izi\\prestashop\\Log\\MonologLoggerFactory' => $baseDir . '/src/Log/MonologLoggerFactory.php', + 'izi\\prestashop\\Mail\\Dto\\MailRecipient' => $baseDir . '/src/Mail/Dto/MailRecipient.php', + 'izi\\prestashop\\Mail\\EventListener\\ReplaceOrderNotificationRecipientListener' => $baseDir . '/src/Mail/EventListener/ReplaceOrderNotificationRecipientListener.php', + 'izi\\prestashop\\Mail\\Event\\SendEmailEvent' => $baseDir . '/src/Mail/Event/SendEmailEvent.php', + 'izi\\prestashop\\Mail\\Resolver\\OrderMailRecipientResolver' => $baseDir . '/src/Mail/Resolver/OrderMailRecipientResolver.php', + 'izi\\prestashop\\MerchantApi\\Command\\AddProductToBasketCommand' => $baseDir . '/src/MerchantApi/Command/AddProductToBasketCommand.php', + 'izi\\prestashop\\MerchantApi\\Command\\Basket\\AddProductToCartCommand' => $baseDir . '/src/MerchantApi/Command/Basket/AddProductToCartCommand.php', + 'izi\\prestashop\\MerchantApi\\Command\\Basket\\CreateCartCommand' => $baseDir . '/src/MerchantApi/Command/Basket/CreateCartCommand.php', + 'izi\\prestashop\\MerchantApi\\Command\\Basket\\IncrementCartQuantityCommand' => $baseDir . '/src/MerchantApi/Command/Basket/IncrementCartQuantityCommand.php', + 'izi\\prestashop\\MerchantApi\\Command\\ConfirmBasketBindingCommand' => $baseDir . '/src/MerchantApi/Command/ConfirmBasketBindingCommand.php', + 'izi\\prestashop\\MerchantApi\\Command\\CreateOrderCommand' => $baseDir . '/src/MerchantApi/Command/CreateOrderCommand.php', + 'izi\\prestashop\\MerchantApi\\Command\\DeleteBasketBindingCommand' => $baseDir . '/src/MerchantApi/Command/DeleteBasketBindingCommand.php', + 'izi\\prestashop\\MerchantApi\\Command\\GetBasketCommand' => $baseDir . '/src/MerchantApi/Command/GetBasketCommand.php', + 'izi\\prestashop\\MerchantApi\\Command\\GetOrderCommand' => $baseDir . '/src/MerchantApi/Command/GetOrderCommand.php', + 'izi\\prestashop\\MerchantApi\\Command\\GetProductsCommand' => $baseDir . '/src/MerchantApi/Command/GetProductsCommand.php', + 'izi\\prestashop\\MerchantApi\\Command\\Order\\UpdateCartMessageCommand' => $baseDir . '/src/MerchantApi/Command/Order/UpdateCartMessageCommand.php', + 'izi\\prestashop\\MerchantApi\\Command\\UpdateBasketCommand' => $baseDir . '/src/MerchantApi/Command/UpdateBasketCommand.php', + 'izi\\prestashop\\MerchantApi\\Command\\UpdateOrderCommand' => $baseDir . '/src/MerchantApi/Command/UpdateOrderCommand.php', + 'izi\\prestashop\\MerchantApi\\EventListener\\UpdateCartRulesListener' => $baseDir . '/src/MerchantApi/EventListener/UpdateCartRulesListener.php', + 'izi\\prestashop\\MerchantApi\\Event\\CartUpdatedEvent' => $baseDir . '/src/MerchantApi/Event/CartUpdatedEvent.php', + 'izi\\prestashop\\MerchantApi\\Exception\\ApiException' => $baseDir . '/src/MerchantApi/Exception/ApiException.php', + 'izi\\prestashop\\MerchantApi\\Exception\\BadGatewayException' => $baseDir . '/src/MerchantApi/Exception/BadGatewayException.php', + 'izi\\prestashop\\MerchantApi\\Exception\\BadRequestException' => $baseDir . '/src/MerchantApi/Exception/BadRequestException.php', + 'izi\\prestashop\\MerchantApi\\Exception\\BasketNotFoundException' => $baseDir . '/src/MerchantApi/Exception/BasketNotFoundException.php', + 'izi\\prestashop\\MerchantApi\\Exception\\CannotAddProductException' => $baseDir . '/src/MerchantApi/Exception/CannotAddProductException.php', + 'izi\\prestashop\\MerchantApi\\Exception\\CannotCreateBasketException' => $baseDir . '/src/MerchantApi/Exception/CannotCreateBasketException.php', + 'izi\\prestashop\\MerchantApi\\Exception\\CannotCreateOrderException' => $baseDir . '/src/MerchantApi/Exception/CannotCreateOrderException.php', + 'izi\\prestashop\\MerchantApi\\Exception\\InternalServerErrorException' => $baseDir . '/src/MerchantApi/Exception/InternalServerErrorException.php', + 'izi\\prestashop\\MerchantApi\\Exception\\InvalidSignatureException' => $baseDir . '/src/MerchantApi/Exception/InvalidSignatureException.php', + 'izi\\prestashop\\MerchantApi\\Exception\\MalformedRequestException' => $baseDir . '/src/MerchantApi/Exception/MalformedRequestException.php', + 'izi\\prestashop\\MerchantApi\\Exception\\OrderExistsException' => $baseDir . '/src/MerchantApi/Exception/OrderExistsException.php', + 'izi\\prestashop\\MerchantApi\\Exception\\OrderNotFoundException' => $baseDir . '/src/MerchantApi/Exception/OrderNotFoundException.php', + 'izi\\prestashop\\MerchantApi\\Exception\\ProductNotFoundException' => $baseDir . '/src/MerchantApi/Exception/ProductNotFoundException.php', + 'izi\\prestashop\\MerchantApi\\Exception\\ProductOutOfStockException' => $baseDir . '/src/MerchantApi/Exception/ProductOutOfStockException.php', + 'izi\\prestashop\\MerchantApi\\Exception\\ServiceUnavailableException' => $baseDir . '/src/MerchantApi/Exception/ServiceUnavailableException.php', + 'izi\\prestashop\\MerchantApi\\Firewall\\MerchantApiAuthenticator' => $baseDir . '/src/MerchantApi/Firewall/MerchantApiAuthenticator.php', + 'izi\\prestashop\\MerchantApi\\Firewall\\SigningKeysService' => $baseDir . '/src/MerchantApi/Firewall/SigningKeysService.php', + 'izi\\prestashop\\MerchantApi\\Firewall\\SigningKeysServiceInterface' => $baseDir . '/src/MerchantApi/Firewall/SigningKeysServiceInterface.php', + 'izi\\prestashop\\MerchantApi\\Handler\\AddProductToBasketHandler' => $baseDir . '/src/MerchantApi/Handler/AddProductToBasketHandler.php', + 'izi\\prestashop\\MerchantApi\\Handler\\AddProductToBasketHandlerInterface' => $baseDir . '/src/MerchantApi/Handler/AddProductToBasketHandlerInterface.php', + 'izi\\prestashop\\MerchantApi\\Handler\\Basket\\AddProductToCartHandler' => $baseDir . '/src/MerchantApi/Handler/Basket/AddProductToCartHandler.php', + 'izi\\prestashop\\MerchantApi\\Handler\\Basket\\AddProductToCartHandlerInterface' => $baseDir . '/src/MerchantApi/Handler/Basket/AddProductToCartHandlerInterface.php', + 'izi\\prestashop\\MerchantApi\\Handler\\Basket\\BasketEventHandler' => $baseDir . '/src/MerchantApi/Handler/Basket/BasketEventHandler.php', + 'izi\\prestashop\\MerchantApi\\Handler\\Basket\\BasketEventHandlerInterface' => $baseDir . '/src/MerchantApi/Handler/Basket/BasketEventHandlerInterface.php', + 'izi\\prestashop\\MerchantApi\\Handler\\Basket\\CreateCartHandler' => $baseDir . '/src/MerchantApi/Handler/Basket/CreateCartHandler.php', + 'izi\\prestashop\\MerchantApi\\Handler\\Basket\\CreateCartHandlerInterface' => $baseDir . '/src/MerchantApi/Handler/Basket/CreateCartHandlerInterface.php', + 'izi\\prestashop\\MerchantApi\\Handler\\Basket\\IncrementCartQuantityHandler' => $baseDir . '/src/MerchantApi/Handler/Basket/IncrementCartQuantityHandler.php', + 'izi\\prestashop\\MerchantApi\\Handler\\Basket\\IncrementCartQuantityHandlerInterface' => $baseDir . '/src/MerchantApi/Handler/Basket/IncrementCartQuantityHandlerInterface.php', + 'izi\\prestashop\\MerchantApi\\Handler\\Basket\\ProductsQuantityEventHandler' => $baseDir . '/src/MerchantApi/Handler/Basket/ProductsQuantityEventHandler.php', + 'izi\\prestashop\\MerchantApi\\Handler\\Basket\\PromoCodesEventHandler' => $baseDir . '/src/MerchantApi/Handler/Basket/PromoCodesEventHandler.php', + 'izi\\prestashop\\MerchantApi\\Handler\\Basket\\RelatedProductsEventHandler' => $baseDir . '/src/MerchantApi/Handler/Basket/RelatedProductsEventHandler.php', + 'izi\\prestashop\\MerchantApi\\Handler\\ConfirmBasketBindingHandler' => $baseDir . '/src/MerchantApi/Handler/ConfirmBasketBindingHandler.php', + 'izi\\prestashop\\MerchantApi\\Handler\\ConfirmBasketBindingHandlerInterface' => $baseDir . '/src/MerchantApi/Handler/ConfirmBasketBindingHandlerInterface.php', + 'izi\\prestashop\\MerchantApi\\Handler\\CreateOrderHandler' => $baseDir . '/src/MerchantApi/Handler/CreateOrderHandler.php', + 'izi\\prestashop\\MerchantApi\\Handler\\CreateOrderHandlerInterface' => $baseDir . '/src/MerchantApi/Handler/CreateOrderHandlerInterface.php', + 'izi\\prestashop\\MerchantApi\\Handler\\DeleteBasketBindingHandler' => $baseDir . '/src/MerchantApi/Handler/DeleteBasketBindingHandler.php', + 'izi\\prestashop\\MerchantApi\\Handler\\DeleteBasketBindingHandlerInterface' => $baseDir . '/src/MerchantApi/Handler/DeleteBasketBindingHandlerInterface.php', + 'izi\\prestashop\\MerchantApi\\Handler\\GetBasketHandler' => $baseDir . '/src/MerchantApi/Handler/GetBasketHandler.php', + 'izi\\prestashop\\MerchantApi\\Handler\\GetBasketHandlerInterface' => $baseDir . '/src/MerchantApi/Handler/GetBasketHandlerInterface.php', + 'izi\\prestashop\\MerchantApi\\Handler\\GetOrderHandler' => $baseDir . '/src/MerchantApi/Handler/GetOrderHandler.php', + 'izi\\prestashop\\MerchantApi\\Handler\\GetOrderHandlerInterface' => $baseDir . '/src/MerchantApi/Handler/GetOrderHandlerInterface.php', + 'izi\\prestashop\\MerchantApi\\Handler\\GetProductsHandler' => $baseDir . '/src/MerchantApi/Handler/GetProductsHandler.php', + 'izi\\prestashop\\MerchantApi\\Handler\\GetProductsHandlerInterface' => $baseDir . '/src/MerchantApi/Handler/GetProductsHandlerInterface.php', + 'izi\\prestashop\\MerchantApi\\Handler\\Order\\UpdateCartMessageHandler' => $baseDir . '/src/MerchantApi/Handler/Order/UpdateCartMessageHandler.php', + 'izi\\prestashop\\MerchantApi\\Handler\\Order\\UpdateCartMessageHandlerInterface' => $baseDir . '/src/MerchantApi/Handler/Order/UpdateCartMessageHandlerInterface.php', + 'izi\\prestashop\\MerchantApi\\Handler\\UpdateBasketHandler' => $baseDir . '/src/MerchantApi/Handler/UpdateBasketHandler.php', + 'izi\\prestashop\\MerchantApi\\Handler\\UpdateBasketHandlerInterface' => $baseDir . '/src/MerchantApi/Handler/UpdateBasketHandlerInterface.php', + 'izi\\prestashop\\MerchantApi\\Handler\\UpdateOrderHandler' => $baseDir . '/src/MerchantApi/Handler/UpdateOrderHandler.php', + 'izi\\prestashop\\MerchantApi\\Handler\\UpdateOrderHandlerInterface' => $baseDir . '/src/MerchantApi/Handler/UpdateOrderHandlerInterface.php', + 'izi\\prestashop\\MerchantApi\\Model\\Basket\\Request\\BasketEvent' => $baseDir . '/src/MerchantApi/Model/Basket/Request/BasketEvent.php', + 'izi\\prestashop\\MerchantApi\\Model\\Basket\\Request\\BasketId' => $baseDir . '/src/MerchantApi/Model/Basket/Request/BasketId.php', + 'izi\\prestashop\\MerchantApi\\Model\\Basket\\Request\\BindingConfirmation' => $baseDir . '/src/MerchantApi/Model/Basket/Request/BindingConfirmation.php', + 'izi\\prestashop\\MerchantApi\\Model\\Basket\\Request\\BindingStatus' => $baseDir . '/src/MerchantApi/Model/Basket/Request/BindingStatus.php', + 'izi\\prestashop\\MerchantApi\\Model\\Basket\\Request\\Browser' => $baseDir . '/src/MerchantApi/Model/Basket/Request/Browser.php', + 'izi\\prestashop\\MerchantApi\\Model\\Basket\\Request\\EventType' => $baseDir . '/src/MerchantApi/Model/Basket/Request/EventType.php', + 'izi\\prestashop\\MerchantApi\\Model\\Basket\\Request\\Quantity' => $baseDir . '/src/MerchantApi/Model/Basket/Request/Quantity.php', + 'izi\\prestashop\\MerchantApi\\Model\\Basket\\Request\\QuantityData' => $baseDir . '/src/MerchantApi/Model/Basket/Request/QuantityData.php', + 'izi\\prestashop\\MerchantApi\\Model\\Basket\\Request\\RelatedProductData' => $baseDir . '/src/MerchantApi/Model/Basket/Request/RelatedProductData.php', + 'izi\\prestashop\\MerchantApi\\Model\\Basket\\Response\\Basket' => $baseDir . '/src/MerchantApi/Model/Basket/Response/Basket.php', + 'izi\\prestashop\\MerchantApi\\Model\\Basket\\Response\\BasketTrait' => $baseDir . '/src/MerchantApi/Model/Basket/Response/BasketTrait.php', + 'izi\\prestashop\\MerchantApi\\Model\\Basket\\Response\\IdentifiableBasket' => $baseDir . '/src/MerchantApi/Model/Basket/Response/IdentifiableBasket.php', + 'izi\\prestashop\\MerchantApi\\Model\\Order\\Request\\AccountInfo' => $baseDir . '/src/MerchantApi/Model/Order/Request/AccountInfo.php', + 'izi\\prestashop\\MerchantApi\\Model\\Order\\Request\\AddressDetails' => $baseDir . '/src/MerchantApi/Model/Order/Request/AddressDetails.php', + 'izi\\prestashop\\MerchantApi\\Model\\Order\\Request\\ClientAddress' => $baseDir . '/src/MerchantApi/Model/Order/Request/ClientAddress.php', + 'izi\\prestashop\\MerchantApi\\Model\\Order\\Request\\CreateOrderRequest' => $baseDir . '/src/MerchantApi/Model/Order/Request/CreateOrderRequest.php', + 'izi\\prestashop\\MerchantApi\\Model\\Order\\Request\\Delivery' => $baseDir . '/src/MerchantApi/Model/Order/Request/Delivery.php', + 'izi\\prestashop\\MerchantApi\\Model\\Order\\Request\\DeliveryAddress' => $baseDir . '/src/MerchantApi/Model/Order/Request/DeliveryAddress.php', + 'izi\\prestashop\\MerchantApi\\Model\\Order\\Request\\EventData' => $baseDir . '/src/MerchantApi/Model/Order/Request/EventData.php', + 'izi\\prestashop\\MerchantApi\\Model\\Order\\Request\\OrderDetails' => $baseDir . '/src/MerchantApi/Model/Order/Request/OrderDetails.php', + 'izi\\prestashop\\MerchantApi\\Model\\Order\\Request\\OrderEvent' => $baseDir . '/src/MerchantApi/Model/Order/Request/OrderEvent.php', + 'izi\\prestashop\\MerchantApi\\Model\\Order\\Request\\OrderStatus' => $baseDir . '/src/MerchantApi/Model/Order/Request/OrderStatus.php', + 'izi\\prestashop\\MerchantApi\\Model\\Order\\Request\\PaymentStatus' => $baseDir . '/src/MerchantApi/Model/Order/Request/PaymentStatus.php', + 'izi\\prestashop\\MerchantApi\\Model\\Order\\Response\\Delivery' => $baseDir . '/src/MerchantApi/Model/Order/Response/Delivery.php', + 'izi\\prestashop\\MerchantApi\\Model\\Order\\Response\\Order' => $baseDir . '/src/MerchantApi/Model/Order/Response/Order.php', + 'izi\\prestashop\\MerchantApi\\Model\\Order\\Response\\OrderDetails' => $baseDir . '/src/MerchantApi/Model/Order/Response/OrderDetails.php', + 'izi\\prestashop\\MerchantApi\\Model\\Order\\Response\\OrderStatusData' => $baseDir . '/src/MerchantApi/Model/Order/Response/OrderStatusData.php', + 'izi\\prestashop\\MerchantApi\\Model\\Product\\Response\\Products' => $baseDir . '/src/MerchantApi/Model/Product/Response/Products.php', + 'izi\\prestashop\\Module\\Exception\\ModuleErrorInterface' => $baseDir . '/src/Module/Exception/ModuleErrorInterface.php', + 'izi\\prestashop\\Module\\Exception\\PrestaShopModuleErrorException' => $baseDir . '/src/Module/Exception/PrestaShopModuleErrorException.php', + 'izi\\prestashop\\Module\\ModuleRepository' => $baseDir . '/src/Module/ModuleRepository.php', + 'izi\\prestashop\\OAuth2\\Authentication\\AuthenticationMethodInterface' => $baseDir . '/src/OAuth2/Authentication/AuthenticationMethodInterface.php', + 'izi\\prestashop\\OAuth2\\Authentication\\ClientCredentials' => $baseDir . '/src/OAuth2/Authentication/ClientCredentials.php', + 'izi\\prestashop\\OAuth2\\Authentication\\ClientCredentialsInterface' => $baseDir . '/src/OAuth2/Authentication/ClientCredentialsInterface.php', + 'izi\\prestashop\\OAuth2\\Authentication\\ClientCredentialsRepositoryInterface' => $baseDir . '/src/OAuth2/Authentication/ClientCredentialsRepositoryInterface.php', + 'izi\\prestashop\\OAuth2\\Authentication\\ClientSecretPost' => $baseDir . '/src/OAuth2/Authentication/ClientSecretPost.php', + 'izi\\prestashop\\OAuth2\\Authentication\\None' => $baseDir . '/src/OAuth2/Authentication/None.php', + 'izi\\prestashop\\OAuth2\\AuthorizationProvider' => $baseDir . '/src/OAuth2/AuthorizationProvider.php', + 'izi\\prestashop\\OAuth2\\AuthorizationProviderFactoryInterface' => $baseDir . '/src/OAuth2/AuthorizationProviderFactoryInterface.php', + 'izi\\prestashop\\OAuth2\\AuthorizationProviderInterface' => $baseDir . '/src/OAuth2/AuthorizationProviderInterface.php', + 'izi\\prestashop\\OAuth2\\AuthorizationServerClient' => $baseDir . '/src/OAuth2/AuthorizationServerClient.php', + 'izi\\prestashop\\OAuth2\\AuthorizationServerClientInterface' => $baseDir . '/src/OAuth2/AuthorizationServerClientInterface.php', + 'izi\\prestashop\\OAuth2\\Exception\\AccessTokenRequestException' => $baseDir . '/src/OAuth2/Exception/AccessTokenRequestException.php', + 'izi\\prestashop\\OAuth2\\Exception\\NetworkException' => $baseDir . '/src/OAuth2/Exception/NetworkException.php', + 'izi\\prestashop\\OAuth2\\Exception\\OAuth2ExceptionInterface' => $baseDir . '/src/OAuth2/Exception/OAuth2ExceptionInterface.php', + 'izi\\prestashop\\OAuth2\\Exception\\UnexpectedValueException' => $baseDir . '/src/OAuth2/Exception/UnexpectedValueException.php', + 'izi\\prestashop\\OAuth2\\Grant\\AbstractGrant' => $baseDir . '/src/OAuth2/Grant/AbstractGrant.php', + 'izi\\prestashop\\OAuth2\\Grant\\ClientCredentialsGrant' => $baseDir . '/src/OAuth2/Grant/ClientCredentialsGrant.php', + 'izi\\prestashop\\OAuth2\\Grant\\GrantTypeInterface' => $baseDir . '/src/OAuth2/Grant/GrantTypeInterface.php', + 'izi\\prestashop\\OAuth2\\Grant\\RefreshTokenGrant' => $baseDir . '/src/OAuth2/Grant/RefreshTokenGrant.php', + 'izi\\prestashop\\OAuth2\\LazyAuthorizationProvider' => $baseDir . '/src/OAuth2/LazyAuthorizationProvider.php', + 'izi\\prestashop\\OAuth2\\Token\\AccessTokenFactoryInterface' => $baseDir . '/src/OAuth2/Token/AccessTokenFactoryInterface.php', + 'izi\\prestashop\\OAuth2\\Token\\AccessTokenFactoryTrait' => $baseDir . '/src/OAuth2/Token/AccessTokenFactoryTrait.php', + 'izi\\prestashop\\OAuth2\\Token\\AccessTokenInterface' => $baseDir . '/src/OAuth2/Token/AccessTokenInterface.php', + 'izi\\prestashop\\OAuth2\\Token\\AccessTokenRepositoryInterface' => $baseDir . '/src/OAuth2/Token/AccessTokenRepositoryInterface.php', + 'izi\\prestashop\\OAuth2\\Token\\AccessTokenTrait' => $baseDir . '/src/OAuth2/Token/AccessTokenTrait.php', + 'izi\\prestashop\\OAuth2\\Token\\BearerToken' => $baseDir . '/src/OAuth2/Token/BearerToken.php', + 'izi\\prestashop\\OAuth2\\Token\\BearerTokenFactory' => $baseDir . '/src/OAuth2/Token/BearerTokenFactory.php', + 'izi\\prestashop\\OAuth2\\Token\\InMemoryTokenRepository' => $baseDir . '/src/OAuth2/Token/InMemoryTokenRepository.php', + 'izi\\prestashop\\OAuth2\\UriCollection' => $baseDir . '/src/OAuth2/UriCollection.php', + 'izi\\prestashop\\OAuth2\\UriCollectionInterface' => $baseDir . '/src/OAuth2/UriCollectionInterface.php', + 'izi\\prestashop\\ObjectModel\\Entity\\InPostIziBasketSession' => $baseDir . '/src/ObjectModel/Entity/InPostIziBasketSession.php', + 'izi\\prestashop\\ObjectModel\\Exception\\InvalidDataException' => $baseDir . '/src/ObjectModel/Exception/InvalidDataException.php', + 'izi\\prestashop\\ObjectModel\\Hydrator' => $baseDir . '/src/ObjectModel/Hydrator.php', + 'izi\\prestashop\\ObjectModel\\HydratorInterface' => $baseDir . '/src/ObjectModel/HydratorInterface.php', + 'izi\\prestashop\\ObjectModel\\ObjectManager' => $baseDir . '/src/ObjectModel/ObjectManager.php', + 'izi\\prestashop\\ObjectModel\\ObjectManagerInterface' => $baseDir . '/src/ObjectModel/ObjectManagerInterface.php', + 'izi\\prestashop\\ObjectModel\\OrderMaintainingLoaderTrait' => $baseDir . '/src/ObjectModel/OrderMaintainingLoaderTrait.php', + 'izi\\prestashop\\ObjectModel\\Query' => $baseDir . '/src/ObjectModel/Query.php', + 'izi\\prestashop\\ObjectModel\\QueryBuilder' => $baseDir . '/src/ObjectModel/QueryBuilder.php', + 'izi\\prestashop\\ObjectModel\\Repository\\CarrierRepository' => $baseDir . '/src/ObjectModel/Repository/CarrierRepository.php', + 'izi\\prestashop\\ObjectModel\\Repository\\CartRuleRepository' => $baseDir . '/src/ObjectModel/Repository/CartRuleRepository.php', + 'izi\\prestashop\\ObjectModel\\Repository\\CmsPageRepository' => $baseDir . '/src/ObjectModel/Repository/CmsPageRepository.php', + 'izi\\prestashop\\ObjectModel\\Repository\\CombinationRepository' => $baseDir . '/src/ObjectModel/Repository/CombinationRepository.php', + 'izi\\prestashop\\ObjectModel\\Repository\\ConfigurationRepository' => $baseDir . '/src/ObjectModel/Repository/ConfigurationRepository.php', + 'izi\\prestashop\\ObjectModel\\Repository\\CurrencyRepository' => $baseDir . '/src/ObjectModel/Repository/CurrencyRepository.php', + 'izi\\prestashop\\ObjectModel\\Repository\\HookRepository' => $baseDir . '/src/ObjectModel/Repository/HookRepository.php', + 'izi\\prestashop\\ObjectModel\\Repository\\ImageTypeRepository' => $baseDir . '/src/ObjectModel/Repository/ImageTypeRepository.php', + 'izi\\prestashop\\ObjectModel\\Repository\\ObjectRepository' => $baseDir . '/src/ObjectModel/Repository/ObjectRepository.php', + 'izi\\prestashop\\ObjectModel\\Repository\\ObjectRepositoryFactory' => $baseDir . '/src/ObjectModel/Repository/ObjectRepositoryFactory.php', + 'izi\\prestashop\\ObjectModel\\Repository\\ObjectRepositoryFactoryInterface' => $baseDir . '/src/ObjectModel/Repository/ObjectRepositoryFactoryInterface.php', + 'izi\\prestashop\\ObjectModel\\Repository\\ObjectRepositoryInterface' => $baseDir . '/src/ObjectModel/Repository/ObjectRepositoryInterface.php', + 'izi\\prestashop\\ObjectModel\\Repository\\ProductRepository' => $baseDir . '/src/ObjectModel/Repository/ProductRepository.php', + 'izi\\prestashop\\ObjectModel\\Repository\\RangePriceRepository' => $baseDir . '/src/ObjectModel/Repository/RangePriceRepository.php', + 'izi\\prestashop\\ObjectModel\\Repository\\RangeWeightRepository' => $baseDir . '/src/ObjectModel/Repository/RangeWeightRepository.php', + 'izi\\prestashop\\ObjectModel\\Repository\\ShipmentRepository' => $baseDir . '/src/ObjectModel/Repository/ShipmentRepository.php', + 'izi\\prestashop\\Order\\Address\\AddressDataMapper' => $baseDir . '/src/Order/Address/AddressDataMapper.php', + 'izi\\prestashop\\Order\\ContextCustomerUpdater' => $baseDir . '/src/Order/ContextCustomerUpdater.php', + 'izi\\prestashop\\Order\\Message\\ExpressionLanguage' => $baseDir . '/src/Order/Message/ExpressionLanguage.php', + 'izi\\prestashop\\Order\\Message\\Message' => $baseDir . '/src/Order/Message/Message.php', + 'izi\\prestashop\\Order\\Message\\MessageFormatter' => $baseDir . '/src/Order/Message/MessageFormatter.php', + 'izi\\prestashop\\Order\\Message\\MessageFormatterInterface' => $baseDir . '/src/Order/Message/MessageFormatterInterface.php', + 'izi\\prestashop\\Order\\Message\\ParameterDescriptorInterface' => $baseDir . '/src/Order/Message/ParameterDescriptorInterface.php', + 'izi\\prestashop\\Order\\Message\\ParametersExtractor' => $baseDir . '/src/Order/Message/ParametersExtractor.php', + 'izi\\prestashop\\Order\\Message\\ParametersExtractorInterface' => $baseDir . '/src/Order/Message/ParametersExtractorInterface.php', + 'izi\\prestashop\\Order\\Message\\Processor\\ConditionalBlockProcessor' => $baseDir . '/src/Order/Message/Processor/ConditionalBlockProcessor.php', + 'izi\\prestashop\\Order\\Message\\Processor\\ExpressionLanguageProcessor' => $baseDir . '/src/Order/Message/Processor/ExpressionLanguageProcessor.php', + 'izi\\prestashop\\Order\\Message\\Processor\\ParameterReplacementProcessor' => $baseDir . '/src/Order/Message/Processor/ParameterReplacementProcessor.php', + 'izi\\prestashop\\Order\\Message\\Processor\\ProcessorInterface' => $baseDir . '/src/Order/Message/Processor/ProcessorInterface.php', + 'izi\\prestashop\\Payment\\PaymentCurrencyChecker' => $baseDir . '/src/Payment/PaymentCurrencyChecker.php', + 'izi\\prestashop\\PrestashopOrder' => $baseDir . '/src/PrestashopOrder.php', + 'izi\\prestashop\\Product\\Event\\CombinationEvent' => $baseDir . '/src/Product/Event/CombinationEvent.php', + 'izi\\prestashop\\Product\\Event\\ImageEvent' => $baseDir . '/src/Product/Event/ImageEvent.php', + 'izi\\prestashop\\Product\\Event\\ProductEvent' => $baseDir . '/src/Product/Event/ProductEvent.php', + 'izi\\prestashop\\Product\\Event\\SpecificPriceEvent' => $baseDir . '/src/Product/Event/SpecificPriceEvent.php', + 'izi\\prestashop\\Product\\Event\\StockQuantityUpdatedEvent' => $baseDir . '/src/Product/Event/StockQuantityUpdatedEvent.php', + 'izi\\prestashop\\Product\\Image\\ImageUrls' => $baseDir . '/src/Product/Image/ImageUrls.php', + 'izi\\prestashop\\Product\\Image\\ImageUrlsProvider' => $baseDir . '/src/Product/Image/ImageUrlsProvider.php', + 'izi\\prestashop\\Product\\Image\\ImageUrlsProviderInterface' => $baseDir . '/src/Product/Image/ImageUrlsProviderInterface.php', + 'izi\\prestashop\\Product\\Price\\BatchLowestPriceProviderInterface' => $baseDir . '/src/Product/Price/BatchLowestPriceProviderInterface.php', + 'izi\\prestashop\\Product\\Price\\CalculationParameters' => $baseDir . '/src/Product/Price/CalculationParameters.php', + 'izi\\prestashop\\Product\\Price\\ErrorHandlingLowestPriceProvider' => $baseDir . '/src/Product/Price/ErrorHandlingLowestPriceProvider.php', + 'izi\\prestashop\\Product\\Price\\LowestPriceProviderFactory' => $baseDir . '/src/Product/Price/LowestPriceProviderFactory.php', + 'izi\\prestashop\\Product\\Price\\LowestPriceProviderInterface' => $baseDir . '/src/Product/Price/LowestPriceProviderInterface.php', + 'izi\\prestashop\\Product\\Price\\LowestPriceQuery' => $baseDir . '/src/Product/Price/LowestPriceQuery.php', + 'izi\\prestashop\\Product\\Price\\NullLowestPriceProvider' => $baseDir . '/src/Product/Price/NullLowestPriceProvider.php', + 'izi\\prestashop\\Product\\Price\\PriceCalculator' => $baseDir . '/src/Product/Price/PriceCalculator.php', + 'izi\\prestashop\\Product\\Price\\PriceCalculatorInterface' => $baseDir . '/src/Product/Price/PriceCalculatorInterface.php', + 'izi\\prestashop\\Product\\Price\\PriceQuery' => $baseDir . '/src/Product/Price/PriceQuery.php', + 'izi\\prestashop\\Product\\Price\\X13PriceHistoryLowestPriceProvider' => $baseDir . '/src/Product/Price/X13PriceHistoryLowestPriceProvider.php', + 'izi\\prestashop\\Product\\ProductAttribute' => $baseDir . '/src/Product/ProductAttribute.php', + 'izi\\prestashop\\Product\\ProductType' => $baseDir . '/src/Product/ProductType.php', + 'izi\\prestashop\\Product\\ProductWithCombination' => $baseDir . '/src/Product/ProductWithCombination.php', + 'izi\\prestashop\\Product\\ReferenceId' => $baseDir . '/src/Product/ReferenceId.php', + 'izi\\prestashop\\Product\\Util\\AttributeListParser' => $baseDir . '/src/Product/Util/AttributeListParser.php', + 'izi\\prestashop\\Product\\Util\\DescriptionFormatter' => $baseDir . '/src/Product/Util/DescriptionFormatter.php', + 'izi\\prestashop\\PromoCode\\AvailableCartRulesProvider' => $baseDir . '/src/PromoCode/AvailableCartRulesProvider.php', + 'izi\\prestashop\\PromoCode\\AvailablePromotionsProviderInterface' => $baseDir . '/src/PromoCode/AvailablePromotionsProviderInterface.php', + 'izi\\prestashop\\PromoCode\\CartRuleOptions' => $baseDir . '/src/PromoCode/CartRuleOptions.php', + 'izi\\prestashop\\PromoCode\\CartRuleOptionsRepository' => $baseDir . '/src/PromoCode/CartRuleOptionsRepository.php', + 'izi\\prestashop\\PromoCode\\CartRuleOptionsRepositoryInterface' => $baseDir . '/src/PromoCode/CartRuleOptionsRepositoryInterface.php', + 'izi\\prestashop\\PromoCode\\CartRulePromoCodeProvider' => $baseDir . '/src/PromoCode/CartRulePromoCodeProvider.php', + 'izi\\prestashop\\PromoCode\\NullAvailablePromotionsProvider' => $baseDir . '/src/PromoCode/NullAvailablePromotionsProvider.php', + 'izi\\prestashop\\PromoCode\\PromoCodeProviderInterface' => $baseDir . '/src/PromoCode/PromoCodeProviderInterface.php', + 'izi\\prestashop\\Repository\\BasketSessionRepository' => $baseDir . '/src/Repository/BasketSessionRepository.php', + 'izi\\prestashop\\Repository\\BasketSessionRepositoryInterface' => $baseDir . '/src/Repository/BasketSessionRepositoryInterface.php', + 'izi\\prestashop\\Repository\\CartRuleRepository' => $baseDir . '/src/Repository/CartRuleRepository.php', + 'izi\\prestashop\\Repository\\CartRuleRepositoryInterface' => $baseDir . '/src/Repository/CartRuleRepositoryInterface.php', + 'izi\\prestashop\\Repository\\OrderDataRepositoryInterface' => $baseDir . '/src/Repository/OrderDataRepositoryInterface.php', + 'izi\\prestashop\\Repository\\ProductRestrictionsRepository' => $baseDir . '/src/Repository/ProductRestrictionsRepository.php', + 'izi\\prestashop\\Repository\\ProductRestrictionsRepositoryInterface' => $baseDir . '/src/Repository/ProductRestrictionsRepositoryInterface.php', + 'izi\\prestashop\\Repository\\Product\\AttributeRestrictionsRepositoryInterface' => $baseDir . '/src/Repository/Product/AttributeRestrictionsRepositoryInterface.php', + 'izi\\prestashop\\Repository\\Product\\CategoryRestrictionsRepositoryInterface' => $baseDir . '/src/Repository/Product/CategoryRestrictionsRepositoryInterface.php', + 'izi\\prestashop\\Repository\\Product\\FeatureRestrictionsRepositoryInterface' => $baseDir . '/src/Repository/Product/FeatureRestrictionsRepositoryInterface.php', + 'izi\\prestashop\\Repository\\Product\\ManufacturerRestrictionsRepositoryInterface' => $baseDir . '/src/Repository/Product/ManufacturerRestrictionsRepositoryInterface.php', + 'izi\\prestashop\\Routing\\AdminUrlGenerator' => $baseDir . '/src/Routing/AdminUrlGenerator.php', + 'izi\\prestashop\\Routing\\AnnotationDirectoryLoader' => $baseDir . '/src/Routing/AnnotationDirectoryLoader.php', + 'izi\\prestashop\\Security\\AuthorizationChecker' => $baseDir . '/src/Security/AuthorizationChecker.php', + 'izi\\prestashop\\Security\\EmployeeAuthenticator' => $baseDir . '/src/Security/EmployeeAuthenticator.php', + 'izi\\prestashop\\Security\\LazyUserProvider' => $baseDir . '/src/Security/LazyUserProvider.php', + 'izi\\prestashop\\Security\\Voter\\BindingWidgetVoter' => $baseDir . '/src/Security/Voter/BindingWidgetVoter.php', + 'izi\\prestashop\\Serializer\\Exception\\MissingConstructorArgumentsException' => $baseDir . '/src/Serializer/Exception/MissingConstructorArgumentsException.php', + 'izi\\prestashop\\Serializer\\Normalizer\\BasketAppPaginationPageDenormalizer' => $baseDir . '/src/Serializer/Normalizer/BasketAppPaginationPageDenormalizer.php', + 'izi\\prestashop\\Serializer\\Normalizer\\CustomDenormalizer' => $baseDir . '/src/Serializer/Normalizer/CustomDenormalizer.php', + 'izi\\prestashop\\Serializer\\Normalizer\\DateTimeNormalizer' => $baseDir . '/src/Serializer/Normalizer/DateTimeNormalizer.php', + 'izi\\prestashop\\Serializer\\Normalizer\\DenormalizableInterface' => $baseDir . '/src/Serializer/Normalizer/DenormalizableInterface.php', + 'izi\\prestashop\\Serializer\\Normalizer\\EnumDenormalizer' => $baseDir . '/src/Serializer/Normalizer/EnumDenormalizer.php', + 'izi\\prestashop\\Serializer\\Normalizer\\JsonSerializableNormalizer' => $baseDir . '/src/Serializer/Normalizer/JsonSerializableNormalizer.php', + 'izi\\prestashop\\Serializer\\Normalizer\\ObjectNormalizer' => $baseDir . '/src/Serializer/Normalizer/ObjectNormalizer.php', + 'izi\\prestashop\\Serializer\\Normalizer\\PriceAmountNormalizer' => $baseDir . '/src/Serializer/Normalizer/PriceAmountNormalizer.php', + 'izi\\prestashop\\Serializer\\Normalizer\\PriceNormalizer' => $baseDir . '/src/Serializer/Normalizer/PriceNormalizer.php', + 'izi\\prestashop\\Serializer\\PropertyDocBlockTypeExtractor' => $baseDir . '/src/Serializer/PropertyDocBlockTypeExtractor.php', + 'izi\\prestashop\\Serializer\\SafeDeserializerTrait' => $baseDir . '/src/Serializer/SafeDeserializerTrait.php', + 'izi\\prestashop\\Serializer\\SerializerFactory' => $baseDir . '/src/Serializer/SerializerFactory.php', + 'izi\\prestashop\\Shipping\\CarrierModuleTrackingNumberProvider' => $baseDir . '/src/Shipping/CarrierModuleTrackingNumberProvider.php', + 'izi\\prestashop\\Shipping\\CartTotal\\CartTotalDeliveryStrategyInterface' => $baseDir . '/src/Shipping/CartTotal/CartTotalDeliveryStrategyInterface.php', + 'izi\\prestashop\\Shipping\\CartTotal\\GenericStrategy' => $baseDir . '/src/Shipping/CartTotal/GenericStrategy.php', + 'izi\\prestashop\\Shipping\\CartTotal\\PriceRangeStrategy' => $baseDir . '/src/Shipping/CartTotal/PriceRangeStrategy.php', + 'izi\\prestashop\\Shipping\\CartWeight\\CartWeightDeliveryStrategyInterface' => $baseDir . '/src/Shipping/CartWeight/CartWeightDeliveryStrategyInterface.php', + 'izi\\prestashop\\Shipping\\CartWeight\\GenericStrategy' => $baseDir . '/src/Shipping/CartWeight/GenericStrategy.php', + 'izi\\prestashop\\Shipping\\CartWeight\\WeightRangeStrategy' => $baseDir . '/src/Shipping/CartWeight/WeightRangeStrategy.php', + 'izi\\prestashop\\Shipping\\DeliveryPriceCalculator' => $baseDir . '/src/Shipping/DeliveryPriceCalculator.php', + 'izi\\prestashop\\Shipping\\DeliveryPriceCalculatorInterface' => $baseDir . '/src/Shipping/DeliveryPriceCalculatorInterface.php', + 'izi\\prestashop\\Shipping\\Exception\\UnavailableDeliveryOptionException' => $baseDir . '/src/Shipping/Exception/UnavailableDeliveryOptionException.php', + 'izi\\prestashop\\Shipping\\FreeDelivery\\GenericStrategy' => $baseDir . '/src/Shipping/FreeDelivery/GenericStrategy.php', + 'izi\\prestashop\\Shipping\\FreeDelivery\\MinAmountCalculationStrategyInterface' => $baseDir . '/src/Shipping/FreeDelivery/MinAmountCalculationStrategyInterface.php', + 'izi\\prestashop\\Shipping\\FreeDelivery\\NullStrategy' => $baseDir . '/src/Shipping/FreeDelivery/NullStrategy.php', + 'izi\\prestashop\\Shipping\\FreeDelivery\\PriceRangeStrategy' => $baseDir . '/src/Shipping/FreeDelivery/PriceRangeStrategy.php', + 'izi\\prestashop\\Shipping\\ProductDimensions\\GenericStrategy' => $baseDir . '/src/Shipping/ProductDimensions/GenericStrategy.php', + 'izi\\prestashop\\Shipping\\ProductDimensions\\ProductDimensionsDeliveryStrategyInterface' => $baseDir . '/src/Shipping/ProductDimensions/ProductDimensionsDeliveryStrategyInterface.php', + 'izi\\prestashop\\Shipping\\ProductRestriction\\ProductRestrictionDelivery' => $baseDir . '/src/Shipping/ProductRestriction/ProductRestrictionDelivery.php', + 'izi\\prestashop\\Shipping\\ProductRestriction\\ProductRestrictionDeliveryInterface' => $baseDir . '/src/Shipping/ProductRestriction/ProductRestrictionDeliveryInterface.php', + 'izi\\prestashop\\Shipping\\TrackingNumberProviderInterface' => $baseDir . '/src/Shipping/TrackingNumberProviderInterface.php', + 'izi\\prestashop\\Translation\\DomainNormalizingTranslator' => $baseDir . '/src/Translation/DomainNormalizingTranslator.php', + 'izi\\prestashop\\Translation\\LegacyTranslator' => $baseDir . '/src/Translation/LegacyTranslator.php', + 'izi\\prestashop\\Translation\\PaymentTypeTranslator' => $baseDir . '/src/Translation/PaymentTypeTranslator.php', + 'izi\\prestashop\\Translation\\ServiceNameTranslator' => $baseDir . '/src/Translation/ServiceNameTranslator.php', + 'izi\\prestashop\\Twig\\Extension\\LegacyTranslationExtension' => $baseDir . '/src/Twig/Extension/LegacyTranslationExtension.php', + 'izi\\prestashop\\Twig\\Loader\\TemplateNameMappingLoader' => $baseDir . '/src/Twig/Loader/TemplateNameMappingLoader.php', + 'izi\\prestashop\\Uuid\\Uuid' => $baseDir . '/src/Uuid/Uuid.php', + 'izi\\prestashop\\Uuid\\UuidV4' => $baseDir . '/src/Uuid/UuidV4.php', + 'izi\\prestashop\\Validator\\Cart\\Bindable' => $baseDir . '/src/Validator/Cart/Bindable.php', + 'izi\\prestashop\\Validator\\Cart\\BindableValidator' => $baseDir . '/src/Validator/Cart/BindableValidator.php', + 'izi\\prestashop\\Validator\\Cart\\HasProducts' => $baseDir . '/src/Validator/Cart/HasProducts.php', + 'izi\\prestashop\\Validator\\Cart\\HasProductsValidator' => $baseDir . '/src/Validator/Cart/HasProductsValidator.php', + 'izi\\prestashop\\Validator\\Cart\\HasUnrestrictedProduct' => $baseDir . '/src/Validator/Cart/HasUnrestrictedProduct.php', + 'izi\\prestashop\\Validator\\Cart\\HasUnrestrictedProductValidator' => $baseDir . '/src/Validator/Cart/HasUnrestrictedProductValidator.php', + 'izi\\prestashop\\Validator\\Cart\\PaymentInCurrencyAvailable' => $baseDir . '/src/Validator/Cart/PaymentInCurrencyAvailable.php', + 'izi\\prestashop\\Validator\\Cart\\PaymentInCurrencyAvailableValidator' => $baseDir . '/src/Validator/Cart/PaymentInCurrencyAvailableValidator.php', + 'izi\\prestashop\\Validator\\Consent\\DescriptionUsesIdPlaceholders' => $baseDir . '/src/Validator/Consent/DescriptionUsesIdPlaceholders.php', + 'izi\\prestashop\\Validator\\Consent\\DescriptionUsesIdPlaceholdersValidator' => $baseDir . '/src/Validator/Consent/DescriptionUsesIdPlaceholdersValidator.php', + 'izi\\prestashop\\Validator\\Consent\\UniqueIdentifiers' => $baseDir . '/src/Validator/Consent/UniqueIdentifiers.php', + 'izi\\prestashop\\Validator\\Consent\\UniqueIdentifiersValidator' => $baseDir . '/src/Validator/Consent/UniqueIdentifiersValidator.php', + 'izi\\prestashop\\Validator\\ConstraintValidatorFactory' => $baseDir . '/src/Validator/ConstraintValidatorFactory.php', + 'izi\\prestashop\\Validator\\InPostApiCredentials' => $baseDir . '/src/Validator/InPostApiCredentials.php', + 'izi\\prestashop\\Validator\\InPostApiCredentialsValidator' => $baseDir . '/src/Validator/InPostApiCredentialsValidator.php', + 'izi\\prestashop\\Validator\\NotBlankInDefaultLanguage' => $baseDir . '/src/Validator/NotBlankInDefaultLanguage.php', + 'izi\\prestashop\\Validator\\NotBlankInDefaultLanguageValidator' => $baseDir . '/src/Validator/NotBlankInDefaultLanguageValidator.php', + 'izi\\prestashop\\Validator\\ProcessableMessageFormat' => $baseDir . '/src/Validator/ProcessableMessageFormat.php', + 'izi\\prestashop\\Validator\\ProcessableMessageFormatValidator' => $baseDir . '/src/Validator/ProcessableMessageFormatValidator.php', + 'izi\\prestashop\\Validator\\Product\\NotFromRestrictedManufacturer' => $baseDir . '/src/Validator/Product/NotFromRestrictedManufacturer.php', + 'izi\\prestashop\\Validator\\Product\\NotFromRestrictedManufacturerValidator' => $baseDir . '/src/Validator/Product/NotFromRestrictedManufacturerValidator.php', + 'izi\\prestashop\\Validator\\Product\\NotInRestrictedCategory' => $baseDir . '/src/Validator/Product/NotInRestrictedCategory.php', + 'izi\\prestashop\\Validator\\Product\\NotInRestrictedCategoryValidator' => $baseDir . '/src/Validator/Product/NotInRestrictedCategoryValidator.php', + 'izi\\prestashop\\Validator\\Product\\NotOfType' => $baseDir . '/src/Validator/Product/NotOfType.php', + 'izi\\prestashop\\Validator\\Product\\NotOfTypeValidator' => $baseDir . '/src/Validator/Product/NotOfTypeValidator.php', + 'izi\\prestashop\\Validator\\Product\\NotWithRestrictedAttributes' => $baseDir . '/src/Validator/Product/NotWithRestrictedAttributes.php', + 'izi\\prestashop\\Validator\\Product\\NotWithRestrictedAttributesValidator' => $baseDir . '/src/Validator/Product/NotWithRestrictedAttributesValidator.php', + 'izi\\prestashop\\Validator\\Product\\NotWithRestrictedFeatures' => $baseDir . '/src/Validator/Product/NotWithRestrictedFeatures.php', + 'izi\\prestashop\\Validator\\Product\\NotWithRestrictedFeaturesValidator' => $baseDir . '/src/Validator/Product/NotWithRestrictedFeaturesValidator.php', + 'izi\\prestashop\\Validator\\Product\\Unrestricted' => $baseDir . '/src/Validator/Product/Unrestricted.php', + 'izi\\prestashop\\Validator\\Product\\UnrestrictedValidator' => $baseDir . '/src/Validator/Product/UnrestrictedValidator.php', + 'izi\\prestashop\\Validator\\Sequentially' => $baseDir . '/src/Validator/Sequentially.php', + 'izi\\prestashop\\Validator\\SequentiallyValidator' => $baseDir . '/src/Validator/SequentiallyValidator.php', + 'izi\\prestashop\\Validator\\Unique' => $baseDir . '/src/Validator/Unique.php', + 'izi\\prestashop\\Validator\\UniqueValidator' => $baseDir . '/src/Validator/UniqueValidator.php', + 'izi\\prestashop\\Validator\\ValidatorFactory' => $baseDir . '/src/Validator/ValidatorFactory.php', + 'izi\\prestashop\\View\\Asset\\AbstractAssetManager' => $baseDir . '/src/View/Asset/AbstractAssetManager.php', + 'izi\\prestashop\\View\\Asset\\AdminAssetManager' => $baseDir . '/src/View/Asset/AdminAssetManager.php', + 'izi\\prestashop\\View\\Asset\\AssetManagerInterface' => $baseDir . '/src/View/Asset/AssetManagerInterface.php', + 'izi\\prestashop\\View\\Asset\\FrontAssetManager' => $baseDir . '/src/View/Asset/FrontAssetManager.php', + 'izi\\prestashop\\View\\Asset\\Provider\\Admin\\CartRulesAssetsProvider' => $baseDir . '/src/View/Asset/Provider/Admin/CartRulesAssetsProvider.php', + 'izi\\prestashop\\View\\Asset\\Provider\\AssetsProviderInterface' => $baseDir . '/src/View/Asset/Provider/AssetsProviderInterface.php', + 'izi\\prestashop\\View\\Asset\\Provider\\DTO\\Assets' => $baseDir . '/src/View/Asset/Provider/DTO/Assets.php', + 'izi\\prestashop\\View\\Asset\\Provider\\Front\\CommonAssetsProvider' => $baseDir . '/src/View/Asset/Provider/Front/CommonAssetsProvider.php', + 'izi\\prestashop\\View\\Asset\\Provider\\Front\\ProductPageAssetsProvider' => $baseDir . '/src/View/Asset/Provider/Front/ProductPageAssetsProvider.php', + 'izi\\prestashop\\View\\Asset\\Provider\\Front\\WidgetConfigurationProvider' => $baseDir . '/src/View/Asset/Provider/Front/WidgetConfigurationProvider.php', + 'izi\\prestashop\\View\\Asset\\VersionStrategy\\JsonManifestVersionStrategy' => $baseDir . '/src/View/Asset/VersionStrategy/JsonManifestVersionStrategy.php', + 'izi\\prestashop\\View\\Templating\\RendererInterface' => $baseDir . '/src/View/Templating/RendererInterface.php', + 'izi\\prestashop\\View\\Templating\\SmartyRenderer' => $baseDir . '/src/View/Templating/SmartyRenderer.php', + 'izi\\prestashop\\View\\Widget\\FrameStyle' => $baseDir . '/src/View/Widget/FrameStyle.php', + 'izi\\prestashop\\View\\Widget\\Size' => $baseDir . '/src/View/Widget/Size.php', + 'izi\\prestashop\\View\\Widget\\Variant' => $baseDir . '/src/View/Widget/Variant.php', + 'izi\\prestashop\\View\\Widget\\WidgetConfiguration' => $baseDir . '/src/View/Widget/WidgetConfiguration.php', + 'izi\\prestashop\\View\\Widget\\WidgetConfigurationInterface' => $baseDir . '/src/View/Widget/WidgetConfigurationInterface.php', + 'izi\\prestashop\\View\\Widget\\WidgetConfigurationResolver' => $baseDir . '/src/View/Widget/WidgetConfigurationResolver.php', + 'izi\\prestashop\\View\\Widget\\WidgetConfigurationResolverInterface' => $baseDir . '/src/View/Widget/WidgetConfigurationResolverInterface.php', + 'izi\\prestashop\\rest\\order\\Create' => $baseDir . '/src/rest/order/Create.php', +); diff --git a/modules/inpostizi/vendor/composer/autoload_files.php b/modules/inpostizi/vendor/composer/autoload_files.php new file mode 100644 index 00000000..87ba5161 --- /dev/null +++ b/modules/inpostizi/vendor/composer/autoload_files.php @@ -0,0 +1,11 @@ + $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', + 'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php', +); diff --git a/modules/inpostizi/vendor/composer/autoload_namespaces.php b/modules/inpostizi/vendor/composer/autoload_namespaces.php new file mode 100644 index 00000000..15a2ff3a --- /dev/null +++ b/modules/inpostizi/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ + array($baseDir . '/src'), + 'ZipStream\\' => array($vendorDir . '/maennchen/zipstream-php/src'), + 'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'), + 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), + 'Symfony\\Contracts\\Service\\' => array($vendorDir . '/symfony/service-contracts'), + 'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'), + 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src', $vendorDir . '/psr/http-factory/src'), + 'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'), + 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'), + 'Psr\\Clock\\' => array($vendorDir . '/psr/clock/src'), + 'Nyholm\\Psr7\\' => array($vendorDir . '/nyholm/psr7/src'), + 'MyCLabs\\Enum\\' => array($vendorDir . '/myclabs/php-enum/src'), + 'Http\\Message\\' => array($vendorDir . '/php-http/message-factory/src'), +); diff --git a/modules/inpostizi/vendor/composer/autoload_real.php b/modules/inpostizi/vendor/composer/autoload_real.php new file mode 100644 index 00000000..817dc519 --- /dev/null +++ b/modules/inpostizi/vendor/composer/autoload_real.php @@ -0,0 +1,48 @@ +register(false); + + $filesToLoad = \Composer\Autoload\ComposerStaticInit3582376b22b8ed8077843f108d3dc00a::$files; + $requireFile = \Closure::bind(static function ($fileIdentifier, $file) { + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + + require $file; + } + }, null, null); + foreach ($filesToLoad as $fileIdentifier => $file) { + $requireFile($fileIdentifier, $file); + } + + return $loader; + } +} diff --git a/modules/inpostizi/vendor/composer/autoload_static.php b/modules/inpostizi/vendor/composer/autoload_static.php new file mode 100644 index 00000000..b7741e05 --- /dev/null +++ b/modules/inpostizi/vendor/composer/autoload_static.php @@ -0,0 +1,1007 @@ + __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', + 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', + ); + + public static $prefixLengthsPsr4 = array ( + 'i' => + array ( + 'izi\\prestashop\\' => 15, + ), + 'Z' => + array ( + 'ZipStream\\' => 10, + ), + 'S' => + array ( + 'Symfony\\Polyfill\\Php80\\' => 23, + 'Symfony\\Polyfill\\Mbstring\\' => 26, + 'Symfony\\Contracts\\Service\\' => 26, + ), + 'P' => + array ( + 'Psr\\SimpleCache\\' => 16, + 'Psr\\Http\\Message\\' => 17, + 'Psr\\Http\\Client\\' => 16, + 'Psr\\Container\\' => 14, + 'Psr\\Clock\\' => 10, + ), + 'N' => + array ( + 'Nyholm\\Psr7\\' => 12, + ), + 'M' => + array ( + 'MyCLabs\\Enum\\' => 13, + ), + 'H' => + array ( + 'Http\\Message\\' => 13, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'izi\\prestashop\\' => + array ( + 0 => __DIR__ . '/../..' . '/src', + ), + 'ZipStream\\' => + array ( + 0 => __DIR__ . '/..' . '/maennchen/zipstream-php/src', + ), + 'Symfony\\Polyfill\\Php80\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php80', + ), + 'Symfony\\Polyfill\\Mbstring\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', + ), + 'Symfony\\Contracts\\Service\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/service-contracts', + ), + 'Psr\\SimpleCache\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/simple-cache/src', + ), + 'Psr\\Http\\Message\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/http-message/src', + 1 => __DIR__ . '/..' . '/psr/http-factory/src', + ), + 'Psr\\Http\\Client\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/http-client/src', + ), + 'Psr\\Container\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/container/src', + ), + 'Psr\\Clock\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/clock/src', + ), + 'Nyholm\\Psr7\\' => + array ( + 0 => __DIR__ . '/..' . '/nyholm/psr7/src', + ), + 'MyCLabs\\Enum\\' => + array ( + 0 => __DIR__ . '/..' . '/myclabs/php-enum/src', + ), + 'Http\\Message\\' => + array ( + 0 => __DIR__ . '/..' . '/php-http/message-factory/src', + ), + ); + + public static $classMap = array ( + 'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + 'Http\\Message\\MessageFactory' => __DIR__ . '/..' . '/php-http/message-factory/src/MessageFactory.php', + 'Http\\Message\\RequestFactory' => __DIR__ . '/..' . '/php-http/message-factory/src/RequestFactory.php', + 'Http\\Message\\ResponseFactory' => __DIR__ . '/..' . '/php-http/message-factory/src/ResponseFactory.php', + 'Http\\Message\\StreamFactory' => __DIR__ . '/..' . '/php-http/message-factory/src/StreamFactory.php', + 'Http\\Message\\UriFactory' => __DIR__ . '/..' . '/php-http/message-factory/src/UriFactory.php', + 'MyCLabs\\Enum\\Enum' => __DIR__ . '/..' . '/myclabs/php-enum/src/Enum.php', + 'MyCLabs\\Enum\\PHPUnit\\Comparator' => __DIR__ . '/..' . '/myclabs/php-enum/src/PHPUnit/Comparator.php', + 'Nyholm\\Psr7\\Factory\\HttplugFactory' => __DIR__ . '/..' . '/nyholm/psr7/src/Factory/HttplugFactory.php', + 'Nyholm\\Psr7\\Factory\\Psr17Factory' => __DIR__ . '/..' . '/nyholm/psr7/src/Factory/Psr17Factory.php', + 'Nyholm\\Psr7\\MessageTrait' => __DIR__ . '/..' . '/nyholm/psr7/src/MessageTrait.php', + 'Nyholm\\Psr7\\Request' => __DIR__ . '/..' . '/nyholm/psr7/src/Request.php', + 'Nyholm\\Psr7\\RequestTrait' => __DIR__ . '/..' . '/nyholm/psr7/src/RequestTrait.php', + 'Nyholm\\Psr7\\Response' => __DIR__ . '/..' . '/nyholm/psr7/src/Response.php', + 'Nyholm\\Psr7\\ServerRequest' => __DIR__ . '/..' . '/nyholm/psr7/src/ServerRequest.php', + 'Nyholm\\Psr7\\Stream' => __DIR__ . '/..' . '/nyholm/psr7/src/Stream.php', + 'Nyholm\\Psr7\\UploadedFile' => __DIR__ . '/..' . '/nyholm/psr7/src/UploadedFile.php', + 'Nyholm\\Psr7\\Uri' => __DIR__ . '/..' . '/nyholm/psr7/src/Uri.php', + 'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', + 'Psr\\Clock\\ClockInterface' => __DIR__ . '/..' . '/psr/clock/src/ClockInterface.php', + 'Psr\\Container\\ContainerExceptionInterface' => __DIR__ . '/..' . '/psr/container/src/ContainerExceptionInterface.php', + 'Psr\\Container\\ContainerInterface' => __DIR__ . '/..' . '/psr/container/src/ContainerInterface.php', + 'Psr\\Container\\NotFoundExceptionInterface' => __DIR__ . '/..' . '/psr/container/src/NotFoundExceptionInterface.php', + 'Psr\\Http\\Client\\ClientExceptionInterface' => __DIR__ . '/..' . '/psr/http-client/src/ClientExceptionInterface.php', + 'Psr\\Http\\Client\\ClientInterface' => __DIR__ . '/..' . '/psr/http-client/src/ClientInterface.php', + 'Psr\\Http\\Client\\NetworkExceptionInterface' => __DIR__ . '/..' . '/psr/http-client/src/NetworkExceptionInterface.php', + 'Psr\\Http\\Client\\RequestExceptionInterface' => __DIR__ . '/..' . '/psr/http-client/src/RequestExceptionInterface.php', + 'Psr\\Http\\Message\\MessageInterface' => __DIR__ . '/..' . '/psr/http-message/src/MessageInterface.php', + 'Psr\\Http\\Message\\RequestFactoryInterface' => __DIR__ . '/..' . '/psr/http-factory/src/RequestFactoryInterface.php', + 'Psr\\Http\\Message\\RequestInterface' => __DIR__ . '/..' . '/psr/http-message/src/RequestInterface.php', + 'Psr\\Http\\Message\\ResponseFactoryInterface' => __DIR__ . '/..' . '/psr/http-factory/src/ResponseFactoryInterface.php', + 'Psr\\Http\\Message\\ResponseInterface' => __DIR__ . '/..' . '/psr/http-message/src/ResponseInterface.php', + 'Psr\\Http\\Message\\ServerRequestFactoryInterface' => __DIR__ . '/..' . '/psr/http-factory/src/ServerRequestFactoryInterface.php', + 'Psr\\Http\\Message\\ServerRequestInterface' => __DIR__ . '/..' . '/psr/http-message/src/ServerRequestInterface.php', + 'Psr\\Http\\Message\\StreamFactoryInterface' => __DIR__ . '/..' . '/psr/http-factory/src/StreamFactoryInterface.php', + 'Psr\\Http\\Message\\StreamInterface' => __DIR__ . '/..' . '/psr/http-message/src/StreamInterface.php', + 'Psr\\Http\\Message\\UploadedFileFactoryInterface' => __DIR__ . '/..' . '/psr/http-factory/src/UploadedFileFactoryInterface.php', + 'Psr\\Http\\Message\\UploadedFileInterface' => __DIR__ . '/..' . '/psr/http-message/src/UploadedFileInterface.php', + 'Psr\\Http\\Message\\UriFactoryInterface' => __DIR__ . '/..' . '/psr/http-factory/src/UriFactoryInterface.php', + 'Psr\\Http\\Message\\UriInterface' => __DIR__ . '/..' . '/psr/http-message/src/UriInterface.php', + 'Psr\\SimpleCache\\CacheException' => __DIR__ . '/..' . '/psr/simple-cache/src/CacheException.php', + 'Psr\\SimpleCache\\CacheInterface' => __DIR__ . '/..' . '/psr/simple-cache/src/CacheInterface.php', + 'Psr\\SimpleCache\\InvalidArgumentException' => __DIR__ . '/..' . '/psr/simple-cache/src/InvalidArgumentException.php', + 'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', + 'Symfony\\Contracts\\Service\\ResetInterface' => __DIR__ . '/..' . '/symfony/service-contracts/ResetInterface.php', + 'Symfony\\Contracts\\Service\\ServiceLocatorTrait' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceLocatorTrait.php', + 'Symfony\\Contracts\\Service\\ServiceProviderInterface' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceProviderInterface.php', + 'Symfony\\Contracts\\Service\\ServiceSubscriberInterface' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceSubscriberInterface.php', + 'Symfony\\Contracts\\Service\\ServiceSubscriberTrait' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceSubscriberTrait.php', + 'Symfony\\Contracts\\Service\\Test\\ServiceLocatorTest' => __DIR__ . '/..' . '/symfony/service-contracts/Test/ServiceLocatorTest.php', + 'Symfony\\Polyfill\\Mbstring\\Mbstring' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/Mbstring.php', + 'Symfony\\Polyfill\\Php80\\Php80' => __DIR__ . '/..' . '/symfony/polyfill-php80/Php80.php', + 'Symfony\\Polyfill\\Php80\\PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/PhpToken.php', + 'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', + 'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', + 'ZipStream\\Bigint' => __DIR__ . '/..' . '/maennchen/zipstream-php/src/Bigint.php', + 'ZipStream\\DeflateStream' => __DIR__ . '/..' . '/maennchen/zipstream-php/src/DeflateStream.php', + 'ZipStream\\Exception' => __DIR__ . '/..' . '/maennchen/zipstream-php/src/Exception.php', + 'ZipStream\\Exception\\EncodingException' => __DIR__ . '/..' . '/maennchen/zipstream-php/src/Exception/EncodingException.php', + 'ZipStream\\Exception\\FileNotFoundException' => __DIR__ . '/..' . '/maennchen/zipstream-php/src/Exception/FileNotFoundException.php', + 'ZipStream\\Exception\\FileNotReadableException' => __DIR__ . '/..' . '/maennchen/zipstream-php/src/Exception/FileNotReadableException.php', + 'ZipStream\\Exception\\IncompatibleOptionsException' => __DIR__ . '/..' . '/maennchen/zipstream-php/src/Exception/IncompatibleOptionsException.php', + 'ZipStream\\Exception\\OverflowException' => __DIR__ . '/..' . '/maennchen/zipstream-php/src/Exception/OverflowException.php', + 'ZipStream\\Exception\\StreamNotReadableException' => __DIR__ . '/..' . '/maennchen/zipstream-php/src/Exception/StreamNotReadableException.php', + 'ZipStream\\File' => __DIR__ . '/..' . '/maennchen/zipstream-php/src/File.php', + 'ZipStream\\Option\\Archive' => __DIR__ . '/..' . '/maennchen/zipstream-php/src/Option/Archive.php', + 'ZipStream\\Option\\File' => __DIR__ . '/..' . '/maennchen/zipstream-php/src/Option/File.php', + 'ZipStream\\Option\\Method' => __DIR__ . '/..' . '/maennchen/zipstream-php/src/Option/Method.php', + 'ZipStream\\Option\\Version' => __DIR__ . '/..' . '/maennchen/zipstream-php/src/Option/Version.php', + 'ZipStream\\Stream' => __DIR__ . '/..' . '/maennchen/zipstream-php/src/Stream.php', + 'ZipStream\\ZipStream' => __DIR__ . '/..' . '/maennchen/zipstream-php/src/ZipStream.php', + 'izi\\prestashop\\AdminKernel' => __DIR__ . '/../..' . '/src/AdminKernel.php', + 'izi\\prestashop\\Analytics\\BasketAnalytics' => __DIR__ . '/../..' . '/src/Analytics/BasketAnalytics.php', + 'izi\\prestashop\\Analytics\\BasketAnalyticsInterface' => __DIR__ . '/../..' . '/src/Analytics/BasketAnalyticsInterface.php', + 'izi\\prestashop\\Analytics\\BasketAnalyticsParams' => __DIR__ . '/../..' . '/src/Analytics/BasketAnalyticsParams.php', + 'izi\\prestashop\\Analytics\\BasketAnalyticsRepository' => __DIR__ . '/../..' . '/src/Analytics/BasketAnalyticsRepository.php', + 'izi\\prestashop\\Analytics\\BasketAnalyticsRepositoryInterface' => __DIR__ . '/../..' . '/src/Analytics/BasketAnalyticsRepositoryInterface.php', + 'izi\\prestashop\\Analytics\\Command\\UpdateCartAnalyticsCommand' => __DIR__ . '/../..' . '/src/Analytics/Command/UpdateCartAnalyticsCommand.php', + 'izi\\prestashop\\Analytics\\Cookie\\CookieEraserInterface' => __DIR__ . '/../..' . '/src/Analytics/Cookie/CookieEraserInterface.php', + 'izi\\prestashop\\Analytics\\Cookie\\CookieExtractorInterface' => __DIR__ . '/../..' . '/src/Analytics/Cookie/CookieExtractorInterface.php', + 'izi\\prestashop\\Analytics\\Cookie\\CookiePersisterInterface' => __DIR__ . '/../..' . '/src/Analytics/Cookie/CookiePersisterInterface.php', + 'izi\\prestashop\\Analytics\\Cookie\\Executor\\CookieEraseExecutor' => __DIR__ . '/../..' . '/src/Analytics/Cookie/Executor/CookieEraseExecutor.php', + 'izi\\prestashop\\Analytics\\Cookie\\Executor\\CookiePersisterExecutor' => __DIR__ . '/../..' . '/src/Analytics/Cookie/Executor/CookiePersisterExecutor.php', + 'izi\\prestashop\\Analytics\\Cookie\\FacebookClickIdCookie' => __DIR__ . '/../..' . '/src/Analytics/Cookie/FacebookClickIdCookie.php', + 'izi\\prestashop\\Analytics\\Cookie\\Factory\\CookieFactory' => __DIR__ . '/../..' . '/src/Analytics/Cookie/Factory/CookieFactory.php', + 'izi\\prestashop\\Analytics\\Cookie\\Factory\\CookieFactoryInterface' => __DIR__ . '/../..' . '/src/Analytics/Cookie/Factory/CookieFactoryInterface.php', + 'izi\\prestashop\\Analytics\\Cookie\\GoogleClickIdCookie' => __DIR__ . '/../..' . '/src/Analytics/Cookie/GoogleClickIdCookie.php', + 'izi\\prestashop\\Analytics\\Cookie\\GoogleClientIdCookie' => __DIR__ . '/../..' . '/src/Analytics/Cookie/GoogleClientIdCookie.php', + 'izi\\prestashop\\Analytics\\Cookie\\Repository\\CookieRepository' => __DIR__ . '/../..' . '/src/Analytics/Cookie/Repository/CookieRepository.php', + 'izi\\prestashop\\Analytics\\Cookie\\Repository\\CookieRepositoryInterface' => __DIR__ . '/../..' . '/src/Analytics/Cookie/Repository/CookieRepositoryInterface.php', + 'izi\\prestashop\\Analytics\\EventListener\\UpdateBasketAnalyticsListener' => __DIR__ . '/../..' . '/src/Analytics/EventListener/UpdateBasketAnalyticsListener.php', + 'izi\\prestashop\\Analytics\\Factory\\BasketAnalyticsFactory' => __DIR__ . '/../..' . '/src/Analytics/Factory/BasketAnalyticsFactory.php', + 'izi\\prestashop\\Analytics\\Factory\\BasketAnalyticsFactoryInterface' => __DIR__ . '/../..' . '/src/Analytics/Factory/BasketAnalyticsFactoryInterface.php', + 'izi\\prestashop\\Analytics\\Handler\\UpdateCartAnalyticsHandler' => __DIR__ . '/../..' . '/src/Analytics/Handler/UpdateCartAnalyticsHandler.php', + 'izi\\prestashop\\Analytics\\Handler\\UpdateCartAnalyticsHandlerInterface' => __DIR__ . '/../..' . '/src/Analytics/Handler/UpdateCartAnalyticsHandlerInterface.php', + 'izi\\prestashop\\BasketApp\\AuthorizationProviderFactory' => __DIR__ . '/../..' . '/src/BasketApp/AuthorizationProviderFactory.php', + 'izi\\prestashop\\BasketApp\\BasketAppClient' => __DIR__ . '/../..' . '/src/BasketApp/BasketAppClient.php', + 'izi\\prestashop\\BasketApp\\BasketAppClientFactory' => __DIR__ . '/../..' . '/src/BasketApp/BasketAppClientFactory.php', + 'izi\\prestashop\\BasketApp\\BasketAppClientInterface' => __DIR__ . '/../..' . '/src/BasketApp/BasketAppClientInterface.php', + 'izi\\prestashop\\BasketApp\\Basket\\BasketsApiClientInterface' => __DIR__ . '/../..' . '/src/BasketApp/Basket/BasketsApiClientInterface.php', + 'izi\\prestashop\\BasketApp\\Basket\\Request\\Basket' => __DIR__ . '/../..' . '/src/BasketApp/Basket/Request/Basket.php', + 'izi\\prestashop\\BasketApp\\Basket\\Response\\BasketBindingKeyResponse' => __DIR__ . '/../..' . '/src/BasketApp/Basket/Response/BasketBindingKeyResponse.php', + 'izi\\prestashop\\BasketApp\\Basket\\Response\\BasketBindingResponse' => __DIR__ . '/../..' . '/src/BasketApp/Basket/Response/BasketBindingResponse.php', + 'izi\\prestashop\\BasketApp\\Basket\\Response\\ClientDetails' => __DIR__ . '/../..' . '/src/BasketApp/Basket/Response/ClientDetails.php', + 'izi\\prestashop\\BasketApp\\Basket\\Response\\UpdateBasketResponse' => __DIR__ . '/../..' . '/src/BasketApp/Basket/Response/UpdateBasketResponse.php', + 'izi\\prestashop\\BasketApp\\Exception\\BadRequestException' => __DIR__ . '/../..' . '/src/BasketApp/Exception/BadRequestException.php', + 'izi\\prestashop\\BasketApp\\Exception\\BasketAlreadyBoundException' => __DIR__ . '/../..' . '/src/BasketApp/Exception/BasketAlreadyBoundException.php', + 'izi\\prestashop\\BasketApp\\Exception\\BasketAppException' => __DIR__ . '/../..' . '/src/BasketApp/Exception/BasketAppException.php', + 'izi\\prestashop\\BasketApp\\Exception\\BasketExpiredException' => __DIR__ . '/../..' . '/src/BasketApp/Exception/BasketExpiredException.php', + 'izi\\prestashop\\BasketApp\\Exception\\BasketNotBoundException' => __DIR__ . '/../..' . '/src/BasketApp/Exception/BasketNotBoundException.php', + 'izi\\prestashop\\BasketApp\\Exception\\BasketNotFoundException' => __DIR__ . '/../..' . '/src/BasketApp/Exception/BasketNotFoundException.php', + 'izi\\prestashop\\BasketApp\\Exception\\CannotChangeOrderStatusException' => __DIR__ . '/../..' . '/src/BasketApp/Exception/CannotChangeOrderStatusException.php', + 'izi\\prestashop\\BasketApp\\Exception\\ForbiddenException' => __DIR__ . '/../..' . '/src/BasketApp/Exception/ForbiddenException.php', + 'izi\\prestashop\\BasketApp\\Exception\\InternalServerErrorException' => __DIR__ . '/../..' . '/src/BasketApp/Exception/InternalServerErrorException.php', + 'izi\\prestashop\\BasketApp\\Exception\\MalformedRequestException' => __DIR__ . '/../..' . '/src/BasketApp/Exception/MalformedRequestException.php', + 'izi\\prestashop\\BasketApp\\Exception\\MerchantDisabledException' => __DIR__ . '/../..' . '/src/BasketApp/Exception/MerchantDisabledException.php', + 'izi\\prestashop\\BasketApp\\Exception\\OrderNotFoundException' => __DIR__ . '/../..' . '/src/BasketApp/Exception/OrderNotFoundException.php', + 'izi\\prestashop\\BasketApp\\Exception\\PhoneBindingUnavailableException' => __DIR__ . '/../..' . '/src/BasketApp/Exception/PhoneBindingUnavailableException.php', + 'izi\\prestashop\\BasketApp\\Exception\\PublicKeyNotFoundException' => __DIR__ . '/../..' . '/src/BasketApp/Exception/PublicKeyNotFoundException.php', + 'izi\\prestashop\\BasketApp\\Exception\\ResourceNotFoundException' => __DIR__ . '/../..' . '/src/BasketApp/Exception/ResourceNotFoundException.php', + 'izi\\prestashop\\BasketApp\\Exception\\UnauthorizedException' => __DIR__ . '/../..' . '/src/BasketApp/Exception/UnauthorizedException.php', + 'izi\\prestashop\\BasketApp\\Order\\OrdersApiClientInterface' => __DIR__ . '/../..' . '/src/BasketApp/Order/OrdersApiClientInterface.php', + 'izi\\prestashop\\BasketApp\\Order\\Request\\Delivery' => __DIR__ . '/../..' . '/src/BasketApp/Order/Request/Delivery.php', + 'izi\\prestashop\\BasketApp\\Order\\Request\\OrderEvent' => __DIR__ . '/../..' . '/src/BasketApp/Order/Request/OrderEvent.php', + 'izi\\prestashop\\BasketApp\\Order\\Request\\OrderEventData' => __DIR__ . '/../..' . '/src/BasketApp/Order/Request/OrderEventData.php', + 'izi\\prestashop\\BasketApp\\PaginationPage' => __DIR__ . '/../..' . '/src/BasketApp/PaginationPage.php', + 'izi\\prestashop\\BasketApp\\Payment\\PaymentsApiClientInterface' => __DIR__ . '/../..' . '/src/BasketApp/Payment/PaymentsApiClientInterface.php', + 'izi\\prestashop\\BasketApp\\Payment\\Response\\AvailablePaymentOptions' => __DIR__ . '/../..' . '/src/BasketApp/Payment/Response/AvailablePaymentOptions.php', + 'izi\\prestashop\\BasketApp\\Product\\Exception\\MaxProductLimitReachedException' => __DIR__ . '/../..' . '/src/BasketApp/Product/Exception/MaxProductLimitReachedException.php', + 'izi\\prestashop\\BasketApp\\Product\\Exception\\ProductExistsException' => __DIR__ . '/../..' . '/src/BasketApp/Product/Exception/ProductExistsException.php', + 'izi\\prestashop\\BasketApp\\Product\\Exception\\ProductNotFoundException' => __DIR__ . '/../..' . '/src/BasketApp/Product/Exception/ProductNotFoundException.php', + 'izi\\prestashop\\BasketApp\\Product\\ProductsApiClientInterface' => __DIR__ . '/../..' . '/src/BasketApp/Product/ProductsApiClientInterface.php', + 'izi\\prestashop\\BasketApp\\Product\\Request\\CreateProductsRequest' => __DIR__ . '/../..' . '/src/BasketApp/Product/Request/CreateProductsRequest.php', + 'izi\\prestashop\\BasketApp\\Product\\Response\\CreateProductsResponse' => __DIR__ . '/../..' . '/src/BasketApp/Product/Response/CreateProductsResponse.php', + 'izi\\prestashop\\BasketApp\\Product\\Response\\Product' => __DIR__ . '/../..' . '/src/BasketApp/Product/Response/Product.php', + 'izi\\prestashop\\BasketApp\\Product\\Response\\ProductId' => __DIR__ . '/../..' . '/src/BasketApp/Product/Response/ProductId.php', + 'izi\\prestashop\\BasketApp\\Product\\Response\\Status' => __DIR__ . '/../..' . '/src/BasketApp/Product/Response/Status.php', + 'izi\\prestashop\\BasketApp\\Signature\\Response\\PublicKey' => __DIR__ . '/../..' . '/src/BasketApp/Signature/Response/PublicKey.php', + 'izi\\prestashop\\BasketApp\\Signature\\Response\\SigningKey' => __DIR__ . '/../..' . '/src/BasketApp/Signature/Response/SigningKey.php', + 'izi\\prestashop\\BasketApp\\Signature\\Response\\SigningKeys' => __DIR__ . '/../..' . '/src/BasketApp/Signature/Response/SigningKeys.php', + 'izi\\prestashop\\BasketApp\\Signature\\SigningKeysApiClientInterface' => __DIR__ . '/../..' . '/src/BasketApp/Signature/SigningKeysApiClientInterface.php', + 'izi\\prestashop\\Builder\\Basket\\AbstractBasketBuilder' => __DIR__ . '/../..' . '/src/Builder/Basket/AbstractBasketBuilder.php', + 'izi\\prestashop\\Builder\\Basket\\BasketAppRequestBuilder' => __DIR__ . '/../..' . '/src/Builder/Basket/BasketAppRequestBuilder.php', + 'izi\\prestashop\\Builder\\Basket\\BasketAppRequestBuilderInterface' => __DIR__ . '/../..' . '/src/Builder/Basket/BasketAppRequestBuilderInterface.php', + 'izi\\prestashop\\Builder\\Basket\\BasketBuilderFactory' => __DIR__ . '/../..' . '/src/Builder/Basket/BasketBuilderFactory.php', + 'izi\\prestashop\\Builder\\Basket\\BasketBuilderFactoryInterface' => __DIR__ . '/../..' . '/src/Builder/Basket/BasketBuilderFactoryInterface.php', + 'izi\\prestashop\\Builder\\Basket\\BasketBuilderInterface' => __DIR__ . '/../..' . '/src/Builder/Basket/BasketBuilderInterface.php', + 'izi\\prestashop\\Builder\\Basket\\DeliveryFactory' => __DIR__ . '/../..' . '/src/Builder/Basket/DeliveryFactory.php', + 'izi\\prestashop\\Builder\\Basket\\MerchantApiResponseBuilder' => __DIR__ . '/../..' . '/src/Builder/Basket/MerchantApiResponseBuilder.php', + 'izi\\prestashop\\Builder\\Basket\\MerchantApiResponseBuilderInterface' => __DIR__ . '/../..' . '/src/Builder/Basket/MerchantApiResponseBuilderInterface.php', + 'izi\\prestashop\\Builder\\Basket\\ProductDeliveryFactory' => __DIR__ . '/../..' . '/src/Builder/Basket/ProductDeliveryFactory.php', + 'izi\\prestashop\\Builder\\Order\\OrderEventBuilder' => __DIR__ . '/../..' . '/src/Builder/Order/OrderEventBuilder.php', + 'izi\\prestashop\\Builder\\Order\\OrderEventBuilderFactory' => __DIR__ . '/../..' . '/src/Builder/Order/OrderEventBuilderFactory.php', + 'izi\\prestashop\\Builder\\Order\\OrderEventBuilderFactoryInterface' => __DIR__ . '/../..' . '/src/Builder/Order/OrderEventBuilderFactoryInterface.php', + 'izi\\prestashop\\Builder\\Order\\OrderEventBuilderInterface' => __DIR__ . '/../..' . '/src/Builder/Order/OrderEventBuilderInterface.php', + 'izi\\prestashop\\Builder\\Order\\OrderStatusDescriptionProvider' => __DIR__ . '/../..' . '/src/Builder/Order/OrderStatusDescriptionProvider.php', + 'izi\\prestashop\\Builder\\PriceFactory' => __DIR__ . '/../..' . '/src/Builder/PriceFactory.php', + 'izi\\prestashop\\CacheClearer\\BindingKeysCacheClearer' => __DIR__ . '/../..' . '/src/CacheClearer/BindingKeysCacheClearer.php', + 'izi\\prestashop\\CacheClearer\\CacheClearerInterface' => __DIR__ . '/../..' . '/src/CacheClearer/CacheClearerInterface.php', + 'izi\\prestashop\\CacheClearer\\ChainCacheClearer' => __DIR__ . '/../..' . '/src/CacheClearer/ChainCacheClearer.php', + 'izi\\prestashop\\CacheClearer\\Psr16CacheClearer' => __DIR__ . '/../..' . '/src/CacheClearer/Psr16CacheClearer.php', + 'izi\\prestashop\\Cache\\ConfigurationCache' => __DIR__ . '/../..' . '/src/Cache/ConfigurationCache.php', + 'izi\\prestashop\\Cache\\Exception\\InvalidArgumentException' => __DIR__ . '/../..' . '/src/Cache/Exception/InvalidArgumentException.php', + 'izi\\prestashop\\Cache\\Exception\\RuntimeException' => __DIR__ . '/../..' . '/src/Cache/Exception/RuntimeException.php', + 'izi\\prestashop\\Cart\\Exception\\ProductAlreadyInCartException' => __DIR__ . '/../..' . '/src/Cart/Exception/ProductAlreadyInCartException.php', + 'izi\\prestashop\\Cart\\Util\\ProductHelper' => __DIR__ . '/../..' . '/src/Cart/Util/ProductHelper.php', + 'izi\\prestashop\\Clock\\SystemClock' => __DIR__ . '/../..' . '/src/Clock/SystemClock.php', + 'izi\\prestashop\\CommandBus' => __DIR__ . '/../..' . '/src/CommandBus.php', + 'izi\\prestashop\\CommandBusInterface' => __DIR__ . '/../..' . '/src/CommandBusInterface.php', + 'izi\\prestashop\\Command\\Config\\CheckStatusCommand' => __DIR__ . '/../..' . '/src/Command/Config/CheckStatusCommand.php', + 'izi\\prestashop\\Command\\Config\\DownloadModuleDataCommand' => __DIR__ . '/../..' . '/src/Command/Config/DownloadModuleDataCommand.php', + 'izi\\prestashop\\Command\\Config\\UpdateAdvancedConfigurationCommand' => __DIR__ . '/../..' . '/src/Command/Config/UpdateAdvancedConfigurationCommand.php', + 'izi\\prestashop\\Command\\Config\\UpdateCartRuleOptionsCommand' => __DIR__ . '/../..' . '/src/Command/Config/UpdateCartRuleOptionsCommand.php', + 'izi\\prestashop\\Command\\Config\\UpdateConsentsConfigurationCommand' => __DIR__ . '/../..' . '/src/Command/Config/UpdateConsentsConfigurationCommand.php', + 'izi\\prestashop\\Command\\Config\\UpdateGeneralConfigurationCommand' => __DIR__ . '/../..' . '/src/Command/Config/UpdateGeneralConfigurationCommand.php', + 'izi\\prestashop\\Command\\Config\\UpdateGeneralConfigurationCommandFactory' => __DIR__ . '/../..' . '/src/Command/Config/UpdateGeneralConfigurationCommandFactory.php', + 'izi\\prestashop\\Command\\Config\\UpdateGuiConfigurationCommand' => __DIR__ . '/../..' . '/src/Command/Config/UpdateGuiConfigurationCommand.php', + 'izi\\prestashop\\Command\\Config\\UpdateShippingConfigurationCommand' => __DIR__ . '/../..' . '/src/Command/Config/UpdateShippingConfigurationCommand.php', + 'izi\\prestashop\\Command\\GetBasketBindingKeyCommand' => __DIR__ . '/../..' . '/src/Command/GetBasketBindingKeyCommand.php', + 'izi\\prestashop\\Command\\GetOrderConfirmationUrlCommand' => __DIR__ . '/../..' . '/src/Command/GetOrderConfirmationUrlCommand.php', + 'izi\\prestashop\\Command\\UnbindBasketCommand' => __DIR__ . '/../..' . '/src/Command/UnbindBasketCommand.php', + 'izi\\prestashop\\Command\\UpdateBasketCommand' => __DIR__ . '/../..' . '/src/Command/UpdateBasketCommand.php', + 'izi\\prestashop\\Command\\UpdateOrderAddressDeliveryCommand' => __DIR__ . '/../..' . '/src/Command/UpdateOrderAddressDeliveryCommand.php', + 'izi\\prestashop\\Command\\UpdateOrderStatusCommand' => __DIR__ . '/../..' . '/src/Command/UpdateOrderStatusCommand.php', + 'izi\\prestashop\\Command\\UpdateOrderTrackingNumbersCommand' => __DIR__ . '/../..' . '/src/Command/UpdateOrderTrackingNumbersCommand.php', + 'izi\\prestashop\\Common\\Basket\\AvailablePromotion' => __DIR__ . '/../..' . '/src/Common/Basket/AvailablePromotion.php', + 'izi\\prestashop\\Common\\Basket\\Consent' => __DIR__ . '/../..' . '/src/Common/Basket/Consent.php', + 'izi\\prestashop\\Common\\Basket\\ConsentLink' => __DIR__ . '/../..' . '/src/Common/Basket/ConsentLink.php', + 'izi\\prestashop\\Common\\Basket\\ConsentRequirementType' => __DIR__ . '/../..' . '/src/Common/Basket/ConsentRequirementType.php', + 'izi\\prestashop\\Common\\Basket\\DeliveryOption' => __DIR__ . '/../..' . '/src/Common/Basket/DeliveryOption.php', + 'izi\\prestashop\\Common\\Basket\\Notice' => __DIR__ . '/../..' . '/src/Common/Basket/Notice.php', + 'izi\\prestashop\\Common\\Basket\\NoticeType' => __DIR__ . '/../..' . '/src/Common/Basket/NoticeType.php', + 'izi\\prestashop\\Common\\Basket\\Product' => __DIR__ . '/../..' . '/src/Common/Basket/Product.php', + 'izi\\prestashop\\Common\\Basket\\PromoDetails' => __DIR__ . '/../..' . '/src/Common/Basket/PromoDetails.php', + 'izi\\prestashop\\Common\\Basket\\PromotionType' => __DIR__ . '/../..' . '/src/Common/Basket/PromotionType.php', + 'izi\\prestashop\\Common\\Basket\\Quantity' => __DIR__ . '/../..' . '/src/Common/Basket/Quantity.php', + 'izi\\prestashop\\Common\\Basket\\Summary' => __DIR__ . '/../..' . '/src/Common/Basket/Summary.php', + 'izi\\prestashop\\Common\\BindingPlace' => __DIR__ . '/../..' . '/src/Common/BindingPlace.php', + 'izi\\prestashop\\Common\\Currency' => __DIR__ . '/../..' . '/src/Common/Currency.php', + 'izi\\prestashop\\Common\\Customer\\AccountInfo' => __DIR__ . '/../..' . '/src/Common/Customer/AccountInfo.php', + 'izi\\prestashop\\Common\\Customer\\ClientAddress' => __DIR__ . '/../..' . '/src/Common/Customer/ClientAddress.php', + 'izi\\prestashop\\Common\\Customer\\InvoiceDetails' => __DIR__ . '/../..' . '/src/Common/Customer/InvoiceDetails.php', + 'izi\\prestashop\\Common\\Customer\\LegalForm' => __DIR__ . '/../..' . '/src/Common/Customer/LegalForm.php', + 'izi\\prestashop\\Common\\Delivery\\DeliveryType' => __DIR__ . '/../..' . '/src/Common/Delivery/DeliveryType.php', + 'izi\\prestashop\\Common\\Delivery\\OptionalService' => __DIR__ . '/../..' . '/src/Common/Delivery/OptionalService.php', + 'izi\\prestashop\\Common\\Delivery\\ServiceCode' => __DIR__ . '/../..' . '/src/Common/Delivery/ServiceCode.php', + 'izi\\prestashop\\Common\\Dimensions' => __DIR__ . '/../..' . '/src/Common/Dimensions.php', + 'izi\\prestashop\\Common\\Error\\Error' => __DIR__ . '/../..' . '/src/Common/Error/Error.php', + 'izi\\prestashop\\Common\\HotProduct\\IdentifiableProduct' => __DIR__ . '/../..' . '/src/Common/HotProduct/IdentifiableProduct.php', + 'izi\\prestashop\\Common\\HotProduct\\Product' => __DIR__ . '/../..' . '/src/Common/HotProduct/Product.php', + 'izi\\prestashop\\Common\\HotProduct\\ProductAvailability' => __DIR__ . '/../..' . '/src/Common/HotProduct/ProductAvailability.php', + 'izi\\prestashop\\Common\\HotProduct\\ProductTrait' => __DIR__ . '/../..' . '/src/Common/HotProduct/ProductTrait.php', + 'izi\\prestashop\\Common\\HotProduct\\Quantity' => __DIR__ . '/../..' . '/src/Common/HotProduct/Quantity.php', + 'izi\\prestashop\\Common\\Order\\Consent' => __DIR__ . '/../..' . '/src/Common/Order/Consent.php', + 'izi\\prestashop\\Common\\Order\\DeliveryAddress' => __DIR__ . '/../..' . '/src/Common/Order/DeliveryAddress.php', + 'izi\\prestashop\\Common\\Order\\MerchantOrderStatus' => __DIR__ . '/../..' . '/src/Common/Order/MerchantOrderStatus.php', + 'izi\\prestashop\\Common\\Order\\OrderAdditionalParameter' => __DIR__ . '/../..' . '/src/Common/Order/OrderAdditionalParameter.php', + 'izi\\prestashop\\Common\\Order\\OrderAdditionalParameters' => __DIR__ . '/../..' . '/src/Common/Order/OrderAdditionalParameters.php', + 'izi\\prestashop\\Common\\Order\\Product' => __DIR__ . '/../..' . '/src/Common/Order/Product.php', + 'izi\\prestashop\\Common\\Order\\Quantity' => __DIR__ . '/../..' . '/src/Common/Order/Quantity.php', + 'izi\\prestashop\\Common\\PaymentType' => __DIR__ . '/../..' . '/src/Common/PaymentType.php', + 'izi\\prestashop\\Common\\PhoneNumber' => __DIR__ . '/../..' . '/src/Common/PhoneNumber.php', + 'izi\\prestashop\\Common\\Price' => __DIR__ . '/../..' . '/src/Common/Price.php', + 'izi\\prestashop\\Common\\PriceAmount' => __DIR__ . '/../..' . '/src/Common/PriceAmount.php', + 'izi\\prestashop\\Common\\Product\\DeliveryProduct' => __DIR__ . '/../..' . '/src/Common/Product/DeliveryProduct.php', + 'izi\\prestashop\\Common\\Product\\DeliveryRelatedProducts' => __DIR__ . '/../..' . '/src/Common/Product/DeliveryRelatedProducts.php', + 'izi\\prestashop\\Common\\Product\\ProductAttribute' => __DIR__ . '/../..' . '/src/Common/Product/ProductAttribute.php', + 'izi\\prestashop\\Common\\Product\\ProductImage' => __DIR__ . '/../..' . '/src/Common/Product/ProductImage.php', + 'izi\\prestashop\\Common\\Product\\ProductVariant' => __DIR__ . '/../..' . '/src/Common/Product/ProductVariant.php', + 'izi\\prestashop\\Common\\PromoCode' => __DIR__ . '/../..' . '/src/Common/PromoCode.php', + 'izi\\prestashop\\Common\\QuantityType' => __DIR__ . '/../..' . '/src/Common/QuantityType.php', + 'izi\\prestashop\\Common\\Weight' => __DIR__ . '/../..' . '/src/Common/Weight.php', + 'izi\\prestashop\\Configuration\\Adapter\\Configuration' => __DIR__ . '/../..' . '/src/Configuration/Adapter/Configuration.php', + 'izi\\prestashop\\Configuration\\AdvancedConfiguration' => __DIR__ . '/../..' . '/src/Configuration/AdvancedConfiguration.php', + 'izi\\prestashop\\Configuration\\AdvancedConfigurationInterface' => __DIR__ . '/../..' . '/src/Configuration/AdvancedConfigurationInterface.php', + 'izi\\prestashop\\Configuration\\ApiConfiguration' => __DIR__ . '/../..' . '/src/Configuration/ApiConfiguration.php', + 'izi\\prestashop\\Configuration\\ApiConfigurationInterface' => __DIR__ . '/../..' . '/src/Configuration/ApiConfigurationInterface.php', + 'izi\\prestashop\\Configuration\\ConfigurationInterface' => __DIR__ . '/../..' . '/src/Configuration/ConfigurationInterface.php', + 'izi\\prestashop\\Configuration\\ConsentsConfiguration' => __DIR__ . '/../..' . '/src/Configuration/ConsentsConfiguration.php', + 'izi\\prestashop\\Configuration\\ConsentsConfigurationInterface' => __DIR__ . '/../..' . '/src/Configuration/ConsentsConfigurationInterface.php', + 'izi\\prestashop\\Configuration\\DTO\\AdvancedConfiguration' => __DIR__ . '/../..' . '/src/Configuration/DTO/AdvancedConfiguration.php', + 'izi\\prestashop\\Configuration\\DTO\\ApiConfiguration' => __DIR__ . '/../..' . '/src/Configuration/DTO/ApiConfiguration.php', + 'izi\\prestashop\\Configuration\\DTO\\Consent' => __DIR__ . '/../..' . '/src/Configuration/DTO/Consent.php', + 'izi\\prestashop\\Configuration\\DTO\\ConsentLink' => __DIR__ . '/../..' . '/src/Configuration/DTO/ConsentLink.php', + 'izi\\prestashop\\Configuration\\DTO\\GeneralConfiguration' => __DIR__ . '/../..' . '/src/Configuration/DTO/GeneralConfiguration.php', + 'izi\\prestashop\\Configuration\\DTO\\GuiConfiguration' => __DIR__ . '/../..' . '/src/Configuration/DTO/GuiConfiguration.php', + 'izi\\prestashop\\Configuration\\DTO\\HtmlStyles' => __DIR__ . '/../..' . '/src/Configuration/DTO/HtmlStyles.php', + 'izi\\prestashop\\Configuration\\DTO\\Order\\MessageOptions' => __DIR__ . '/../..' . '/src/Configuration/DTO/Order/MessageOptions.php', + 'izi\\prestashop\\Configuration\\DTO\\OrdersConfiguration' => __DIR__ . '/../..' . '/src/Configuration/DTO/OrdersConfiguration.php', + 'izi\\prestashop\\Configuration\\DTO\\ProductConfiguration' => __DIR__ . '/../..' . '/src/Configuration/DTO/ProductConfiguration.php', + 'izi\\prestashop\\Configuration\\DTO\\ProductPageDisplayConfiguration' => __DIR__ . '/../..' . '/src/Configuration/DTO/ProductPageDisplayConfiguration.php', + 'izi\\prestashop\\Configuration\\DTO\\Product\\ProductRestrictions' => __DIR__ . '/../..' . '/src/Configuration/DTO/Product/ProductRestrictions.php', + 'izi\\prestashop\\Configuration\\DTO\\Product\\ProductRestrictionsCache' => __DIR__ . '/../..' . '/src/Configuration/DTO/Product/ProductRestrictionsCache.php', + 'izi\\prestashop\\Configuration\\DTO\\ShippingConfiguration' => __DIR__ . '/../..' . '/src/Configuration/DTO/ShippingConfiguration.php', + 'izi\\prestashop\\Configuration\\DTO\\Shipping\\CarrierMapping' => __DIR__ . '/../..' . '/src/Configuration/DTO/Shipping/CarrierMapping.php', + 'izi\\prestashop\\Configuration\\DTO\\Shipping\\ServiceOptions' => __DIR__ . '/../..' . '/src/Configuration/DTO/Shipping/ServiceOptions.php', + 'izi\\prestashop\\Configuration\\DTO\\Shipping\\ShippingOptions' => __DIR__ . '/../..' . '/src/Configuration/DTO/Shipping/ShippingOptions.php', + 'izi\\prestashop\\Configuration\\DTO\\Shipping\\TimeOfWeek' => __DIR__ . '/../..' . '/src/Configuration/DTO/Shipping/TimeOfWeek.php', + 'izi\\prestashop\\Configuration\\DTO\\Shipping\\TimeOfWeekRange' => __DIR__ . '/../..' . '/src/Configuration/DTO/Shipping/TimeOfWeekRange.php', + 'izi\\prestashop\\Configuration\\DTO\\Shipping\\WeekDay' => __DIR__ . '/../..' . '/src/Configuration/DTO/Shipping/WeekDay.php', + 'izi\\prestashop\\Configuration\\DTO\\WidgetDisplayConfiguration' => __DIR__ . '/../..' . '/src/Configuration/DTO/WidgetDisplayConfiguration.php', + 'izi\\prestashop\\Configuration\\GeneralConfiguration' => __DIR__ . '/../..' . '/src/Configuration/GeneralConfiguration.php', + 'izi\\prestashop\\Configuration\\GeneralConfigurationInterface' => __DIR__ . '/../..' . '/src/Configuration/GeneralConfigurationInterface.php', + 'izi\\prestashop\\Configuration\\GuiConfiguration' => __DIR__ . '/../..' . '/src/Configuration/GuiConfiguration.php', + 'izi\\prestashop\\Configuration\\GuiConfigurationInterface' => __DIR__ . '/../..' . '/src/Configuration/GuiConfigurationInterface.php', + 'izi\\prestashop\\Configuration\\Initializer\\AnnotationsConfigInitializer' => __DIR__ . '/../..' . '/src/Configuration/Initializer/AnnotationsConfigInitializer.php', + 'izi\\prestashop\\Configuration\\Initializer\\AssetPackageInitializer' => __DIR__ . '/../..' . '/src/Configuration/Initializer/AssetPackageInitializer.php', + 'izi\\prestashop\\Configuration\\Initializer\\ConfigurationInitializerInterface' => __DIR__ . '/../..' . '/src/Configuration/Initializer/ConfigurationInitializerInterface.php', + 'izi\\prestashop\\Configuration\\Initializer\\TwigConfigInitializer' => __DIR__ . '/../..' . '/src/Configuration/Initializer/TwigConfigInitializer.php', + 'izi\\prestashop\\Configuration\\LanguageAwareConfigurationInterface' => __DIR__ . '/../..' . '/src/Configuration/LanguageAwareConfigurationInterface.php', + 'izi\\prestashop\\Configuration\\OrdersConfiguration' => __DIR__ . '/../..' . '/src/Configuration/OrdersConfiguration.php', + 'izi\\prestashop\\Configuration\\OrdersConfigurationInterface' => __DIR__ . '/../..' . '/src/Configuration/OrdersConfigurationInterface.php', + 'izi\\prestashop\\Configuration\\PersistentConfigurationInterface' => __DIR__ . '/../..' . '/src/Configuration/PersistentConfigurationInterface.php', + 'izi\\prestashop\\Configuration\\PrestaShopConfiguration' => __DIR__ . '/../..' . '/src/Configuration/PrestaShopConfiguration.php', + 'izi\\prestashop\\Configuration\\ProductAwareWidgetDisplayConfigurationInterface' => __DIR__ . '/../..' . '/src/Configuration/ProductAwareWidgetDisplayConfigurationInterface.php', + 'izi\\prestashop\\Configuration\\ProductConfiguration' => __DIR__ . '/../..' . '/src/Configuration/ProductConfiguration.php', + 'izi\\prestashop\\Configuration\\ProductConfigurationInterface' => __DIR__ . '/../..' . '/src/Configuration/ProductConfigurationInterface.php', + 'izi\\prestashop\\Configuration\\ProductPageDisplayConfiguration' => __DIR__ . '/../..' . '/src/Configuration/ProductPageDisplayConfiguration.php', + 'izi\\prestashop\\Configuration\\ProductRestrictionsConfigurationInterface' => __DIR__ . '/../..' . '/src/Configuration/ProductRestrictionsConfigurationInterface.php', + 'izi\\prestashop\\Configuration\\ProductRestrictionsProviderInterface' => __DIR__ . '/../..' . '/src/Configuration/ProductRestrictionsProviderInterface.php', + 'izi\\prestashop\\Configuration\\PromoCodesConfigurationInterface' => __DIR__ . '/../..' . '/src/Configuration/PromoCodesConfigurationInterface.php', + 'izi\\prestashop\\Configuration\\ShippingConfiguration' => __DIR__ . '/../..' . '/src/Configuration/ShippingConfiguration.php', + 'izi\\prestashop\\Configuration\\ShippingConfigurationInterface' => __DIR__ . '/../..' . '/src/Configuration/ShippingConfigurationInterface.php', + 'izi\\prestashop\\Configuration\\ShopAwareConfigurationInterface' => __DIR__ . '/../..' . '/src/Configuration/ShopAwareConfigurationInterface.php', + 'izi\\prestashop\\Configuration\\WidgetDisplayConfigurationInterface' => __DIR__ . '/../..' . '/src/Configuration/WidgetDisplayConfigurationInterface.php', + 'izi\\prestashop\\ContextManager' => __DIR__ . '/../..' . '/src/ContextManager.php', + 'izi\\prestashop\\Controller\\Admin\\AbstractConfigurationController' => __DIR__ . '/../..' . '/src/Controller/Admin/AbstractConfigurationController.php', + 'izi\\prestashop\\Controller\\Admin\\AbstractController' => __DIR__ . '/../..' . '/src/Controller/Admin/AbstractController.php', + 'izi\\prestashop\\Controller\\Admin\\ConfigurationController' => __DIR__ . '/../..' . '/src/Controller/Admin/ConfigurationController.php', + 'izi\\prestashop\\Controller\\Admin\\HotProductController' => __DIR__ . '/../..' . '/src/Controller/Admin/HotProductController.php', + 'izi\\prestashop\\Controller\\Api\\AbstractApiController' => __DIR__ . '/../..' . '/src/Controller/Api/AbstractApiController.php', + 'izi\\prestashop\\Controller\\Api\\BasketController' => __DIR__ . '/../..' . '/src/Controller/Api/BasketController.php', + 'izi\\prestashop\\Controller\\Api\\OrderController' => __DIR__ . '/../..' . '/src/Controller/Api/OrderController.php', + 'izi\\prestashop\\Controller\\Api\\ProductController' => __DIR__ . '/../..' . '/src/Controller/Api/ProductController.php', + 'izi\\prestashop\\Controller\\WidgetController' => __DIR__ . '/../..' . '/src/Controller/WidgetController.php', + 'izi\\prestashop\\Currency\\PriceConverter' => __DIR__ . '/../..' . '/src/Currency/PriceConverter.php', + 'izi\\prestashop\\Currency\\PriceConverterInterface' => __DIR__ . '/../..' . '/src/Currency/PriceConverterInterface.php', + 'izi\\prestashop\\Database\\Connection' => __DIR__ . '/../..' . '/src/Database/Connection.php', + 'izi\\prestashop\\DependencyInjection\\Argument\\ServiceClosureArgument' => __DIR__ . '/../..' . '/src/DependencyInjection/Argument/ServiceClosureArgument.php', + 'izi\\prestashop\\DependencyInjection\\Compiler\\AnalyzeServiceReferencesPass' => __DIR__ . '/../..' . '/src/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php', + 'izi\\prestashop\\DependencyInjection\\Compiler\\ProvideServiceLocatorFactoriesPass' => __DIR__ . '/../..' . '/src/DependencyInjection/Compiler/ProvideServiceLocatorFactoriesPass.php', + 'izi\\prestashop\\DependencyInjection\\Compiler\\TaggedIteratorsCollectorPass' => __DIR__ . '/../..' . '/src/DependencyInjection/Compiler/TaggedIteratorsCollectorPass.php', + 'izi\\prestashop\\DependencyInjection\\ContainerBuilder' => __DIR__ . '/../..' . '/src/DependencyInjection/ContainerBuilder.php', + 'izi\\prestashop\\DependencyInjection\\ContainerFactory' => __DIR__ . '/../..' . '/src/DependencyInjection/ContainerFactory.php', + 'izi\\prestashop\\DependencyInjection\\Dumper\\PhpDumper' => __DIR__ . '/../..' . '/src/DependencyInjection/Dumper/PhpDumper.php', + 'izi\\prestashop\\DependencyInjection\\Exception\\ContainerNotFoundException' => __DIR__ . '/../..' . '/src/DependencyInjection/Exception/ContainerNotFoundException.php', + 'izi\\prestashop\\DependencyInjection\\ServiceLocator' => __DIR__ . '/../..' . '/src/DependencyInjection/ServiceLocator.php', + 'izi\\prestashop\\DependencyInjection\\ServiceSubscriberInterface' => __DIR__ . '/../..' . '/src/DependencyInjection/ServiceSubscriberInterface.php', + 'izi\\prestashop\\DependencyInjection\\TypedReference' => __DIR__ . '/../..' . '/src/DependencyInjection/TypedReference.php', + 'izi\\prestashop\\Entities\\BasketInterface' => __DIR__ . '/../..' . '/src/Entities/BasketInterface.php', + 'izi\\prestashop\\Entities\\BasketSession' => __DIR__ . '/../..' . '/src/Entities/BasketSession.php', + 'izi\\prestashop\\Entities\\BasketSessionInterface' => __DIR__ . '/../..' . '/src/Entities/BasketSessionInterface.php', + 'izi\\prestashop\\Entities\\Cart' => __DIR__ . '/../..' . '/src/Entities/Cart.php', + 'izi\\prestashop\\Entities\\CartProxy' => __DIR__ . '/../..' . '/src/Entities/CartProxy.php', + 'izi\\prestashop\\Entities\\SwitchableBasketSessionInterface' => __DIR__ . '/../..' . '/src/Entities/SwitchableBasketSessionInterface.php', + 'izi\\prestashop\\Enum\\Enum' => __DIR__ . '/../..' . '/src/Enum/Enum.php', + 'izi\\prestashop\\Enum\\IntEnum' => __DIR__ . '/../..' . '/src/Enum/IntEnum.php', + 'izi\\prestashop\\Enum\\StringEnum' => __DIR__ . '/../..' . '/src/Enum/StringEnum.php', + 'izi\\prestashop\\Environment\\AuthServerUriCollection' => __DIR__ . '/../..' . '/src/Environment/AuthServerUriCollection.php', + 'izi\\prestashop\\Environment\\EnvironmentFactory' => __DIR__ . '/../..' . '/src/Environment/EnvironmentFactory.php', + 'izi\\prestashop\\Environment\\EnvironmentFactoryInterface' => __DIR__ . '/../..' . '/src/Environment/EnvironmentFactoryInterface.php', + 'izi\\prestashop\\Environment\\EnvironmentInterface' => __DIR__ . '/../..' . '/src/Environment/EnvironmentInterface.php', + 'izi\\prestashop\\Environment\\EnvironmentType' => __DIR__ . '/../..' . '/src/Environment/EnvironmentType.php', + 'izi\\prestashop\\Environment\\ProductionEnvironment' => __DIR__ . '/../..' . '/src/Environment/ProductionEnvironment.php', + 'izi\\prestashop\\Environment\\SandboxEnvironment' => __DIR__ . '/../..' . '/src/Environment/SandboxEnvironment.php', + 'izi\\prestashop\\EventListener\\CartListener' => __DIR__ . '/../..' . '/src/EventListener/CartListener.php', + 'izi\\prestashop\\EventListener\\CreateShipmentListener' => __DIR__ . '/../..' . '/src/EventListener/CreateShipmentListener.php', + 'izi\\prestashop\\EventListener\\OrderListener' => __DIR__ . '/../..' . '/src/EventListener/OrderListener.php', + 'izi\\prestashop\\EventListener\\ShipmentListener' => __DIR__ . '/../..' . '/src/EventListener/ShipmentListener.php', + 'izi\\prestashop\\Event\\Adapter\\EventDispatcher' => __DIR__ . '/../..' . '/src/Event/Adapter/EventDispatcher.php', + 'izi\\prestashop\\Event\\CartUpdatedEvent' => __DIR__ . '/../..' . '/src/Event/CartUpdatedEvent.php', + 'izi\\prestashop\\Event\\CreateShipmentRequestEvent' => __DIR__ . '/../..' . '/src/Event/CreateShipmentRequestEvent.php', + 'izi\\prestashop\\Event\\CreateShipmentRequestProcessedEvent' => __DIR__ . '/../..' . '/src/Event/CreateShipmentRequestProcessedEvent.php', + 'izi\\prestashop\\Event\\Event' => __DIR__ . '/../..' . '/src/Event/Event.php', + 'izi\\prestashop\\Event\\EventDispatcherFactory' => __DIR__ . '/../..' . '/src/Event/EventDispatcherFactory.php', + 'izi\\prestashop\\Event\\EventDispatcherInterface' => __DIR__ . '/../..' . '/src/Event/EventDispatcherInterface.php', + 'izi\\prestashop\\Event\\OrderEvent' => __DIR__ . '/../..' . '/src/Event/OrderEvent.php', + 'izi\\prestashop\\Event\\OrderStatusUpdatedEvent' => __DIR__ . '/../..' . '/src/Event/OrderStatusUpdatedEvent.php', + 'izi\\prestashop\\Event\\ShipmentEvent' => __DIR__ . '/../..' . '/src/Event/ShipmentEvent.php', + 'izi\\prestashop\\Event\\ValidateOrderEvent' => __DIR__ . '/../..' . '/src/Event/ValidateOrderEvent.php', + 'izi\\prestashop\\Form\\BasketAppClientProvider' => __DIR__ . '/../..' . '/src/Form/BasketAppClientProvider.php', + 'izi\\prestashop\\Form\\ChoiceList\\AvailablePaymentOptionChoiceLoader' => __DIR__ . '/../..' . '/src/Form/ChoiceList/AvailablePaymentOptionChoiceLoader.php', + 'izi\\prestashop\\Form\\ChoiceList\\CarrierChoiceLoader' => __DIR__ . '/../..' . '/src/Form/ChoiceList/CarrierChoiceLoader.php', + 'izi\\prestashop\\Form\\ChoiceList\\LazyChoiceLoader' => __DIR__ . '/../..' . '/src/Form/ChoiceList/LazyChoiceLoader.php', + 'izi\\prestashop\\Form\\ChoiceList\\ObjectModelChoiceLoader' => __DIR__ . '/../..' . '/src/Form/ChoiceList/ObjectModelChoiceLoader.php', + 'izi\\prestashop\\Form\\ChoiceList\\OrderStateChoiceLoader' => __DIR__ . '/../..' . '/src/Form/ChoiceList/OrderStateChoiceLoader.php', + 'izi\\prestashop\\Form\\ChoiceList\\ProductImageTypeChoiceLoader' => __DIR__ . '/../..' . '/src/Form/ChoiceList/ProductImageTypeChoiceLoader.php', + 'izi\\prestashop\\Form\\DataMapper\\ClientCredentialsDataMapper' => __DIR__ . '/../..' . '/src/Form/DataMapper/ClientCredentialsDataMapper.php', + 'izi\\prestashop\\Form\\DataTransformer\\CombinationToAttributeIdsTransformer' => __DIR__ . '/../..' . '/src/Form/DataTransformer/CombinationToAttributeIdsTransformer.php', + 'izi\\prestashop\\Form\\DataTransformer\\DateTimeImmutableToDateTimeTransformer' => __DIR__ . '/../..' . '/src/Form/DataTransformer/DateTimeImmutableToDateTimeTransformer.php', + 'izi\\prestashop\\Form\\DataTransformer\\EnumDataTransformer' => __DIR__ . '/../..' . '/src/Form/DataTransformer/EnumDataTransformer.php', + 'izi\\prestashop\\Form\\DataTransformer\\ObjectModelCollectionToIdsTransformer' => __DIR__ . '/../..' . '/src/Form/DataTransformer/ObjectModelCollectionToIdsTransformer.php', + 'izi\\prestashop\\Form\\DataTransformer\\ObjectModelToIdTransformer' => __DIR__ . '/../..' . '/src/Form/DataTransformer/ObjectModelToIdTransformer.php', + 'izi\\prestashop\\Form\\EventListener\\ReindexDataListener' => __DIR__ . '/../..' . '/src/Form/EventListener/ReindexDataListener.php', + 'izi\\prestashop\\Form\\Event\\ApiConfigurationValidatedEvent' => __DIR__ . '/../..' . '/src/Form/Event/ApiConfigurationValidatedEvent.php', + 'izi\\prestashop\\Form\\Extension\\DependencyInjectionExtension' => __DIR__ . '/../..' . '/src/Form/Extension/DependencyInjectionExtension.php', + 'izi\\prestashop\\Form\\FormFactoryFactory' => __DIR__ . '/../..' . '/src/Form/FormFactoryFactory.php', + 'izi\\prestashop\\Form\\TypeExtension\\ChoicesAsValuesTypeExtension' => __DIR__ . '/../..' . '/src/Form/TypeExtension/ChoicesAsValuesTypeExtension.php', + 'izi\\prestashop\\Form\\TypeExtension\\DatePickerCompatibilityTypeExtension' => __DIR__ . '/../..' . '/src/Form/TypeExtension/DatePickerCompatibilityTypeExtension.php', + 'izi\\prestashop\\Form\\TypeExtension\\DateTimeImmutableTimeTypeExtension' => __DIR__ . '/../..' . '/src/Form/TypeExtension/DateTimeImmutableTimeTypeExtension.php', + 'izi\\prestashop\\Form\\TypeExtension\\HelpTextExtension' => __DIR__ . '/../..' . '/src/Form/TypeExtension/HelpTextExtension.php', + 'izi\\prestashop\\Form\\TypeExtension\\UnitTypeExtension' => __DIR__ . '/../..' . '/src/Form/TypeExtension/UnitTypeExtension.php', + 'izi\\prestashop\\Form\\Type\\AdvancedConfigurationType' => __DIR__ . '/../..' . '/src/Form/Type/AdvancedConfigurationType.php', + 'izi\\prestashop\\Form\\Type\\ApiConfigurationType' => __DIR__ . '/../..' . '/src/Form/Type/ApiConfigurationType.php', + 'izi\\prestashop\\Form\\Type\\CartRuleOptionsType' => __DIR__ . '/../..' . '/src/Form/Type/CartRuleOptionsType.php', + 'izi\\prestashop\\Form\\Type\\ClientCredentialsType' => __DIR__ . '/../..' . '/src/Form/Type/ClientCredentialsType.php', + 'izi\\prestashop\\Form\\Type\\Compatibility\\CategoryChoiceTreeType' => __DIR__ . '/../..' . '/src/Form/Type/Compatibility/CategoryChoiceTreeType.php', + 'izi\\prestashop\\Form\\Type\\Consent\\ConsentLinkType' => __DIR__ . '/../..' . '/src/Form/Type/Consent/ConsentLinkType.php', + 'izi\\prestashop\\Form\\Type\\Consent\\ConsentRequirementChoiceType' => __DIR__ . '/../..' . '/src/Form/Type/Consent/ConsentRequirementChoiceType.php', + 'izi\\prestashop\\Form\\Type\\Consent\\ConsentType' => __DIR__ . '/../..' . '/src/Form/Type/Consent/ConsentType.php', + 'izi\\prestashop\\Form\\Type\\ConsentsConfigurationType' => __DIR__ . '/../..' . '/src/Form/Type/ConsentsConfigurationType.php', + 'izi\\prestashop\\Form\\Type\\EnvironmentChoiceType' => __DIR__ . '/../..' . '/src/Form/Type/EnvironmentChoiceType.php', + 'izi\\prestashop\\Form\\Type\\GeneralConfigurationType' => __DIR__ . '/../..' . '/src/Form/Type/GeneralConfigurationType.php', + 'izi\\prestashop\\Form\\Type\\GuiConfigurationType' => __DIR__ . '/../..' . '/src/Form/Type/GuiConfigurationType.php', + 'izi\\prestashop\\Form\\Type\\Image\\ImageTypeChoiceType' => __DIR__ . '/../..' . '/src/Form/Type/Image/ImageTypeChoiceType.php', + 'izi\\prestashop\\Form\\Type\\MaskedPasswordType' => __DIR__ . '/../..' . '/src/Form/Type/MaskedPasswordType.php', + 'izi\\prestashop\\Form\\Type\\ObjectModelAutocompleteType' => __DIR__ . '/../..' . '/src/Form/Type/ObjectModelAutocompleteType.php', + 'izi\\prestashop\\Form\\Type\\ObjectModelType' => __DIR__ . '/../..' . '/src/Form/Type/ObjectModelType.php', + 'izi\\prestashop\\Form\\Type\\OrderStateChoiceType' => __DIR__ . '/../..' . '/src/Form/Type/OrderStateChoiceType.php', + 'izi\\prestashop\\Form\\Type\\OrderStatusDescriptionMapType' => __DIR__ . '/../..' . '/src/Form/Type/OrderStatusDescriptionMapType.php', + 'izi\\prestashop\\Form\\Type\\Order\\AvailablePaymentOptionsChoiceType' => __DIR__ . '/../..' . '/src/Form/Type/Order/AvailablePaymentOptionsChoiceType.php', + 'izi\\prestashop\\Form\\Type\\Order\\MessageOptionsType' => __DIR__ . '/../..' . '/src/Form/Type/Order/MessageOptionsType.php', + 'izi\\prestashop\\Form\\Type\\OrdersConfigurationType' => __DIR__ . '/../..' . '/src/Form/Type/OrdersConfigurationType.php', + 'izi\\prestashop\\Form\\Type\\ProductConfigurationType' => __DIR__ . '/../..' . '/src/Form/Type/ProductConfigurationType.php', + 'izi\\prestashop\\Form\\Type\\Product\\CombinationByAttributesChoiceType' => __DIR__ . '/../..' . '/src/Form/Type/Product/CombinationByAttributesChoiceType.php', + 'izi\\prestashop\\Form\\Type\\Product\\ProductRestrictionsType' => __DIR__ . '/../..' . '/src/Form/Type/Product/ProductRestrictionsType.php', + 'izi\\prestashop\\Form\\Type\\ShippingConfigurationType' => __DIR__ . '/../..' . '/src/Form/Type/ShippingConfigurationType.php', + 'izi\\prestashop\\Form\\Type\\Shipping\\CarrierChoiceType' => __DIR__ . '/../..' . '/src/Form/Type/Shipping/CarrierChoiceType.php', + 'izi\\prestashop\\Form\\Type\\Shipping\\CarrierMappingType' => __DIR__ . '/../..' . '/src/Form/Type/Shipping/CarrierMappingType.php', + 'izi\\prestashop\\Form\\Type\\Shipping\\CarrierMappingsType' => __DIR__ . '/../..' . '/src/Form/Type/Shipping/CarrierMappingsType.php', + 'izi\\prestashop\\Form\\Type\\Shipping\\OptionalServicesType' => __DIR__ . '/../..' . '/src/Form/Type/Shipping/OptionalServicesType.php', + 'izi\\prestashop\\Form\\Type\\Shipping\\ServiceOptionsType' => __DIR__ . '/../..' . '/src/Form/Type/Shipping/ServiceOptionsType.php', + 'izi\\prestashop\\Form\\Type\\Shipping\\ShippingOptionsType' => __DIR__ . '/../..' . '/src/Form/Type/Shipping/ShippingOptionsType.php', + 'izi\\prestashop\\Form\\Type\\Shipping\\TimeOfWeekRangeType' => __DIR__ . '/../..' . '/src/Form/Type/Shipping/TimeOfWeekRangeType.php', + 'izi\\prestashop\\Form\\Type\\Shipping\\TimeOfWeekType' => __DIR__ . '/../..' . '/src/Form/Type/Shipping/TimeOfWeekType.php', + 'izi\\prestashop\\Form\\Type\\Shipping\\WeekDayChoiceType' => __DIR__ . '/../..' . '/src/Form/Type/Shipping/WeekDayChoiceType.php', + 'izi\\prestashop\\Form\\Type\\SwitchType' => __DIR__ . '/../..' . '/src/Form/Type/SwitchType.php', + 'izi\\prestashop\\Form\\Type\\TranslatableType' => __DIR__ . '/../..' . '/src/Form/Type/TranslatableType.php', + 'izi\\prestashop\\Form\\Type\\Widget\\HtmlStylesType' => __DIR__ . '/../..' . '/src/Form/Type/Widget/HtmlStylesType.php', + 'izi\\prestashop\\Form\\Type\\Widget\\ProductPageDisplayConfigurationType' => __DIR__ . '/../..' . '/src/Form/Type/Widget/ProductPageDisplayConfigurationType.php', + 'izi\\prestashop\\Form\\Type\\Widget\\WidgetConfigurationType' => __DIR__ . '/../..' . '/src/Form/Type/Widget/WidgetConfigurationType.php', + 'izi\\prestashop\\Form\\Type\\Widget\\WidgetDisplayConfigurationType' => __DIR__ . '/../..' . '/src/Form/Type/Widget/WidgetDisplayConfigurationType.php', + 'izi\\prestashop\\Form\\Type\\Widget\\WidgetFrameStyleChoiceType' => __DIR__ . '/../..' . '/src/Form/Type/Widget/WidgetFrameStyleChoiceType.php', + 'izi\\prestashop\\Form\\Type\\Widget\\WidgetSizeChoiceType' => __DIR__ . '/../..' . '/src/Form/Type/Widget/WidgetSizeChoiceType.php', + 'izi\\prestashop\\Form\\Type\\Widget\\WidgetVariantChoiceType' => __DIR__ . '/../..' . '/src/Form/Type/Widget/WidgetVariantChoiceType.php', + 'izi\\prestashop\\Handler\\CommandHandlerTrait' => __DIR__ . '/../..' . '/src/Handler/CommandHandlerTrait.php', + 'izi\\prestashop\\Handler\\Config\\CheckStatusHandler' => __DIR__ . '/../..' . '/src/Handler/Config/CheckStatusHandler.php', + 'izi\\prestashop\\Handler\\Config\\CheckStatusHandlerInterface' => __DIR__ . '/../..' . '/src/Handler/Config/CheckStatusHandlerInterface.php', + 'izi\\prestashop\\Handler\\Config\\DownloadModuleDataHandler' => __DIR__ . '/../..' . '/src/Handler/Config/DownloadModuleDataHandler.php', + 'izi\\prestashop\\Handler\\Config\\DownloadModuleDataHandlerInterface' => __DIR__ . '/../..' . '/src/Handler/Config/DownloadModuleDataHandlerInterface.php', + 'izi\\prestashop\\Handler\\Config\\ModuleStatus' => __DIR__ . '/../..' . '/src/Handler/Config/ModuleStatus.php', + 'izi\\prestashop\\Handler\\Config\\Status\\CacheStatusChecker' => __DIR__ . '/../..' . '/src/Handler/Config/Status/CacheStatusChecker.php', + 'izi\\prestashop\\Handler\\Config\\Status\\ConfigurationStatusChecker' => __DIR__ . '/../..' . '/src/Handler/Config/Status/ConfigurationStatusChecker.php', + 'izi\\prestashop\\Handler\\Config\\Status\\DeliveryOptionsStatusChecker' => __DIR__ . '/../..' . '/src/Handler/Config/Status/DeliveryOptionsStatusChecker.php', + 'izi\\prestashop\\Handler\\Config\\Status\\StatusCheckerInterface' => __DIR__ . '/../..' . '/src/Handler/Config/Status/StatusCheckerInterface.php', + 'izi\\prestashop\\Handler\\Config\\UpdateAdvancedConfigurationHandler' => __DIR__ . '/../..' . '/src/Handler/Config/UpdateAdvancedConfigurationHandler.php', + 'izi\\prestashop\\Handler\\Config\\UpdateAdvancedConfigurationHandlerInterface' => __DIR__ . '/../..' . '/src/Handler/Config/UpdateAdvancedConfigurationHandlerInterface.php', + 'izi\\prestashop\\Handler\\Config\\UpdateCartRuleOptionsHandler' => __DIR__ . '/../..' . '/src/Handler/Config/UpdateCartRuleOptionsHandler.php', + 'izi\\prestashop\\Handler\\Config\\UpdateCartRuleOptionsHandlerInterface' => __DIR__ . '/../..' . '/src/Handler/Config/UpdateCartRuleOptionsHandlerInterface.php', + 'izi\\prestashop\\Handler\\Config\\UpdateConsentsConfigurationHandler' => __DIR__ . '/../..' . '/src/Handler/Config/UpdateConsentsConfigurationHandler.php', + 'izi\\prestashop\\Handler\\Config\\UpdateConsentsConfigurationHandlerInterface' => __DIR__ . '/../..' . '/src/Handler/Config/UpdateConsentsConfigurationHandlerInterface.php', + 'izi\\prestashop\\Handler\\Config\\UpdateGeneralConfigurationHandler' => __DIR__ . '/../..' . '/src/Handler/Config/UpdateGeneralConfigurationHandler.php', + 'izi\\prestashop\\Handler\\Config\\UpdateGeneralConfigurationHandlerInterface' => __DIR__ . '/../..' . '/src/Handler/Config/UpdateGeneralConfigurationHandlerInterface.php', + 'izi\\prestashop\\Handler\\Config\\UpdateGuiConfigurationHandler' => __DIR__ . '/../..' . '/src/Handler/Config/UpdateGuiConfigurationHandler.php', + 'izi\\prestashop\\Handler\\Config\\UpdateGuiConfigurationHandlerInterface' => __DIR__ . '/../..' . '/src/Handler/Config/UpdateGuiConfigurationHandlerInterface.php', + 'izi\\prestashop\\Handler\\Config\\UpdateShippingConfigurationHandler' => __DIR__ . '/../..' . '/src/Handler/Config/UpdateShippingConfigurationHandler.php', + 'izi\\prestashop\\Handler\\Config\\UpdateShippingConfigurationHandlerInterface' => __DIR__ . '/../..' . '/src/Handler/Config/UpdateShippingConfigurationHandlerInterface.php', + 'izi\\prestashop\\Handler\\GetBasketBindingKeyHandler' => __DIR__ . '/../..' . '/src/Handler/GetBasketBindingKeyHandler.php', + 'izi\\prestashop\\Handler\\GetBasketBindingKeyHandlerInterface' => __DIR__ . '/../..' . '/src/Handler/GetBasketBindingKeyHandlerInterface.php', + 'izi\\prestashop\\Handler\\GetOrderConfirmationUrlHandler' => __DIR__ . '/../..' . '/src/Handler/GetOrderConfirmationUrlHandler.php', + 'izi\\prestashop\\Handler\\GetOrderConfirmationUrlHandlerInterface' => __DIR__ . '/../..' . '/src/Handler/GetOrderConfirmationUrlHandlerInterface.php', + 'izi\\prestashop\\Handler\\Result\\BasketBindingKey' => __DIR__ . '/../..' . '/src/Handler/Result/BasketBindingKey.php', + 'izi\\prestashop\\Handler\\UnbindBasketHandler' => __DIR__ . '/../..' . '/src/Handler/UnbindBasketHandler.php', + 'izi\\prestashop\\Handler\\UnbindBasketHandlerInterface' => __DIR__ . '/../..' . '/src/Handler/UnbindBasketHandlerInterface.php', + 'izi\\prestashop\\Handler\\UpdateBasketHandler' => __DIR__ . '/../..' . '/src/Handler/UpdateBasketHandler.php', + 'izi\\prestashop\\Handler\\UpdateBasketHandlerInterface' => __DIR__ . '/../..' . '/src/Handler/UpdateBasketHandlerInterface.php', + 'izi\\prestashop\\Handler\\UpdateOrderAddressDeliveryHandler' => __DIR__ . '/../..' . '/src/Handler/UpdateOrderAddressDeliveryHandler.php', + 'izi\\prestashop\\Handler\\UpdateOrderAddressDeliveryHandlerInterface' => __DIR__ . '/../..' . '/src/Handler/UpdateOrderAddressDeliveryHandlerInterface.php', + 'izi\\prestashop\\Handler\\UpdateOrderStatusHandler' => __DIR__ . '/../..' . '/src/Handler/UpdateOrderStatusHandler.php', + 'izi\\prestashop\\Handler\\UpdateOrderStatusHandlerInterface' => __DIR__ . '/../..' . '/src/Handler/UpdateOrderStatusHandlerInterface.php', + 'izi\\prestashop\\Handler\\UpdateOrderTrackingNumbersHandler' => __DIR__ . '/../..' . '/src/Handler/UpdateOrderTrackingNumbersHandler.php', + 'izi\\prestashop\\Handler\\UpdateOrderTrackingNumbersHandlerInterface' => __DIR__ . '/../..' . '/src/Handler/UpdateOrderTrackingNumbersHandlerInterface.php', + 'izi\\prestashop\\Hook\\Adapter\\HookDispatcher' => __DIR__ . '/../..' . '/src/Hook/Adapter/HookDispatcher.php', + 'izi\\prestashop\\Hook\\Admin\\ActionAdminCartRuleSaveAfter' => __DIR__ . '/../..' . '/src/Hook/Admin/ActionAdminCartRuleSaveAfter.php', + 'izi\\prestashop\\Hook\\Admin\\ActionAdminControllerSetMedia' => __DIR__ . '/../..' . '/src/Hook/Admin/ActionAdminControllerSetMedia.php', + 'izi\\prestashop\\Hook\\Admin\\ActionAdminInPostConfirmedShipmentsControllerAfter' => __DIR__ . '/../..' . '/src/Hook/Admin/ActionAdminInPostConfirmedShipmentsControllerAfter.php', + 'izi\\prestashop\\Hook\\Admin\\ActionAdminInPostConfirmedShipmentsControllerBefore' => __DIR__ . '/../..' . '/src/Hook/Admin/ActionAdminInPostConfirmedShipmentsControllerBefore.php', + 'izi\\prestashop\\Hook\\Admin\\DisplayAdminOrderLeft' => __DIR__ . '/../..' . '/src/Hook/Admin/DisplayAdminOrderLeft.php', + 'izi\\prestashop\\Hook\\Admin\\DisplayAdminOrderSide' => __DIR__ . '/../..' . '/src/Hook/Admin/DisplayAdminOrderSide.php', + 'izi\\prestashop\\Hook\\Admin\\DisplayBackOfficeHeader' => __DIR__ . '/../..' . '/src/Hook/Admin/DisplayBackOfficeHeader.php', + 'izi\\prestashop\\Hook\\AliasedHookInterface' => __DIR__ . '/../..' . '/src/Hook/AliasedHookInterface.php', + 'izi\\prestashop\\Hook\\AssetRegistryUpdaterTrait' => __DIR__ . '/../..' . '/src/Hook/AssetRegistryUpdaterTrait.php', + 'izi\\prestashop\\Hook\\Common\\ActionCartDeleteBefore' => __DIR__ . '/../..' . '/src/Hook/Common/ActionCartDeleteBefore.php', + 'izi\\prestashop\\Hook\\Common\\ActionCartUpdateAfter' => __DIR__ . '/../..' . '/src/Hook/Common/ActionCartUpdateAfter.php', + 'izi\\prestashop\\Hook\\Common\\ActionEmailSendBefore' => __DIR__ . '/../..' . '/src/Hook/Common/ActionEmailSendBefore.php', + 'izi\\prestashop\\Hook\\Common\\ActionObjectOrderUpdateAfter' => __DIR__ . '/../..' . '/src/Hook/Common/ActionObjectOrderUpdateAfter.php', + 'izi\\prestashop\\Hook\\Common\\ActionObjectOrderUpdateBefore' => __DIR__ . '/../..' . '/src/Hook/Common/ActionObjectOrderUpdateBefore.php', + 'izi\\prestashop\\Hook\\Common\\ActionOrderStatusPostUpdate' => __DIR__ . '/../..' . '/src/Hook/Common/ActionOrderStatusPostUpdate.php', + 'izi\\prestashop\\Hook\\Common\\ActionShipmentAddAfter' => __DIR__ . '/../..' . '/src/Hook/Common/ActionShipmentAddAfter.php', + 'izi\\prestashop\\Hook\\Common\\ActionShipmentUpdateAfter' => __DIR__ . '/../..' . '/src/Hook/Common/ActionShipmentUpdateAfter.php', + 'izi\\prestashop\\Hook\\Common\\ActionShipmentUpdateBefore' => __DIR__ . '/../..' . '/src/Hook/Common/ActionShipmentUpdateBefore.php', + 'izi\\prestashop\\Hook\\Common\\ActionValidateOrder' => __DIR__ . '/../..' . '/src/Hook/Common/ActionValidateOrder.php', + 'izi\\prestashop\\Hook\\Common\\Product\\ActionCombinationDeleteAfter' => __DIR__ . '/../..' . '/src/Hook/Common/Product/ActionCombinationDeleteAfter.php', + 'izi\\prestashop\\Hook\\Common\\Product\\ActionCombinationDeleteBefore' => __DIR__ . '/../..' . '/src/Hook/Common/Product/ActionCombinationDeleteBefore.php', + 'izi\\prestashop\\Hook\\Common\\Product\\ActionCombinationUpdateAfter' => __DIR__ . '/../..' . '/src/Hook/Common/Product/ActionCombinationUpdateAfter.php', + 'izi\\prestashop\\Hook\\Common\\Product\\ActionImageAddAfter' => __DIR__ . '/../..' . '/src/Hook/Common/Product/ActionImageAddAfter.php', + 'izi\\prestashop\\Hook\\Common\\Product\\ActionImageDeleteAfter' => __DIR__ . '/../..' . '/src/Hook/Common/Product/ActionImageDeleteAfter.php', + 'izi\\prestashop\\Hook\\Common\\Product\\ActionProductDeleteAfter' => __DIR__ . '/../..' . '/src/Hook/Common/Product/ActionProductDeleteAfter.php', + 'izi\\prestashop\\Hook\\Common\\Product\\ActionProductDeleteBefore' => __DIR__ . '/../..' . '/src/Hook/Common/Product/ActionProductDeleteBefore.php', + 'izi\\prestashop\\Hook\\Common\\Product\\ActionProductUpdateAfter' => __DIR__ . '/../..' . '/src/Hook/Common/Product/ActionProductUpdateAfter.php', + 'izi\\prestashop\\Hook\\Common\\Product\\ActionSpecificPriceAddAfter' => __DIR__ . '/../..' . '/src/Hook/Common/Product/ActionSpecificPriceAddAfter.php', + 'izi\\prestashop\\Hook\\Common\\Product\\ActionSpecificPriceDeleteAfter' => __DIR__ . '/../..' . '/src/Hook/Common/Product/ActionSpecificPriceDeleteAfter.php', + 'izi\\prestashop\\Hook\\Common\\Product\\ActionSpecificPriceUpdateAfter' => __DIR__ . '/../..' . '/src/Hook/Common/Product/ActionSpecificPriceUpdateAfter.php', + 'izi\\prestashop\\Hook\\Common\\Product\\ActionUpdateQuantity' => __DIR__ . '/../..' . '/src/Hook/Common/Product/ActionUpdateQuantity.php', + 'izi\\prestashop\\Hook\\Exception\\HookExceptionInterface' => __DIR__ . '/../..' . '/src/Hook/Exception/HookExceptionInterface.php', + 'izi\\prestashop\\Hook\\Exception\\HookExceptionTrait' => __DIR__ . '/../..' . '/src/Hook/Exception/HookExceptionTrait.php', + 'izi\\prestashop\\Hook\\Exception\\HookNotFoundException' => __DIR__ . '/../..' . '/src/Hook/Exception/HookNotFoundException.php', + 'izi\\prestashop\\Hook\\Exception\\HookNotImplementedException' => __DIR__ . '/../..' . '/src/Hook/Exception/HookNotImplementedException.php', + 'izi\\prestashop\\Hook\\Front\\ActionCartControllerAjaxUpdateResponse' => __DIR__ . '/../..' . '/src/Hook/Front/ActionCartControllerAjaxUpdateResponse.php', + 'izi\\prestashop\\Hook\\Front\\ActionFrontControllerSetMedia' => __DIR__ . '/../..' . '/src/Hook/Front/ActionFrontControllerSetMedia.php', + 'izi\\prestashop\\Hook\\Front\\ActionGetPaymentOptions' => __DIR__ . '/../..' . '/src/Hook/Front/ActionGetPaymentOptions.php', + 'izi\\prestashop\\Hook\\Front\\ButtonWidgetRendererTrait' => __DIR__ . '/../..' . '/src/Hook/Front/ButtonWidgetRendererTrait.php', + 'izi\\prestashop\\Hook\\Front\\DisplayCheckoutSummaryTop' => __DIR__ . '/../..' . '/src/Hook/Front/DisplayCheckoutSummaryTop.php', + 'izi\\prestashop\\Hook\\Front\\DisplayCustomerAccountFormTop' => __DIR__ . '/../..' . '/src/Hook/Front/DisplayCustomerAccountFormTop.php', + 'izi\\prestashop\\Hook\\Front\\DisplayCustomerLoginFormAfter' => __DIR__ . '/../..' . '/src/Hook/Front/DisplayCustomerLoginFormAfter.php', + 'izi\\prestashop\\Hook\\Front\\DisplayExpressCheckout' => __DIR__ . '/../..' . '/src/Hook/Front/DisplayExpressCheckout.php', + 'izi\\prestashop\\Hook\\Front\\DisplayHeader' => __DIR__ . '/../..' . '/src/Hook/Front/DisplayHeader.php', + 'izi\\prestashop\\Hook\\Front\\DisplayIziCartPreviewButton' => __DIR__ . '/../..' . '/src/Hook/Front/DisplayIziCartPreviewButton.php', + 'izi\\prestashop\\Hook\\Front\\DisplayIziCheckoutButton' => __DIR__ . '/../..' . '/src/Hook/Front/DisplayIziCheckoutButton.php', + 'izi\\prestashop\\Hook\\Front\\DisplayIziThankYou' => __DIR__ . '/../..' . '/src/Hook/Front/DisplayIziThankYou.php', + 'izi\\prestashop\\Hook\\Front\\DisplayOrderConfirmation' => __DIR__ . '/../..' . '/src/Hook/Front/DisplayOrderConfirmation.php', + 'izi\\prestashop\\Hook\\Front\\DisplayPaymentReturn' => __DIR__ . '/../..' . '/src/Hook/Front/DisplayPaymentReturn.php', + 'izi\\prestashop\\Hook\\Front\\DisplayProductActions' => __DIR__ . '/../..' . '/src/Hook/Front/DisplayProductActions.php', + 'izi\\prestashop\\Hook\\Front\\DisplayProductAdditionalInfo' => __DIR__ . '/../..' . '/src/Hook/Front/DisplayProductAdditionalInfo.php', + 'izi\\prestashop\\Hook\\Front\\DisplayShoppingCart' => __DIR__ . '/../..' . '/src/Hook/Front/DisplayShoppingCart.php', + 'izi\\prestashop\\Hook\\Front\\DisplayShoppingCartFooter' => __DIR__ . '/../..' . '/src/Hook/Front/DisplayShoppingCartFooter.php', + 'izi\\prestashop\\Hook\\Front\\ProductWidgetRendererTrait' => __DIR__ . '/../..' . '/src/Hook/Front/ProductWidgetRendererTrait.php', + 'izi\\prestashop\\Hook\\Front\\ThankYouWidgetRendererTrait' => __DIR__ . '/../..' . '/src/Hook/Front/ThankYouWidgetRendererTrait.php', + 'izi\\prestashop\\Hook\\HookDispatcherInterface' => __DIR__ . '/../..' . '/src/Hook/HookDispatcherInterface.php', + 'izi\\prestashop\\Hook\\HookExecutor' => __DIR__ . '/../..' . '/src/Hook/HookExecutor.php', + 'izi\\prestashop\\Hook\\HookExecutorInterface' => __DIR__ . '/../..' . '/src/Hook/HookExecutorInterface.php', + 'izi\\prestashop\\Hook\\HookInterface' => __DIR__ . '/../..' . '/src/Hook/HookInterface.php', + 'izi\\prestashop\\Hook\\PrestaShopVersionAwareHookInterface' => __DIR__ . '/../..' . '/src/Hook/PrestaShopVersionAwareHookInterface.php', + 'izi\\prestashop\\Hook\\VersionRange' => __DIR__ . '/../..' . '/src/Hook/VersionRange.php', + 'izi\\prestashop\\Hook\\Widget' => __DIR__ . '/../..' . '/src/Hook/Widget.php', + 'izi\\prestashop\\Hook\\WidgetParametersProvider' => __DIR__ . '/../..' . '/src/Hook/WidgetParametersProvider.php', + 'izi\\prestashop\\Hook\\WidgetParametersProviderInterface' => __DIR__ . '/../..' . '/src/Hook/WidgetParametersProviderInterface.php', + 'izi\\prestashop\\HotProduct\\EventListener\\UpdateHotProductsListener' => __DIR__ . '/../..' . '/src/HotProduct/EventListener/UpdateHotProductsListener.php', + 'izi\\prestashop\\HotProduct\\Exception\\HotProductExceptionInterface' => __DIR__ . '/../..' . '/src/HotProduct/Exception/HotProductExceptionInterface.php', + 'izi\\prestashop\\HotProduct\\Exception\\HotProductExistsException' => __DIR__ . '/../..' . '/src/HotProduct/Exception/HotProductExistsException.php', + 'izi\\prestashop\\HotProduct\\Exception\\HotProductNotFoundException' => __DIR__ . '/../..' . '/src/HotProduct/Exception/HotProductNotFoundException.php', + 'izi\\prestashop\\HotProduct\\Exception\\InvalidProductDataException' => __DIR__ . '/../..' . '/src/HotProduct/Exception/InvalidProductDataException.php', + 'izi\\prestashop\\HotProduct\\Form\\CreateHotProductType' => __DIR__ . '/../..' . '/src/HotProduct/Form/CreateHotProductType.php', + 'izi\\prestashop\\HotProduct\\Form\\UpdateHotProductType' => __DIR__ . '/../..' . '/src/HotProduct/Form/UpdateHotProductType.php', + 'izi\\prestashop\\HotProduct\\HotProduct' => __DIR__ . '/../..' . '/src/HotProduct/HotProduct.php', + 'izi\\prestashop\\HotProduct\\HotProductDataMapper' => __DIR__ . '/../..' . '/src/HotProduct/HotProductDataMapper.php', + 'izi\\prestashop\\HotProduct\\HotProductDataMapperInterface' => __DIR__ . '/../..' . '/src/HotProduct/HotProductDataMapperInterface.php', + 'izi\\prestashop\\HotProduct\\HotProductRepository' => __DIR__ . '/../..' . '/src/HotProduct/HotProductRepository.php', + 'izi\\prestashop\\HotProduct\\HotProductRepositoryInterface' => __DIR__ . '/../..' . '/src/HotProduct/HotProductRepositoryInterface.php', + 'izi\\prestashop\\HotProduct\\HotProductValidator' => __DIR__ . '/../..' . '/src/HotProduct/HotProductValidator.php', + 'izi\\prestashop\\HotProduct\\MessageHandler\\CreateHotProductHandler' => __DIR__ . '/../..' . '/src/HotProduct/MessageHandler/CreateHotProductHandler.php', + 'izi\\prestashop\\HotProduct\\MessageHandler\\CreateHotProductHandlerInterface' => __DIR__ . '/../..' . '/src/HotProduct/MessageHandler/CreateHotProductHandlerInterface.php', + 'izi\\prestashop\\HotProduct\\MessageHandler\\DeleteHotProductHandler' => __DIR__ . '/../..' . '/src/HotProduct/MessageHandler/DeleteHotProductHandler.php', + 'izi\\prestashop\\HotProduct\\MessageHandler\\DeleteHotProductHandlerInterface' => __DIR__ . '/../..' . '/src/HotProduct/MessageHandler/DeleteHotProductHandlerInterface.php', + 'izi\\prestashop\\HotProduct\\MessageHandler\\DeleteRemoteProductHandler' => __DIR__ . '/../..' . '/src/HotProduct/MessageHandler/DeleteRemoteProductHandler.php', + 'izi\\prestashop\\HotProduct\\MessageHandler\\DeleteRemoteProductHandlerInterface' => __DIR__ . '/../..' . '/src/HotProduct/MessageHandler/DeleteRemoteProductHandlerInterface.php', + 'izi\\prestashop\\HotProduct\\MessageHandler\\ImportHotProductHandler' => __DIR__ . '/../..' . '/src/HotProduct/MessageHandler/ImportHotProductHandler.php', + 'izi\\prestashop\\HotProduct\\MessageHandler\\ImportHotProductHandlerInterface' => __DIR__ . '/../..' . '/src/HotProduct/MessageHandler/ImportHotProductHandlerInterface.php', + 'izi\\prestashop\\HotProduct\\MessageHandler\\UpdateHotProductHandler' => __DIR__ . '/../..' . '/src/HotProduct/MessageHandler/UpdateHotProductHandler.php', + 'izi\\prestashop\\HotProduct\\MessageHandler\\UpdateHotProductHandlerInterface' => __DIR__ . '/../..' . '/src/HotProduct/MessageHandler/UpdateHotProductHandlerInterface.php', + 'izi\\prestashop\\HotProduct\\Message\\CreateHotProductCommand' => __DIR__ . '/../..' . '/src/HotProduct/Message/CreateHotProductCommand.php', + 'izi\\prestashop\\HotProduct\\Message\\DeleteHotProductCommand' => __DIR__ . '/../..' . '/src/HotProduct/Message/DeleteHotProductCommand.php', + 'izi\\prestashop\\HotProduct\\Message\\DeleteRemoteProductCommand' => __DIR__ . '/../..' . '/src/HotProduct/Message/DeleteRemoteProductCommand.php', + 'izi\\prestashop\\HotProduct\\Message\\ImportHotProductCommand' => __DIR__ . '/../..' . '/src/HotProduct/Message/ImportHotProductCommand.php', + 'izi\\prestashop\\HotProduct\\Message\\UpdateHotProductCommand' => __DIR__ . '/../..' . '/src/HotProduct/Message/UpdateHotProductCommand.php', + 'izi\\prestashop\\HotProduct\\View\\HotProductListView' => __DIR__ . '/../..' . '/src/HotProduct/View/HotProductListView.php', + 'izi\\prestashop\\HotProduct\\View\\HotProductView' => __DIR__ . '/../..' . '/src/HotProduct/View/HotProductView.php', + 'izi\\prestashop\\HotProduct\\View\\HotProductViewDataFactory' => __DIR__ . '/../..' . '/src/HotProduct/View/HotProductViewDataFactory.php', + 'izi\\prestashop\\HttpKernel\\ServiceParamConverter' => __DIR__ . '/../..' . '/src/HttpKernel/ServiceParamConverter.php', + 'izi\\prestashop\\Http\\Client\\Adapter\\Guzzle5Adapter' => __DIR__ . '/../..' . '/src/Http/Client/Adapter/Guzzle5Adapter.php', + 'izi\\prestashop\\Http\\Client\\Adapter\\NetworkException' => __DIR__ . '/../..' . '/src/Http/Client/Adapter/NetworkException.php', + 'izi\\prestashop\\Http\\Client\\Adapter\\RequestException' => __DIR__ . '/../..' . '/src/Http/Client/Adapter/RequestException.php', + 'izi\\prestashop\\Http\\Client\\AuthorizingClient' => __DIR__ . '/../..' . '/src/Http/Client/AuthorizingClient.php', + 'izi\\prestashop\\Http\\Client\\Factory\\ClientFactoryInterface' => __DIR__ . '/../..' . '/src/Http/Client/Factory/ClientFactoryInterface.php', + 'izi\\prestashop\\Http\\Client\\Factory\\GuzzleClientFactory' => __DIR__ . '/../..' . '/src/Http/Client/Factory/GuzzleClientFactory.php', + 'izi\\prestashop\\Http\\Client\\LoggingClient' => __DIR__ . '/../..' . '/src/Http/Client/LoggingClient.php', + 'izi\\prestashop\\Http\\Client\\ModuleVersionInfoProvidingClient' => __DIR__ . '/../..' . '/src/Http/Client/ModuleVersionInfoProvidingClient.php', + 'izi\\prestashop\\Http\\Exception\\ClientException' => __DIR__ . '/../..' . '/src/Http/Exception/ClientException.php', + 'izi\\prestashop\\Http\\Exception\\HttpExceptionInterface' => __DIR__ . '/../..' . '/src/Http/Exception/HttpExceptionInterface.php', + 'izi\\prestashop\\Http\\Exception\\HttpExceptionTrait' => __DIR__ . '/../..' . '/src/Http/Exception/HttpExceptionTrait.php', + 'izi\\prestashop\\Http\\Exception\\RedirectionException' => __DIR__ . '/../..' . '/src/Http/Exception/RedirectionException.php', + 'izi\\prestashop\\Http\\Exception\\ServerException' => __DIR__ . '/../..' . '/src/Http/Exception/ServerException.php', + 'izi\\prestashop\\Http\\Response\\EventStreamResponse' => __DIR__ . '/../..' . '/src/Http/Response/EventStreamResponse.php', + 'izi\\prestashop\\Http\\Response\\ServerSentEvent' => __DIR__ . '/../..' . '/src/Http/Response/ServerSentEvent.php', + 'izi\\prestashop\\Http\\Response\\ServerSentEventBuilder' => __DIR__ . '/../..' . '/src/Http/Response/ServerSentEventBuilder.php', + 'izi\\prestashop\\Http\\Util\\UriResolver' => __DIR__ . '/../..' . '/src/Http/Util/UriResolver.php', + 'izi\\prestashop\\Installer\\DatabaseInstaller' => __DIR__ . '/../..' . '/src/Installer/DatabaseInstaller.php', + 'izi\\prestashop\\Installer\\DatabaseMigrationInterface' => __DIR__ . '/../..' . '/src/Installer/DatabaseMigrationInterface.php', + 'izi\\prestashop\\Installer\\Database\\AbstractMigration' => __DIR__ . '/../..' . '/src/Installer/Database/AbstractMigration.php', + 'izi\\prestashop\\Installer\\Database\\Version_1_0_0' => __DIR__ . '/../..' . '/src/Installer/Database/Version_1_0_0.php', + 'izi\\prestashop\\Installer\\Database\\Version_1_11_0' => __DIR__ . '/../..' . '/src/Installer/Database/Version_1_11_0.php', + 'izi\\prestashop\\Installer\\Database\\Version_1_4_0' => __DIR__ . '/../..' . '/src/Installer/Database/Version_1_4_0.php', + 'izi\\prestashop\\Installer\\Database\\Version_1_9_0' => __DIR__ . '/../..' . '/src/Installer/Database/Version_1_9_0.php', + 'izi\\prestashop\\Installer\\Database\\Version_2_0_0' => __DIR__ . '/../..' . '/src/Installer/Database/Version_2_0_0.php', + 'izi\\prestashop\\Installer\\Database\\Version_2_1_0' => __DIR__ . '/../..' . '/src/Installer/Database/Version_2_1_0.php', + 'izi\\prestashop\\Installer\\Database\\Version_2_2_0' => __DIR__ . '/../..' . '/src/Installer/Database/Version_2_2_0.php', + 'izi\\prestashop\\Log\\Handler\\AbstractHandlerFactory' => __DIR__ . '/../..' . '/src/Log/Handler/AbstractHandlerFactory.php', + 'izi\\prestashop\\Log\\Handler\\HandlerFactoryInterface' => __DIR__ . '/../..' . '/src/Log/Handler/HandlerFactoryInterface.php', + 'izi\\prestashop\\Log\\Handler\\RotatingFileHandlerFactory' => __DIR__ . '/../..' . '/src/Log/Handler/RotatingFileHandlerFactory.php', + 'izi\\prestashop\\Log\\LoggerFactoryInterface' => __DIR__ . '/../..' . '/src/Log/LoggerFactoryInterface.php', + 'izi\\prestashop\\Log\\MonologLoggerFactory' => __DIR__ . '/../..' . '/src/Log/MonologLoggerFactory.php', + 'izi\\prestashop\\Mail\\Dto\\MailRecipient' => __DIR__ . '/../..' . '/src/Mail/Dto/MailRecipient.php', + 'izi\\prestashop\\Mail\\EventListener\\ReplaceOrderNotificationRecipientListener' => __DIR__ . '/../..' . '/src/Mail/EventListener/ReplaceOrderNotificationRecipientListener.php', + 'izi\\prestashop\\Mail\\Event\\SendEmailEvent' => __DIR__ . '/../..' . '/src/Mail/Event/SendEmailEvent.php', + 'izi\\prestashop\\Mail\\Resolver\\OrderMailRecipientResolver' => __DIR__ . '/../..' . '/src/Mail/Resolver/OrderMailRecipientResolver.php', + 'izi\\prestashop\\MerchantApi\\Command\\AddProductToBasketCommand' => __DIR__ . '/../..' . '/src/MerchantApi/Command/AddProductToBasketCommand.php', + 'izi\\prestashop\\MerchantApi\\Command\\Basket\\AddProductToCartCommand' => __DIR__ . '/../..' . '/src/MerchantApi/Command/Basket/AddProductToCartCommand.php', + 'izi\\prestashop\\MerchantApi\\Command\\Basket\\CreateCartCommand' => __DIR__ . '/../..' . '/src/MerchantApi/Command/Basket/CreateCartCommand.php', + 'izi\\prestashop\\MerchantApi\\Command\\Basket\\IncrementCartQuantityCommand' => __DIR__ . '/../..' . '/src/MerchantApi/Command/Basket/IncrementCartQuantityCommand.php', + 'izi\\prestashop\\MerchantApi\\Command\\ConfirmBasketBindingCommand' => __DIR__ . '/../..' . '/src/MerchantApi/Command/ConfirmBasketBindingCommand.php', + 'izi\\prestashop\\MerchantApi\\Command\\CreateOrderCommand' => __DIR__ . '/../..' . '/src/MerchantApi/Command/CreateOrderCommand.php', + 'izi\\prestashop\\MerchantApi\\Command\\DeleteBasketBindingCommand' => __DIR__ . '/../..' . '/src/MerchantApi/Command/DeleteBasketBindingCommand.php', + 'izi\\prestashop\\MerchantApi\\Command\\GetBasketCommand' => __DIR__ . '/../..' . '/src/MerchantApi/Command/GetBasketCommand.php', + 'izi\\prestashop\\MerchantApi\\Command\\GetOrderCommand' => __DIR__ . '/../..' . '/src/MerchantApi/Command/GetOrderCommand.php', + 'izi\\prestashop\\MerchantApi\\Command\\GetProductsCommand' => __DIR__ . '/../..' . '/src/MerchantApi/Command/GetProductsCommand.php', + 'izi\\prestashop\\MerchantApi\\Command\\Order\\UpdateCartMessageCommand' => __DIR__ . '/../..' . '/src/MerchantApi/Command/Order/UpdateCartMessageCommand.php', + 'izi\\prestashop\\MerchantApi\\Command\\UpdateBasketCommand' => __DIR__ . '/../..' . '/src/MerchantApi/Command/UpdateBasketCommand.php', + 'izi\\prestashop\\MerchantApi\\Command\\UpdateOrderCommand' => __DIR__ . '/../..' . '/src/MerchantApi/Command/UpdateOrderCommand.php', + 'izi\\prestashop\\MerchantApi\\EventListener\\UpdateCartRulesListener' => __DIR__ . '/../..' . '/src/MerchantApi/EventListener/UpdateCartRulesListener.php', + 'izi\\prestashop\\MerchantApi\\Event\\CartUpdatedEvent' => __DIR__ . '/../..' . '/src/MerchantApi/Event/CartUpdatedEvent.php', + 'izi\\prestashop\\MerchantApi\\Exception\\ApiException' => __DIR__ . '/../..' . '/src/MerchantApi/Exception/ApiException.php', + 'izi\\prestashop\\MerchantApi\\Exception\\BadGatewayException' => __DIR__ . '/../..' . '/src/MerchantApi/Exception/BadGatewayException.php', + 'izi\\prestashop\\MerchantApi\\Exception\\BadRequestException' => __DIR__ . '/../..' . '/src/MerchantApi/Exception/BadRequestException.php', + 'izi\\prestashop\\MerchantApi\\Exception\\BasketNotFoundException' => __DIR__ . '/../..' . '/src/MerchantApi/Exception/BasketNotFoundException.php', + 'izi\\prestashop\\MerchantApi\\Exception\\CannotAddProductException' => __DIR__ . '/../..' . '/src/MerchantApi/Exception/CannotAddProductException.php', + 'izi\\prestashop\\MerchantApi\\Exception\\CannotCreateBasketException' => __DIR__ . '/../..' . '/src/MerchantApi/Exception/CannotCreateBasketException.php', + 'izi\\prestashop\\MerchantApi\\Exception\\CannotCreateOrderException' => __DIR__ . '/../..' . '/src/MerchantApi/Exception/CannotCreateOrderException.php', + 'izi\\prestashop\\MerchantApi\\Exception\\InternalServerErrorException' => __DIR__ . '/../..' . '/src/MerchantApi/Exception/InternalServerErrorException.php', + 'izi\\prestashop\\MerchantApi\\Exception\\InvalidSignatureException' => __DIR__ . '/../..' . '/src/MerchantApi/Exception/InvalidSignatureException.php', + 'izi\\prestashop\\MerchantApi\\Exception\\MalformedRequestException' => __DIR__ . '/../..' . '/src/MerchantApi/Exception/MalformedRequestException.php', + 'izi\\prestashop\\MerchantApi\\Exception\\OrderExistsException' => __DIR__ . '/../..' . '/src/MerchantApi/Exception/OrderExistsException.php', + 'izi\\prestashop\\MerchantApi\\Exception\\OrderNotFoundException' => __DIR__ . '/../..' . '/src/MerchantApi/Exception/OrderNotFoundException.php', + 'izi\\prestashop\\MerchantApi\\Exception\\ProductNotFoundException' => __DIR__ . '/../..' . '/src/MerchantApi/Exception/ProductNotFoundException.php', + 'izi\\prestashop\\MerchantApi\\Exception\\ProductOutOfStockException' => __DIR__ . '/../..' . '/src/MerchantApi/Exception/ProductOutOfStockException.php', + 'izi\\prestashop\\MerchantApi\\Exception\\ServiceUnavailableException' => __DIR__ . '/../..' . '/src/MerchantApi/Exception/ServiceUnavailableException.php', + 'izi\\prestashop\\MerchantApi\\Firewall\\MerchantApiAuthenticator' => __DIR__ . '/../..' . '/src/MerchantApi/Firewall/MerchantApiAuthenticator.php', + 'izi\\prestashop\\MerchantApi\\Firewall\\SigningKeysService' => __DIR__ . '/../..' . '/src/MerchantApi/Firewall/SigningKeysService.php', + 'izi\\prestashop\\MerchantApi\\Firewall\\SigningKeysServiceInterface' => __DIR__ . '/../..' . '/src/MerchantApi/Firewall/SigningKeysServiceInterface.php', + 'izi\\prestashop\\MerchantApi\\Handler\\AddProductToBasketHandler' => __DIR__ . '/../..' . '/src/MerchantApi/Handler/AddProductToBasketHandler.php', + 'izi\\prestashop\\MerchantApi\\Handler\\AddProductToBasketHandlerInterface' => __DIR__ . '/../..' . '/src/MerchantApi/Handler/AddProductToBasketHandlerInterface.php', + 'izi\\prestashop\\MerchantApi\\Handler\\Basket\\AddProductToCartHandler' => __DIR__ . '/../..' . '/src/MerchantApi/Handler/Basket/AddProductToCartHandler.php', + 'izi\\prestashop\\MerchantApi\\Handler\\Basket\\AddProductToCartHandlerInterface' => __DIR__ . '/../..' . '/src/MerchantApi/Handler/Basket/AddProductToCartHandlerInterface.php', + 'izi\\prestashop\\MerchantApi\\Handler\\Basket\\BasketEventHandler' => __DIR__ . '/../..' . '/src/MerchantApi/Handler/Basket/BasketEventHandler.php', + 'izi\\prestashop\\MerchantApi\\Handler\\Basket\\BasketEventHandlerInterface' => __DIR__ . '/../..' . '/src/MerchantApi/Handler/Basket/BasketEventHandlerInterface.php', + 'izi\\prestashop\\MerchantApi\\Handler\\Basket\\CreateCartHandler' => __DIR__ . '/../..' . '/src/MerchantApi/Handler/Basket/CreateCartHandler.php', + 'izi\\prestashop\\MerchantApi\\Handler\\Basket\\CreateCartHandlerInterface' => __DIR__ . '/../..' . '/src/MerchantApi/Handler/Basket/CreateCartHandlerInterface.php', + 'izi\\prestashop\\MerchantApi\\Handler\\Basket\\IncrementCartQuantityHandler' => __DIR__ . '/../..' . '/src/MerchantApi/Handler/Basket/IncrementCartQuantityHandler.php', + 'izi\\prestashop\\MerchantApi\\Handler\\Basket\\IncrementCartQuantityHandlerInterface' => __DIR__ . '/../..' . '/src/MerchantApi/Handler/Basket/IncrementCartQuantityHandlerInterface.php', + 'izi\\prestashop\\MerchantApi\\Handler\\Basket\\ProductsQuantityEventHandler' => __DIR__ . '/../..' . '/src/MerchantApi/Handler/Basket/ProductsQuantityEventHandler.php', + 'izi\\prestashop\\MerchantApi\\Handler\\Basket\\PromoCodesEventHandler' => __DIR__ . '/../..' . '/src/MerchantApi/Handler/Basket/PromoCodesEventHandler.php', + 'izi\\prestashop\\MerchantApi\\Handler\\Basket\\RelatedProductsEventHandler' => __DIR__ . '/../..' . '/src/MerchantApi/Handler/Basket/RelatedProductsEventHandler.php', + 'izi\\prestashop\\MerchantApi\\Handler\\ConfirmBasketBindingHandler' => __DIR__ . '/../..' . '/src/MerchantApi/Handler/ConfirmBasketBindingHandler.php', + 'izi\\prestashop\\MerchantApi\\Handler\\ConfirmBasketBindingHandlerInterface' => __DIR__ . '/../..' . '/src/MerchantApi/Handler/ConfirmBasketBindingHandlerInterface.php', + 'izi\\prestashop\\MerchantApi\\Handler\\CreateOrderHandler' => __DIR__ . '/../..' . '/src/MerchantApi/Handler/CreateOrderHandler.php', + 'izi\\prestashop\\MerchantApi\\Handler\\CreateOrderHandlerInterface' => __DIR__ . '/../..' . '/src/MerchantApi/Handler/CreateOrderHandlerInterface.php', + 'izi\\prestashop\\MerchantApi\\Handler\\DeleteBasketBindingHandler' => __DIR__ . '/../..' . '/src/MerchantApi/Handler/DeleteBasketBindingHandler.php', + 'izi\\prestashop\\MerchantApi\\Handler\\DeleteBasketBindingHandlerInterface' => __DIR__ . '/../..' . '/src/MerchantApi/Handler/DeleteBasketBindingHandlerInterface.php', + 'izi\\prestashop\\MerchantApi\\Handler\\GetBasketHandler' => __DIR__ . '/../..' . '/src/MerchantApi/Handler/GetBasketHandler.php', + 'izi\\prestashop\\MerchantApi\\Handler\\GetBasketHandlerInterface' => __DIR__ . '/../..' . '/src/MerchantApi/Handler/GetBasketHandlerInterface.php', + 'izi\\prestashop\\MerchantApi\\Handler\\GetOrderHandler' => __DIR__ . '/../..' . '/src/MerchantApi/Handler/GetOrderHandler.php', + 'izi\\prestashop\\MerchantApi\\Handler\\GetOrderHandlerInterface' => __DIR__ . '/../..' . '/src/MerchantApi/Handler/GetOrderHandlerInterface.php', + 'izi\\prestashop\\MerchantApi\\Handler\\GetProductsHandler' => __DIR__ . '/../..' . '/src/MerchantApi/Handler/GetProductsHandler.php', + 'izi\\prestashop\\MerchantApi\\Handler\\GetProductsHandlerInterface' => __DIR__ . '/../..' . '/src/MerchantApi/Handler/GetProductsHandlerInterface.php', + 'izi\\prestashop\\MerchantApi\\Handler\\Order\\UpdateCartMessageHandler' => __DIR__ . '/../..' . '/src/MerchantApi/Handler/Order/UpdateCartMessageHandler.php', + 'izi\\prestashop\\MerchantApi\\Handler\\Order\\UpdateCartMessageHandlerInterface' => __DIR__ . '/../..' . '/src/MerchantApi/Handler/Order/UpdateCartMessageHandlerInterface.php', + 'izi\\prestashop\\MerchantApi\\Handler\\UpdateBasketHandler' => __DIR__ . '/../..' . '/src/MerchantApi/Handler/UpdateBasketHandler.php', + 'izi\\prestashop\\MerchantApi\\Handler\\UpdateBasketHandlerInterface' => __DIR__ . '/../..' . '/src/MerchantApi/Handler/UpdateBasketHandlerInterface.php', + 'izi\\prestashop\\MerchantApi\\Handler\\UpdateOrderHandler' => __DIR__ . '/../..' . '/src/MerchantApi/Handler/UpdateOrderHandler.php', + 'izi\\prestashop\\MerchantApi\\Handler\\UpdateOrderHandlerInterface' => __DIR__ . '/../..' . '/src/MerchantApi/Handler/UpdateOrderHandlerInterface.php', + 'izi\\prestashop\\MerchantApi\\Model\\Basket\\Request\\BasketEvent' => __DIR__ . '/../..' . '/src/MerchantApi/Model/Basket/Request/BasketEvent.php', + 'izi\\prestashop\\MerchantApi\\Model\\Basket\\Request\\BasketId' => __DIR__ . '/../..' . '/src/MerchantApi/Model/Basket/Request/BasketId.php', + 'izi\\prestashop\\MerchantApi\\Model\\Basket\\Request\\BindingConfirmation' => __DIR__ . '/../..' . '/src/MerchantApi/Model/Basket/Request/BindingConfirmation.php', + 'izi\\prestashop\\MerchantApi\\Model\\Basket\\Request\\BindingStatus' => __DIR__ . '/../..' . '/src/MerchantApi/Model/Basket/Request/BindingStatus.php', + 'izi\\prestashop\\MerchantApi\\Model\\Basket\\Request\\Browser' => __DIR__ . '/../..' . '/src/MerchantApi/Model/Basket/Request/Browser.php', + 'izi\\prestashop\\MerchantApi\\Model\\Basket\\Request\\EventType' => __DIR__ . '/../..' . '/src/MerchantApi/Model/Basket/Request/EventType.php', + 'izi\\prestashop\\MerchantApi\\Model\\Basket\\Request\\Quantity' => __DIR__ . '/../..' . '/src/MerchantApi/Model/Basket/Request/Quantity.php', + 'izi\\prestashop\\MerchantApi\\Model\\Basket\\Request\\QuantityData' => __DIR__ . '/../..' . '/src/MerchantApi/Model/Basket/Request/QuantityData.php', + 'izi\\prestashop\\MerchantApi\\Model\\Basket\\Request\\RelatedProductData' => __DIR__ . '/../..' . '/src/MerchantApi/Model/Basket/Request/RelatedProductData.php', + 'izi\\prestashop\\MerchantApi\\Model\\Basket\\Response\\Basket' => __DIR__ . '/../..' . '/src/MerchantApi/Model/Basket/Response/Basket.php', + 'izi\\prestashop\\MerchantApi\\Model\\Basket\\Response\\BasketTrait' => __DIR__ . '/../..' . '/src/MerchantApi/Model/Basket/Response/BasketTrait.php', + 'izi\\prestashop\\MerchantApi\\Model\\Basket\\Response\\IdentifiableBasket' => __DIR__ . '/../..' . '/src/MerchantApi/Model/Basket/Response/IdentifiableBasket.php', + 'izi\\prestashop\\MerchantApi\\Model\\Order\\Request\\AccountInfo' => __DIR__ . '/../..' . '/src/MerchantApi/Model/Order/Request/AccountInfo.php', + 'izi\\prestashop\\MerchantApi\\Model\\Order\\Request\\AddressDetails' => __DIR__ . '/../..' . '/src/MerchantApi/Model/Order/Request/AddressDetails.php', + 'izi\\prestashop\\MerchantApi\\Model\\Order\\Request\\ClientAddress' => __DIR__ . '/../..' . '/src/MerchantApi/Model/Order/Request/ClientAddress.php', + 'izi\\prestashop\\MerchantApi\\Model\\Order\\Request\\CreateOrderRequest' => __DIR__ . '/../..' . '/src/MerchantApi/Model/Order/Request/CreateOrderRequest.php', + 'izi\\prestashop\\MerchantApi\\Model\\Order\\Request\\Delivery' => __DIR__ . '/../..' . '/src/MerchantApi/Model/Order/Request/Delivery.php', + 'izi\\prestashop\\MerchantApi\\Model\\Order\\Request\\DeliveryAddress' => __DIR__ . '/../..' . '/src/MerchantApi/Model/Order/Request/DeliveryAddress.php', + 'izi\\prestashop\\MerchantApi\\Model\\Order\\Request\\EventData' => __DIR__ . '/../..' . '/src/MerchantApi/Model/Order/Request/EventData.php', + 'izi\\prestashop\\MerchantApi\\Model\\Order\\Request\\OrderDetails' => __DIR__ . '/../..' . '/src/MerchantApi/Model/Order/Request/OrderDetails.php', + 'izi\\prestashop\\MerchantApi\\Model\\Order\\Request\\OrderEvent' => __DIR__ . '/../..' . '/src/MerchantApi/Model/Order/Request/OrderEvent.php', + 'izi\\prestashop\\MerchantApi\\Model\\Order\\Request\\OrderStatus' => __DIR__ . '/../..' . '/src/MerchantApi/Model/Order/Request/OrderStatus.php', + 'izi\\prestashop\\MerchantApi\\Model\\Order\\Request\\PaymentStatus' => __DIR__ . '/../..' . '/src/MerchantApi/Model/Order/Request/PaymentStatus.php', + 'izi\\prestashop\\MerchantApi\\Model\\Order\\Response\\Delivery' => __DIR__ . '/../..' . '/src/MerchantApi/Model/Order/Response/Delivery.php', + 'izi\\prestashop\\MerchantApi\\Model\\Order\\Response\\Order' => __DIR__ . '/../..' . '/src/MerchantApi/Model/Order/Response/Order.php', + 'izi\\prestashop\\MerchantApi\\Model\\Order\\Response\\OrderDetails' => __DIR__ . '/../..' . '/src/MerchantApi/Model/Order/Response/OrderDetails.php', + 'izi\\prestashop\\MerchantApi\\Model\\Order\\Response\\OrderStatusData' => __DIR__ . '/../..' . '/src/MerchantApi/Model/Order/Response/OrderStatusData.php', + 'izi\\prestashop\\MerchantApi\\Model\\Product\\Response\\Products' => __DIR__ . '/../..' . '/src/MerchantApi/Model/Product/Response/Products.php', + 'izi\\prestashop\\Module\\Exception\\ModuleErrorInterface' => __DIR__ . '/../..' . '/src/Module/Exception/ModuleErrorInterface.php', + 'izi\\prestashop\\Module\\Exception\\PrestaShopModuleErrorException' => __DIR__ . '/../..' . '/src/Module/Exception/PrestaShopModuleErrorException.php', + 'izi\\prestashop\\Module\\ModuleRepository' => __DIR__ . '/../..' . '/src/Module/ModuleRepository.php', + 'izi\\prestashop\\OAuth2\\Authentication\\AuthenticationMethodInterface' => __DIR__ . '/../..' . '/src/OAuth2/Authentication/AuthenticationMethodInterface.php', + 'izi\\prestashop\\OAuth2\\Authentication\\ClientCredentials' => __DIR__ . '/../..' . '/src/OAuth2/Authentication/ClientCredentials.php', + 'izi\\prestashop\\OAuth2\\Authentication\\ClientCredentialsInterface' => __DIR__ . '/../..' . '/src/OAuth2/Authentication/ClientCredentialsInterface.php', + 'izi\\prestashop\\OAuth2\\Authentication\\ClientCredentialsRepositoryInterface' => __DIR__ . '/../..' . '/src/OAuth2/Authentication/ClientCredentialsRepositoryInterface.php', + 'izi\\prestashop\\OAuth2\\Authentication\\ClientSecretPost' => __DIR__ . '/../..' . '/src/OAuth2/Authentication/ClientSecretPost.php', + 'izi\\prestashop\\OAuth2\\Authentication\\None' => __DIR__ . '/../..' . '/src/OAuth2/Authentication/None.php', + 'izi\\prestashop\\OAuth2\\AuthorizationProvider' => __DIR__ . '/../..' . '/src/OAuth2/AuthorizationProvider.php', + 'izi\\prestashop\\OAuth2\\AuthorizationProviderFactoryInterface' => __DIR__ . '/../..' . '/src/OAuth2/AuthorizationProviderFactoryInterface.php', + 'izi\\prestashop\\OAuth2\\AuthorizationProviderInterface' => __DIR__ . '/../..' . '/src/OAuth2/AuthorizationProviderInterface.php', + 'izi\\prestashop\\OAuth2\\AuthorizationServerClient' => __DIR__ . '/../..' . '/src/OAuth2/AuthorizationServerClient.php', + 'izi\\prestashop\\OAuth2\\AuthorizationServerClientInterface' => __DIR__ . '/../..' . '/src/OAuth2/AuthorizationServerClientInterface.php', + 'izi\\prestashop\\OAuth2\\Exception\\AccessTokenRequestException' => __DIR__ . '/../..' . '/src/OAuth2/Exception/AccessTokenRequestException.php', + 'izi\\prestashop\\OAuth2\\Exception\\NetworkException' => __DIR__ . '/../..' . '/src/OAuth2/Exception/NetworkException.php', + 'izi\\prestashop\\OAuth2\\Exception\\OAuth2ExceptionInterface' => __DIR__ . '/../..' . '/src/OAuth2/Exception/OAuth2ExceptionInterface.php', + 'izi\\prestashop\\OAuth2\\Exception\\UnexpectedValueException' => __DIR__ . '/../..' . '/src/OAuth2/Exception/UnexpectedValueException.php', + 'izi\\prestashop\\OAuth2\\Grant\\AbstractGrant' => __DIR__ . '/../..' . '/src/OAuth2/Grant/AbstractGrant.php', + 'izi\\prestashop\\OAuth2\\Grant\\ClientCredentialsGrant' => __DIR__ . '/../..' . '/src/OAuth2/Grant/ClientCredentialsGrant.php', + 'izi\\prestashop\\OAuth2\\Grant\\GrantTypeInterface' => __DIR__ . '/../..' . '/src/OAuth2/Grant/GrantTypeInterface.php', + 'izi\\prestashop\\OAuth2\\Grant\\RefreshTokenGrant' => __DIR__ . '/../..' . '/src/OAuth2/Grant/RefreshTokenGrant.php', + 'izi\\prestashop\\OAuth2\\LazyAuthorizationProvider' => __DIR__ . '/../..' . '/src/OAuth2/LazyAuthorizationProvider.php', + 'izi\\prestashop\\OAuth2\\Token\\AccessTokenFactoryInterface' => __DIR__ . '/../..' . '/src/OAuth2/Token/AccessTokenFactoryInterface.php', + 'izi\\prestashop\\OAuth2\\Token\\AccessTokenFactoryTrait' => __DIR__ . '/../..' . '/src/OAuth2/Token/AccessTokenFactoryTrait.php', + 'izi\\prestashop\\OAuth2\\Token\\AccessTokenInterface' => __DIR__ . '/../..' . '/src/OAuth2/Token/AccessTokenInterface.php', + 'izi\\prestashop\\OAuth2\\Token\\AccessTokenRepositoryInterface' => __DIR__ . '/../..' . '/src/OAuth2/Token/AccessTokenRepositoryInterface.php', + 'izi\\prestashop\\OAuth2\\Token\\AccessTokenTrait' => __DIR__ . '/../..' . '/src/OAuth2/Token/AccessTokenTrait.php', + 'izi\\prestashop\\OAuth2\\Token\\BearerToken' => __DIR__ . '/../..' . '/src/OAuth2/Token/BearerToken.php', + 'izi\\prestashop\\OAuth2\\Token\\BearerTokenFactory' => __DIR__ . '/../..' . '/src/OAuth2/Token/BearerTokenFactory.php', + 'izi\\prestashop\\OAuth2\\Token\\InMemoryTokenRepository' => __DIR__ . '/../..' . '/src/OAuth2/Token/InMemoryTokenRepository.php', + 'izi\\prestashop\\OAuth2\\UriCollection' => __DIR__ . '/../..' . '/src/OAuth2/UriCollection.php', + 'izi\\prestashop\\OAuth2\\UriCollectionInterface' => __DIR__ . '/../..' . '/src/OAuth2/UriCollectionInterface.php', + 'izi\\prestashop\\ObjectModel\\Entity\\InPostIziBasketSession' => __DIR__ . '/../..' . '/src/ObjectModel/Entity/InPostIziBasketSession.php', + 'izi\\prestashop\\ObjectModel\\Exception\\InvalidDataException' => __DIR__ . '/../..' . '/src/ObjectModel/Exception/InvalidDataException.php', + 'izi\\prestashop\\ObjectModel\\Hydrator' => __DIR__ . '/../..' . '/src/ObjectModel/Hydrator.php', + 'izi\\prestashop\\ObjectModel\\HydratorInterface' => __DIR__ . '/../..' . '/src/ObjectModel/HydratorInterface.php', + 'izi\\prestashop\\ObjectModel\\ObjectManager' => __DIR__ . '/../..' . '/src/ObjectModel/ObjectManager.php', + 'izi\\prestashop\\ObjectModel\\ObjectManagerInterface' => __DIR__ . '/../..' . '/src/ObjectModel/ObjectManagerInterface.php', + 'izi\\prestashop\\ObjectModel\\OrderMaintainingLoaderTrait' => __DIR__ . '/../..' . '/src/ObjectModel/OrderMaintainingLoaderTrait.php', + 'izi\\prestashop\\ObjectModel\\Query' => __DIR__ . '/../..' . '/src/ObjectModel/Query.php', + 'izi\\prestashop\\ObjectModel\\QueryBuilder' => __DIR__ . '/../..' . '/src/ObjectModel/QueryBuilder.php', + 'izi\\prestashop\\ObjectModel\\Repository\\CarrierRepository' => __DIR__ . '/../..' . '/src/ObjectModel/Repository/CarrierRepository.php', + 'izi\\prestashop\\ObjectModel\\Repository\\CartRuleRepository' => __DIR__ . '/../..' . '/src/ObjectModel/Repository/CartRuleRepository.php', + 'izi\\prestashop\\ObjectModel\\Repository\\CmsPageRepository' => __DIR__ . '/../..' . '/src/ObjectModel/Repository/CmsPageRepository.php', + 'izi\\prestashop\\ObjectModel\\Repository\\CombinationRepository' => __DIR__ . '/../..' . '/src/ObjectModel/Repository/CombinationRepository.php', + 'izi\\prestashop\\ObjectModel\\Repository\\ConfigurationRepository' => __DIR__ . '/../..' . '/src/ObjectModel/Repository/ConfigurationRepository.php', + 'izi\\prestashop\\ObjectModel\\Repository\\CurrencyRepository' => __DIR__ . '/../..' . '/src/ObjectModel/Repository/CurrencyRepository.php', + 'izi\\prestashop\\ObjectModel\\Repository\\HookRepository' => __DIR__ . '/../..' . '/src/ObjectModel/Repository/HookRepository.php', + 'izi\\prestashop\\ObjectModel\\Repository\\ImageTypeRepository' => __DIR__ . '/../..' . '/src/ObjectModel/Repository/ImageTypeRepository.php', + 'izi\\prestashop\\ObjectModel\\Repository\\ObjectRepository' => __DIR__ . '/../..' . '/src/ObjectModel/Repository/ObjectRepository.php', + 'izi\\prestashop\\ObjectModel\\Repository\\ObjectRepositoryFactory' => __DIR__ . '/../..' . '/src/ObjectModel/Repository/ObjectRepositoryFactory.php', + 'izi\\prestashop\\ObjectModel\\Repository\\ObjectRepositoryFactoryInterface' => __DIR__ . '/../..' . '/src/ObjectModel/Repository/ObjectRepositoryFactoryInterface.php', + 'izi\\prestashop\\ObjectModel\\Repository\\ObjectRepositoryInterface' => __DIR__ . '/../..' . '/src/ObjectModel/Repository/ObjectRepositoryInterface.php', + 'izi\\prestashop\\ObjectModel\\Repository\\ProductRepository' => __DIR__ . '/../..' . '/src/ObjectModel/Repository/ProductRepository.php', + 'izi\\prestashop\\ObjectModel\\Repository\\RangePriceRepository' => __DIR__ . '/../..' . '/src/ObjectModel/Repository/RangePriceRepository.php', + 'izi\\prestashop\\ObjectModel\\Repository\\RangeWeightRepository' => __DIR__ . '/../..' . '/src/ObjectModel/Repository/RangeWeightRepository.php', + 'izi\\prestashop\\ObjectModel\\Repository\\ShipmentRepository' => __DIR__ . '/../..' . '/src/ObjectModel/Repository/ShipmentRepository.php', + 'izi\\prestashop\\Order\\Address\\AddressDataMapper' => __DIR__ . '/../..' . '/src/Order/Address/AddressDataMapper.php', + 'izi\\prestashop\\Order\\ContextCustomerUpdater' => __DIR__ . '/../..' . '/src/Order/ContextCustomerUpdater.php', + 'izi\\prestashop\\Order\\Message\\ExpressionLanguage' => __DIR__ . '/../..' . '/src/Order/Message/ExpressionLanguage.php', + 'izi\\prestashop\\Order\\Message\\Message' => __DIR__ . '/../..' . '/src/Order/Message/Message.php', + 'izi\\prestashop\\Order\\Message\\MessageFormatter' => __DIR__ . '/../..' . '/src/Order/Message/MessageFormatter.php', + 'izi\\prestashop\\Order\\Message\\MessageFormatterInterface' => __DIR__ . '/../..' . '/src/Order/Message/MessageFormatterInterface.php', + 'izi\\prestashop\\Order\\Message\\ParameterDescriptorInterface' => __DIR__ . '/../..' . '/src/Order/Message/ParameterDescriptorInterface.php', + 'izi\\prestashop\\Order\\Message\\ParametersExtractor' => __DIR__ . '/../..' . '/src/Order/Message/ParametersExtractor.php', + 'izi\\prestashop\\Order\\Message\\ParametersExtractorInterface' => __DIR__ . '/../..' . '/src/Order/Message/ParametersExtractorInterface.php', + 'izi\\prestashop\\Order\\Message\\Processor\\ConditionalBlockProcessor' => __DIR__ . '/../..' . '/src/Order/Message/Processor/ConditionalBlockProcessor.php', + 'izi\\prestashop\\Order\\Message\\Processor\\ExpressionLanguageProcessor' => __DIR__ . '/../..' . '/src/Order/Message/Processor/ExpressionLanguageProcessor.php', + 'izi\\prestashop\\Order\\Message\\Processor\\ParameterReplacementProcessor' => __DIR__ . '/../..' . '/src/Order/Message/Processor/ParameterReplacementProcessor.php', + 'izi\\prestashop\\Order\\Message\\Processor\\ProcessorInterface' => __DIR__ . '/../..' . '/src/Order/Message/Processor/ProcessorInterface.php', + 'izi\\prestashop\\Payment\\PaymentCurrencyChecker' => __DIR__ . '/../..' . '/src/Payment/PaymentCurrencyChecker.php', + 'izi\\prestashop\\PrestashopOrder' => __DIR__ . '/../..' . '/src/PrestashopOrder.php', + 'izi\\prestashop\\Product\\Event\\CombinationEvent' => __DIR__ . '/../..' . '/src/Product/Event/CombinationEvent.php', + 'izi\\prestashop\\Product\\Event\\ImageEvent' => __DIR__ . '/../..' . '/src/Product/Event/ImageEvent.php', + 'izi\\prestashop\\Product\\Event\\ProductEvent' => __DIR__ . '/../..' . '/src/Product/Event/ProductEvent.php', + 'izi\\prestashop\\Product\\Event\\SpecificPriceEvent' => __DIR__ . '/../..' . '/src/Product/Event/SpecificPriceEvent.php', + 'izi\\prestashop\\Product\\Event\\StockQuantityUpdatedEvent' => __DIR__ . '/../..' . '/src/Product/Event/StockQuantityUpdatedEvent.php', + 'izi\\prestashop\\Product\\Image\\ImageUrls' => __DIR__ . '/../..' . '/src/Product/Image/ImageUrls.php', + 'izi\\prestashop\\Product\\Image\\ImageUrlsProvider' => __DIR__ . '/../..' . '/src/Product/Image/ImageUrlsProvider.php', + 'izi\\prestashop\\Product\\Image\\ImageUrlsProviderInterface' => __DIR__ . '/../..' . '/src/Product/Image/ImageUrlsProviderInterface.php', + 'izi\\prestashop\\Product\\Price\\BatchLowestPriceProviderInterface' => __DIR__ . '/../..' . '/src/Product/Price/BatchLowestPriceProviderInterface.php', + 'izi\\prestashop\\Product\\Price\\CalculationParameters' => __DIR__ . '/../..' . '/src/Product/Price/CalculationParameters.php', + 'izi\\prestashop\\Product\\Price\\ErrorHandlingLowestPriceProvider' => __DIR__ . '/../..' . '/src/Product/Price/ErrorHandlingLowestPriceProvider.php', + 'izi\\prestashop\\Product\\Price\\LowestPriceProviderFactory' => __DIR__ . '/../..' . '/src/Product/Price/LowestPriceProviderFactory.php', + 'izi\\prestashop\\Product\\Price\\LowestPriceProviderInterface' => __DIR__ . '/../..' . '/src/Product/Price/LowestPriceProviderInterface.php', + 'izi\\prestashop\\Product\\Price\\LowestPriceQuery' => __DIR__ . '/../..' . '/src/Product/Price/LowestPriceQuery.php', + 'izi\\prestashop\\Product\\Price\\NullLowestPriceProvider' => __DIR__ . '/../..' . '/src/Product/Price/NullLowestPriceProvider.php', + 'izi\\prestashop\\Product\\Price\\PriceCalculator' => __DIR__ . '/../..' . '/src/Product/Price/PriceCalculator.php', + 'izi\\prestashop\\Product\\Price\\PriceCalculatorInterface' => __DIR__ . '/../..' . '/src/Product/Price/PriceCalculatorInterface.php', + 'izi\\prestashop\\Product\\Price\\PriceQuery' => __DIR__ . '/../..' . '/src/Product/Price/PriceQuery.php', + 'izi\\prestashop\\Product\\Price\\X13PriceHistoryLowestPriceProvider' => __DIR__ . '/../..' . '/src/Product/Price/X13PriceHistoryLowestPriceProvider.php', + 'izi\\prestashop\\Product\\ProductAttribute' => __DIR__ . '/../..' . '/src/Product/ProductAttribute.php', + 'izi\\prestashop\\Product\\ProductType' => __DIR__ . '/../..' . '/src/Product/ProductType.php', + 'izi\\prestashop\\Product\\ProductWithCombination' => __DIR__ . '/../..' . '/src/Product/ProductWithCombination.php', + 'izi\\prestashop\\Product\\ReferenceId' => __DIR__ . '/../..' . '/src/Product/ReferenceId.php', + 'izi\\prestashop\\Product\\Util\\AttributeListParser' => __DIR__ . '/../..' . '/src/Product/Util/AttributeListParser.php', + 'izi\\prestashop\\Product\\Util\\DescriptionFormatter' => __DIR__ . '/../..' . '/src/Product/Util/DescriptionFormatter.php', + 'izi\\prestashop\\PromoCode\\AvailableCartRulesProvider' => __DIR__ . '/../..' . '/src/PromoCode/AvailableCartRulesProvider.php', + 'izi\\prestashop\\PromoCode\\AvailablePromotionsProviderInterface' => __DIR__ . '/../..' . '/src/PromoCode/AvailablePromotionsProviderInterface.php', + 'izi\\prestashop\\PromoCode\\CartRuleOptions' => __DIR__ . '/../..' . '/src/PromoCode/CartRuleOptions.php', + 'izi\\prestashop\\PromoCode\\CartRuleOptionsRepository' => __DIR__ . '/../..' . '/src/PromoCode/CartRuleOptionsRepository.php', + 'izi\\prestashop\\PromoCode\\CartRuleOptionsRepositoryInterface' => __DIR__ . '/../..' . '/src/PromoCode/CartRuleOptionsRepositoryInterface.php', + 'izi\\prestashop\\PromoCode\\CartRulePromoCodeProvider' => __DIR__ . '/../..' . '/src/PromoCode/CartRulePromoCodeProvider.php', + 'izi\\prestashop\\PromoCode\\NullAvailablePromotionsProvider' => __DIR__ . '/../..' . '/src/PromoCode/NullAvailablePromotionsProvider.php', + 'izi\\prestashop\\PromoCode\\PromoCodeProviderInterface' => __DIR__ . '/../..' . '/src/PromoCode/PromoCodeProviderInterface.php', + 'izi\\prestashop\\Repository\\BasketSessionRepository' => __DIR__ . '/../..' . '/src/Repository/BasketSessionRepository.php', + 'izi\\prestashop\\Repository\\BasketSessionRepositoryInterface' => __DIR__ . '/../..' . '/src/Repository/BasketSessionRepositoryInterface.php', + 'izi\\prestashop\\Repository\\CartRuleRepository' => __DIR__ . '/../..' . '/src/Repository/CartRuleRepository.php', + 'izi\\prestashop\\Repository\\CartRuleRepositoryInterface' => __DIR__ . '/../..' . '/src/Repository/CartRuleRepositoryInterface.php', + 'izi\\prestashop\\Repository\\OrderDataRepositoryInterface' => __DIR__ . '/../..' . '/src/Repository/OrderDataRepositoryInterface.php', + 'izi\\prestashop\\Repository\\ProductRestrictionsRepository' => __DIR__ . '/../..' . '/src/Repository/ProductRestrictionsRepository.php', + 'izi\\prestashop\\Repository\\ProductRestrictionsRepositoryInterface' => __DIR__ . '/../..' . '/src/Repository/ProductRestrictionsRepositoryInterface.php', + 'izi\\prestashop\\Repository\\Product\\AttributeRestrictionsRepositoryInterface' => __DIR__ . '/../..' . '/src/Repository/Product/AttributeRestrictionsRepositoryInterface.php', + 'izi\\prestashop\\Repository\\Product\\CategoryRestrictionsRepositoryInterface' => __DIR__ . '/../..' . '/src/Repository/Product/CategoryRestrictionsRepositoryInterface.php', + 'izi\\prestashop\\Repository\\Product\\FeatureRestrictionsRepositoryInterface' => __DIR__ . '/../..' . '/src/Repository/Product/FeatureRestrictionsRepositoryInterface.php', + 'izi\\prestashop\\Repository\\Product\\ManufacturerRestrictionsRepositoryInterface' => __DIR__ . '/../..' . '/src/Repository/Product/ManufacturerRestrictionsRepositoryInterface.php', + 'izi\\prestashop\\Routing\\AdminUrlGenerator' => __DIR__ . '/../..' . '/src/Routing/AdminUrlGenerator.php', + 'izi\\prestashop\\Routing\\AnnotationDirectoryLoader' => __DIR__ . '/../..' . '/src/Routing/AnnotationDirectoryLoader.php', + 'izi\\prestashop\\Security\\AuthorizationChecker' => __DIR__ . '/../..' . '/src/Security/AuthorizationChecker.php', + 'izi\\prestashop\\Security\\EmployeeAuthenticator' => __DIR__ . '/../..' . '/src/Security/EmployeeAuthenticator.php', + 'izi\\prestashop\\Security\\LazyUserProvider' => __DIR__ . '/../..' . '/src/Security/LazyUserProvider.php', + 'izi\\prestashop\\Security\\Voter\\BindingWidgetVoter' => __DIR__ . '/../..' . '/src/Security/Voter/BindingWidgetVoter.php', + 'izi\\prestashop\\Serializer\\Exception\\MissingConstructorArgumentsException' => __DIR__ . '/../..' . '/src/Serializer/Exception/MissingConstructorArgumentsException.php', + 'izi\\prestashop\\Serializer\\Normalizer\\BasketAppPaginationPageDenormalizer' => __DIR__ . '/../..' . '/src/Serializer/Normalizer/BasketAppPaginationPageDenormalizer.php', + 'izi\\prestashop\\Serializer\\Normalizer\\CustomDenormalizer' => __DIR__ . '/../..' . '/src/Serializer/Normalizer/CustomDenormalizer.php', + 'izi\\prestashop\\Serializer\\Normalizer\\DateTimeNormalizer' => __DIR__ . '/../..' . '/src/Serializer/Normalizer/DateTimeNormalizer.php', + 'izi\\prestashop\\Serializer\\Normalizer\\DenormalizableInterface' => __DIR__ . '/../..' . '/src/Serializer/Normalizer/DenormalizableInterface.php', + 'izi\\prestashop\\Serializer\\Normalizer\\EnumDenormalizer' => __DIR__ . '/../..' . '/src/Serializer/Normalizer/EnumDenormalizer.php', + 'izi\\prestashop\\Serializer\\Normalizer\\JsonSerializableNormalizer' => __DIR__ . '/../..' . '/src/Serializer/Normalizer/JsonSerializableNormalizer.php', + 'izi\\prestashop\\Serializer\\Normalizer\\ObjectNormalizer' => __DIR__ . '/../..' . '/src/Serializer/Normalizer/ObjectNormalizer.php', + 'izi\\prestashop\\Serializer\\Normalizer\\PriceAmountNormalizer' => __DIR__ . '/../..' . '/src/Serializer/Normalizer/PriceAmountNormalizer.php', + 'izi\\prestashop\\Serializer\\Normalizer\\PriceNormalizer' => __DIR__ . '/../..' . '/src/Serializer/Normalizer/PriceNormalizer.php', + 'izi\\prestashop\\Serializer\\PropertyDocBlockTypeExtractor' => __DIR__ . '/../..' . '/src/Serializer/PropertyDocBlockTypeExtractor.php', + 'izi\\prestashop\\Serializer\\SafeDeserializerTrait' => __DIR__ . '/../..' . '/src/Serializer/SafeDeserializerTrait.php', + 'izi\\prestashop\\Serializer\\SerializerFactory' => __DIR__ . '/../..' . '/src/Serializer/SerializerFactory.php', + 'izi\\prestashop\\Shipping\\CarrierModuleTrackingNumberProvider' => __DIR__ . '/../..' . '/src/Shipping/CarrierModuleTrackingNumberProvider.php', + 'izi\\prestashop\\Shipping\\CartTotal\\CartTotalDeliveryStrategyInterface' => __DIR__ . '/../..' . '/src/Shipping/CartTotal/CartTotalDeliveryStrategyInterface.php', + 'izi\\prestashop\\Shipping\\CartTotal\\GenericStrategy' => __DIR__ . '/../..' . '/src/Shipping/CartTotal/GenericStrategy.php', + 'izi\\prestashop\\Shipping\\CartTotal\\PriceRangeStrategy' => __DIR__ . '/../..' . '/src/Shipping/CartTotal/PriceRangeStrategy.php', + 'izi\\prestashop\\Shipping\\CartWeight\\CartWeightDeliveryStrategyInterface' => __DIR__ . '/../..' . '/src/Shipping/CartWeight/CartWeightDeliveryStrategyInterface.php', + 'izi\\prestashop\\Shipping\\CartWeight\\GenericStrategy' => __DIR__ . '/../..' . '/src/Shipping/CartWeight/GenericStrategy.php', + 'izi\\prestashop\\Shipping\\CartWeight\\WeightRangeStrategy' => __DIR__ . '/../..' . '/src/Shipping/CartWeight/WeightRangeStrategy.php', + 'izi\\prestashop\\Shipping\\DeliveryPriceCalculator' => __DIR__ . '/../..' . '/src/Shipping/DeliveryPriceCalculator.php', + 'izi\\prestashop\\Shipping\\DeliveryPriceCalculatorInterface' => __DIR__ . '/../..' . '/src/Shipping/DeliveryPriceCalculatorInterface.php', + 'izi\\prestashop\\Shipping\\Exception\\UnavailableDeliveryOptionException' => __DIR__ . '/../..' . '/src/Shipping/Exception/UnavailableDeliveryOptionException.php', + 'izi\\prestashop\\Shipping\\FreeDelivery\\GenericStrategy' => __DIR__ . '/../..' . '/src/Shipping/FreeDelivery/GenericStrategy.php', + 'izi\\prestashop\\Shipping\\FreeDelivery\\MinAmountCalculationStrategyInterface' => __DIR__ . '/../..' . '/src/Shipping/FreeDelivery/MinAmountCalculationStrategyInterface.php', + 'izi\\prestashop\\Shipping\\FreeDelivery\\NullStrategy' => __DIR__ . '/../..' . '/src/Shipping/FreeDelivery/NullStrategy.php', + 'izi\\prestashop\\Shipping\\FreeDelivery\\PriceRangeStrategy' => __DIR__ . '/../..' . '/src/Shipping/FreeDelivery/PriceRangeStrategy.php', + 'izi\\prestashop\\Shipping\\ProductDimensions\\GenericStrategy' => __DIR__ . '/../..' . '/src/Shipping/ProductDimensions/GenericStrategy.php', + 'izi\\prestashop\\Shipping\\ProductDimensions\\ProductDimensionsDeliveryStrategyInterface' => __DIR__ . '/../..' . '/src/Shipping/ProductDimensions/ProductDimensionsDeliveryStrategyInterface.php', + 'izi\\prestashop\\Shipping\\ProductRestriction\\ProductRestrictionDelivery' => __DIR__ . '/../..' . '/src/Shipping/ProductRestriction/ProductRestrictionDelivery.php', + 'izi\\prestashop\\Shipping\\ProductRestriction\\ProductRestrictionDeliveryInterface' => __DIR__ . '/../..' . '/src/Shipping/ProductRestriction/ProductRestrictionDeliveryInterface.php', + 'izi\\prestashop\\Shipping\\TrackingNumberProviderInterface' => __DIR__ . '/../..' . '/src/Shipping/TrackingNumberProviderInterface.php', + 'izi\\prestashop\\Translation\\DomainNormalizingTranslator' => __DIR__ . '/../..' . '/src/Translation/DomainNormalizingTranslator.php', + 'izi\\prestashop\\Translation\\LegacyTranslator' => __DIR__ . '/../..' . '/src/Translation/LegacyTranslator.php', + 'izi\\prestashop\\Translation\\PaymentTypeTranslator' => __DIR__ . '/../..' . '/src/Translation/PaymentTypeTranslator.php', + 'izi\\prestashop\\Translation\\ServiceNameTranslator' => __DIR__ . '/../..' . '/src/Translation/ServiceNameTranslator.php', + 'izi\\prestashop\\Twig\\Extension\\LegacyTranslationExtension' => __DIR__ . '/../..' . '/src/Twig/Extension/LegacyTranslationExtension.php', + 'izi\\prestashop\\Twig\\Loader\\TemplateNameMappingLoader' => __DIR__ . '/../..' . '/src/Twig/Loader/TemplateNameMappingLoader.php', + 'izi\\prestashop\\Uuid\\Uuid' => __DIR__ . '/../..' . '/src/Uuid/Uuid.php', + 'izi\\prestashop\\Uuid\\UuidV4' => __DIR__ . '/../..' . '/src/Uuid/UuidV4.php', + 'izi\\prestashop\\Validator\\Cart\\Bindable' => __DIR__ . '/../..' . '/src/Validator/Cart/Bindable.php', + 'izi\\prestashop\\Validator\\Cart\\BindableValidator' => __DIR__ . '/../..' . '/src/Validator/Cart/BindableValidator.php', + 'izi\\prestashop\\Validator\\Cart\\HasProducts' => __DIR__ . '/../..' . '/src/Validator/Cart/HasProducts.php', + 'izi\\prestashop\\Validator\\Cart\\HasProductsValidator' => __DIR__ . '/../..' . '/src/Validator/Cart/HasProductsValidator.php', + 'izi\\prestashop\\Validator\\Cart\\HasUnrestrictedProduct' => __DIR__ . '/../..' . '/src/Validator/Cart/HasUnrestrictedProduct.php', + 'izi\\prestashop\\Validator\\Cart\\HasUnrestrictedProductValidator' => __DIR__ . '/../..' . '/src/Validator/Cart/HasUnrestrictedProductValidator.php', + 'izi\\prestashop\\Validator\\Cart\\PaymentInCurrencyAvailable' => __DIR__ . '/../..' . '/src/Validator/Cart/PaymentInCurrencyAvailable.php', + 'izi\\prestashop\\Validator\\Cart\\PaymentInCurrencyAvailableValidator' => __DIR__ . '/../..' . '/src/Validator/Cart/PaymentInCurrencyAvailableValidator.php', + 'izi\\prestashop\\Validator\\Consent\\DescriptionUsesIdPlaceholders' => __DIR__ . '/../..' . '/src/Validator/Consent/DescriptionUsesIdPlaceholders.php', + 'izi\\prestashop\\Validator\\Consent\\DescriptionUsesIdPlaceholdersValidator' => __DIR__ . '/../..' . '/src/Validator/Consent/DescriptionUsesIdPlaceholdersValidator.php', + 'izi\\prestashop\\Validator\\Consent\\UniqueIdentifiers' => __DIR__ . '/../..' . '/src/Validator/Consent/UniqueIdentifiers.php', + 'izi\\prestashop\\Validator\\Consent\\UniqueIdentifiersValidator' => __DIR__ . '/../..' . '/src/Validator/Consent/UniqueIdentifiersValidator.php', + 'izi\\prestashop\\Validator\\ConstraintValidatorFactory' => __DIR__ . '/../..' . '/src/Validator/ConstraintValidatorFactory.php', + 'izi\\prestashop\\Validator\\InPostApiCredentials' => __DIR__ . '/../..' . '/src/Validator/InPostApiCredentials.php', + 'izi\\prestashop\\Validator\\InPostApiCredentialsValidator' => __DIR__ . '/../..' . '/src/Validator/InPostApiCredentialsValidator.php', + 'izi\\prestashop\\Validator\\NotBlankInDefaultLanguage' => __DIR__ . '/../..' . '/src/Validator/NotBlankInDefaultLanguage.php', + 'izi\\prestashop\\Validator\\NotBlankInDefaultLanguageValidator' => __DIR__ . '/../..' . '/src/Validator/NotBlankInDefaultLanguageValidator.php', + 'izi\\prestashop\\Validator\\ProcessableMessageFormat' => __DIR__ . '/../..' . '/src/Validator/ProcessableMessageFormat.php', + 'izi\\prestashop\\Validator\\ProcessableMessageFormatValidator' => __DIR__ . '/../..' . '/src/Validator/ProcessableMessageFormatValidator.php', + 'izi\\prestashop\\Validator\\Product\\NotFromRestrictedManufacturer' => __DIR__ . '/../..' . '/src/Validator/Product/NotFromRestrictedManufacturer.php', + 'izi\\prestashop\\Validator\\Product\\NotFromRestrictedManufacturerValidator' => __DIR__ . '/../..' . '/src/Validator/Product/NotFromRestrictedManufacturerValidator.php', + 'izi\\prestashop\\Validator\\Product\\NotInRestrictedCategory' => __DIR__ . '/../..' . '/src/Validator/Product/NotInRestrictedCategory.php', + 'izi\\prestashop\\Validator\\Product\\NotInRestrictedCategoryValidator' => __DIR__ . '/../..' . '/src/Validator/Product/NotInRestrictedCategoryValidator.php', + 'izi\\prestashop\\Validator\\Product\\NotOfType' => __DIR__ . '/../..' . '/src/Validator/Product/NotOfType.php', + 'izi\\prestashop\\Validator\\Product\\NotOfTypeValidator' => __DIR__ . '/../..' . '/src/Validator/Product/NotOfTypeValidator.php', + 'izi\\prestashop\\Validator\\Product\\NotWithRestrictedAttributes' => __DIR__ . '/../..' . '/src/Validator/Product/NotWithRestrictedAttributes.php', + 'izi\\prestashop\\Validator\\Product\\NotWithRestrictedAttributesValidator' => __DIR__ . '/../..' . '/src/Validator/Product/NotWithRestrictedAttributesValidator.php', + 'izi\\prestashop\\Validator\\Product\\NotWithRestrictedFeatures' => __DIR__ . '/../..' . '/src/Validator/Product/NotWithRestrictedFeatures.php', + 'izi\\prestashop\\Validator\\Product\\NotWithRestrictedFeaturesValidator' => __DIR__ . '/../..' . '/src/Validator/Product/NotWithRestrictedFeaturesValidator.php', + 'izi\\prestashop\\Validator\\Product\\Unrestricted' => __DIR__ . '/../..' . '/src/Validator/Product/Unrestricted.php', + 'izi\\prestashop\\Validator\\Product\\UnrestrictedValidator' => __DIR__ . '/../..' . '/src/Validator/Product/UnrestrictedValidator.php', + 'izi\\prestashop\\Validator\\Sequentially' => __DIR__ . '/../..' . '/src/Validator/Sequentially.php', + 'izi\\prestashop\\Validator\\SequentiallyValidator' => __DIR__ . '/../..' . '/src/Validator/SequentiallyValidator.php', + 'izi\\prestashop\\Validator\\Unique' => __DIR__ . '/../..' . '/src/Validator/Unique.php', + 'izi\\prestashop\\Validator\\UniqueValidator' => __DIR__ . '/../..' . '/src/Validator/UniqueValidator.php', + 'izi\\prestashop\\Validator\\ValidatorFactory' => __DIR__ . '/../..' . '/src/Validator/ValidatorFactory.php', + 'izi\\prestashop\\View\\Asset\\AbstractAssetManager' => __DIR__ . '/../..' . '/src/View/Asset/AbstractAssetManager.php', + 'izi\\prestashop\\View\\Asset\\AdminAssetManager' => __DIR__ . '/../..' . '/src/View/Asset/AdminAssetManager.php', + 'izi\\prestashop\\View\\Asset\\AssetManagerInterface' => __DIR__ . '/../..' . '/src/View/Asset/AssetManagerInterface.php', + 'izi\\prestashop\\View\\Asset\\FrontAssetManager' => __DIR__ . '/../..' . '/src/View/Asset/FrontAssetManager.php', + 'izi\\prestashop\\View\\Asset\\Provider\\Admin\\CartRulesAssetsProvider' => __DIR__ . '/../..' . '/src/View/Asset/Provider/Admin/CartRulesAssetsProvider.php', + 'izi\\prestashop\\View\\Asset\\Provider\\AssetsProviderInterface' => __DIR__ . '/../..' . '/src/View/Asset/Provider/AssetsProviderInterface.php', + 'izi\\prestashop\\View\\Asset\\Provider\\DTO\\Assets' => __DIR__ . '/../..' . '/src/View/Asset/Provider/DTO/Assets.php', + 'izi\\prestashop\\View\\Asset\\Provider\\Front\\CommonAssetsProvider' => __DIR__ . '/../..' . '/src/View/Asset/Provider/Front/CommonAssetsProvider.php', + 'izi\\prestashop\\View\\Asset\\Provider\\Front\\ProductPageAssetsProvider' => __DIR__ . '/../..' . '/src/View/Asset/Provider/Front/ProductPageAssetsProvider.php', + 'izi\\prestashop\\View\\Asset\\Provider\\Front\\WidgetConfigurationProvider' => __DIR__ . '/../..' . '/src/View/Asset/Provider/Front/WidgetConfigurationProvider.php', + 'izi\\prestashop\\View\\Asset\\VersionStrategy\\JsonManifestVersionStrategy' => __DIR__ . '/../..' . '/src/View/Asset/VersionStrategy/JsonManifestVersionStrategy.php', + 'izi\\prestashop\\View\\Templating\\RendererInterface' => __DIR__ . '/../..' . '/src/View/Templating/RendererInterface.php', + 'izi\\prestashop\\View\\Templating\\SmartyRenderer' => __DIR__ . '/../..' . '/src/View/Templating/SmartyRenderer.php', + 'izi\\prestashop\\View\\Widget\\FrameStyle' => __DIR__ . '/../..' . '/src/View/Widget/FrameStyle.php', + 'izi\\prestashop\\View\\Widget\\Size' => __DIR__ . '/../..' . '/src/View/Widget/Size.php', + 'izi\\prestashop\\View\\Widget\\Variant' => __DIR__ . '/../..' . '/src/View/Widget/Variant.php', + 'izi\\prestashop\\View\\Widget\\WidgetConfiguration' => __DIR__ . '/../..' . '/src/View/Widget/WidgetConfiguration.php', + 'izi\\prestashop\\View\\Widget\\WidgetConfigurationInterface' => __DIR__ . '/../..' . '/src/View/Widget/WidgetConfigurationInterface.php', + 'izi\\prestashop\\View\\Widget\\WidgetConfigurationResolver' => __DIR__ . '/../..' . '/src/View/Widget/WidgetConfigurationResolver.php', + 'izi\\prestashop\\View\\Widget\\WidgetConfigurationResolverInterface' => __DIR__ . '/../..' . '/src/View/Widget/WidgetConfigurationResolverInterface.php', + 'izi\\prestashop\\rest\\order\\Create' => __DIR__ . '/../..' . '/src/rest/order/Create.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit3582376b22b8ed8077843f108d3dc00a::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit3582376b22b8ed8077843f108d3dc00a::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInit3582376b22b8ed8077843f108d3dc00a::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/modules/inpostizi/vendor/composer/installed.json b/modules/inpostizi/vendor/composer/installed.json new file mode 100644 index 00000000..61d92136 --- /dev/null +++ b/modules/inpostizi/vendor/composer/installed.json @@ -0,0 +1,864 @@ +{ + "packages": [ + { + "name": "maennchen/zipstream-php", + "version": "2.1.0", + "version_normalized": "2.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/maennchen/ZipStream-PHP.git", + "reference": "c4c5803cc1f93df3d2448478ef79394a5981cc58" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/c4c5803cc1f93df3d2448478ef79394a5981cc58", + "reference": "c4c5803cc1f93df3d2448478ef79394a5981cc58", + "shasum": "" + }, + "require": { + "myclabs/php-enum": "^1.5", + "php": ">= 7.1", + "psr/http-message": "^1.0", + "symfony/polyfill-mbstring": "^1.0" + }, + "require-dev": { + "ext-zip": "*", + "guzzlehttp/guzzle": ">= 6.3", + "mikey179/vfsstream": "^1.6", + "phpunit/phpunit": ">= 7.5" + }, + "time": "2020-05-30T13:11:16+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "ZipStream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paul Duncan", + "email": "pabs@pablotron.org" + }, + { + "name": "Jonatan Männchen", + "email": "jonatan@maennchen.ch" + }, + { + "name": "Jesse Donat", + "email": "donatj@gmail.com" + }, + { + "name": "András Kolesár", + "email": "kolesar@kolesar.hu" + } + ], + "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.", + "keywords": [ + "stream", + "zip" + ], + "support": { + "issues": "https://github.com/maennchen/ZipStream-PHP/issues", + "source": "https://github.com/maennchen/ZipStream-PHP/tree/2.1.0" + }, + "funding": [ + { + "url": "https://github.com/maennchen", + "type": "github" + }, + { + "url": "https://opencollective.com/zipstream", + "type": "open_collective" + } + ], + "install-path": "../maennchen/zipstream-php" + }, + { + "name": "myclabs/php-enum", + "version": "1.7.7", + "version_normalized": "1.7.7.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/php-enum.git", + "reference": "d178027d1e679832db9f38248fcc7200647dc2b7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/php-enum/zipball/d178027d1e679832db9f38248fcc7200647dc2b7", + "reference": "d178027d1e679832db9f38248fcc7200647dc2b7", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7", + "squizlabs/php_codesniffer": "1.*", + "vimeo/psalm": "^3.8" + }, + "time": "2020-11-14T18:14:52+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "MyCLabs\\Enum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP Enum contributors", + "homepage": "https://github.com/myclabs/php-enum/graphs/contributors" + } + ], + "description": "PHP Enum implementation", + "homepage": "http://github.com/myclabs/php-enum", + "keywords": [ + "enum" + ], + "support": { + "issues": "https://github.com/myclabs/php-enum/issues", + "source": "https://github.com/myclabs/php-enum/tree/1.7.7" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/php-enum", + "type": "tidelift" + } + ], + "install-path": "../myclabs/php-enum" + }, + { + "name": "nyholm/psr7", + "version": "1.6.1", + "version_normalized": "1.6.1.0", + "source": { + "type": "git", + "url": "https://github.com/Nyholm/psr7.git", + "reference": "e874c8c4286a1e010fb4f385f3a55ac56a05cc93" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Nyholm/psr7/zipball/e874c8c4286a1e010fb4f385f3a55ac56a05cc93", + "reference": "e874c8c4286a1e010fb4f385f3a55ac56a05cc93", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "php-http/message-factory": "^1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "provide": { + "php-http/message-factory-implementation": "1.0", + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "http-interop/http-factory-tests": "^0.9", + "php-http/psr7-integration-tests": "^1.0", + "phpunit/phpunit": "^7.5 || 8.5 || 9.4", + "symfony/error-handler": "^4.4" + }, + "time": "2023-04-17T16:03:48+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Nyholm\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + }, + { + "name": "Martijn van der Ven", + "email": "martijn@vanderven.se" + } + ], + "description": "A fast PHP7 implementation of PSR-7", + "homepage": "https://tnyholm.se", + "keywords": [ + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/Nyholm/psr7/issues", + "source": "https://github.com/Nyholm/psr7/tree/1.6.1" + }, + "funding": [ + { + "url": "https://github.com/Zegnat", + "type": "github" + }, + { + "url": "https://github.com/nyholm", + "type": "github" + } + ], + "install-path": "../nyholm/psr7" + }, + { + "name": "php-http/message-factory", + "version": "1.1.0", + "version_normalized": "1.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/message-factory.git", + "reference": "4d8778e1c7d405cbb471574821c1ff5b68cc8f57" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/message-factory/zipball/4d8778e1c7d405cbb471574821c1ff5b68cc8f57", + "reference": "4d8778e1c7d405cbb471574821c1ff5b68cc8f57", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "psr/http-message": "^1.0 || ^2.0" + }, + "time": "2023-04-14T14:16:17+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Factory interfaces for PSR-7 HTTP Message", + "homepage": "http://php-http.org", + "keywords": [ + "factory", + "http", + "message", + "stream", + "uri" + ], + "support": { + "issues": "https://github.com/php-http/message-factory/issues", + "source": "https://github.com/php-http/message-factory/tree/1.1.0" + }, + "abandoned": "psr/http-factory", + "install-path": "../php-http/message-factory" + }, + { + "name": "psr/clock", + "version": "1.0.0", + "version_normalized": "1.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "time": "2022-11-25T14:36:26+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "install-path": "../psr/clock" + }, + { + "name": "psr/container", + "version": "1.0.0", + "version_normalized": "1.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2017-02-14T16:28:37+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/master" + }, + "install-path": "../psr/container" + }, + { + "name": "psr/http-client", + "version": "1.0.3", + "version_normalized": "1.0.3.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "time": "2023-09-23T14:17:50+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "install-path": "../psr/http-client" + }, + { + "name": "psr/http-factory", + "version": "1.0.2", + "version_normalized": "1.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "e616d01114759c4c489f93b099585439f795fe35" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", + "reference": "e616d01114759c4c489f93b099585439f795fe35", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "time": "2023-04-10T20:10:41+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/1.0.2" + }, + "install-path": "../psr/http-factory" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2016-08-06T14:39:51+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "install-path": "../psr/http-message" + }, + { + "name": "psr/simple-cache", + "version": "1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2017-10-23T01:57:42+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/master" + }, + "install-path": "../psr/simple-cache" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.29.0", + "version_normalized": "1.29.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "time": "2024-01-29T20:11:03+00:00", + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-mbstring" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.29.0", + "version_normalized": "1.29.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "time": "2024-01-29T20:11:03+00:00", + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-php80" + }, + { + "name": "symfony/service-contracts", + "version": "v1.10.0", + "version_normalized": "1.10.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "afa00c500c2d6aea6e3b2f4862355f507bc5ebb4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/afa00c500c2d6aea6e3b2f4862355f507bc5ebb4", + "reference": "afa00c500c2d6aea6e3b2f4862355f507bc5ebb4", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "psr/container": "^1.0" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "time": "2022-05-27T14:01:05+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.1-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v1.10.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/service-contracts" + } + ], + "dev": false, + "dev-package-names": [] +} diff --git a/modules/inpostizi/vendor/composer/installed.php b/modules/inpostizi/vendor/composer/installed.php new file mode 100644 index 00000000..e50fee90 --- /dev/null +++ b/modules/inpostizi/vendor/composer/installed.php @@ -0,0 +1,158 @@ + array( + 'name' => 'inpost-izi-prestashop/inpostizi', + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'reference' => '14efe961af288b0e825052bdcc93d29781661b8f', + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev' => false, + ), + 'versions' => array( + 'inpost-izi-prestashop/inpostizi' => array( + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'reference' => '14efe961af288b0e825052bdcc93d29781661b8f', + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'maennchen/zipstream-php' => array( + 'pretty_version' => '2.1.0', + 'version' => '2.1.0.0', + 'reference' => 'c4c5803cc1f93df3d2448478ef79394a5981cc58', + 'type' => 'library', + 'install_path' => __DIR__ . '/../maennchen/zipstream-php', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'myclabs/php-enum' => array( + 'pretty_version' => '1.7.7', + 'version' => '1.7.7.0', + 'reference' => 'd178027d1e679832db9f38248fcc7200647dc2b7', + 'type' => 'library', + 'install_path' => __DIR__ . '/../myclabs/php-enum', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'nyholm/psr7' => array( + 'pretty_version' => '1.6.1', + 'version' => '1.6.1.0', + 'reference' => 'e874c8c4286a1e010fb4f385f3a55ac56a05cc93', + 'type' => 'library', + 'install_path' => __DIR__ . '/../nyholm/psr7', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'php-http/message-factory' => array( + 'pretty_version' => '1.1.0', + 'version' => '1.1.0.0', + 'reference' => '4d8778e1c7d405cbb471574821c1ff5b68cc8f57', + 'type' => 'library', + 'install_path' => __DIR__ . '/../php-http/message-factory', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'php-http/message-factory-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0', + ), + ), + 'psr/clock' => array( + 'pretty_version' => '1.0.0', + 'version' => '1.0.0.0', + 'reference' => 'e41a24703d4560fd0acb709162f73b8adfc3aa0d', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/clock', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/container' => array( + 'pretty_version' => '1.0.0', + 'version' => '1.0.0.0', + 'reference' => 'b7ce3b176482dbbc1245ebf52b181af44c2cf55f', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/container', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/http-client' => array( + 'pretty_version' => '1.0.3', + 'version' => '1.0.3.0', + 'reference' => 'bb5906edc1c324c9a05aa0873d40117941e5fa90', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/http-client', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/http-factory' => array( + 'pretty_version' => '1.0.2', + 'version' => '1.0.2.0', + 'reference' => 'e616d01114759c4c489f93b099585439f795fe35', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/http-factory', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/http-factory-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0', + ), + ), + 'psr/http-message' => array( + 'pretty_version' => '1.0.1', + 'version' => '1.0.1.0', + 'reference' => 'f6561bf28d520154e4b0ec72be95418abe6d9363', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/http-message', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/http-message-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0', + ), + ), + 'psr/simple-cache' => array( + 'pretty_version' => '1.0.1', + 'version' => '1.0.1.0', + 'reference' => '408d5eafb83c57f6365a3ca330ff23aa4a5fa39b', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/simple-cache', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/polyfill-mbstring' => array( + 'pretty_version' => 'v1.29.0', + 'version' => '1.29.0.0', + 'reference' => '9773676c8a1bb1f8d4340a62efe641cf76eda7ec', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-mbstring', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/polyfill-php80' => array( + 'pretty_version' => 'v1.29.0', + 'version' => '1.29.0.0', + 'reference' => '87b68208d5c1188808dd7839ee1e6c8ec3b02f1b', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-php80', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/service-contracts' => array( + 'pretty_version' => 'v1.10.0', + 'version' => '1.10.0.0', + 'reference' => 'afa00c500c2d6aea6e3b2f4862355f507bc5ebb4', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/service-contracts', + 'aliases' => array(), + 'dev_requirement' => false, + ), + ), +); diff --git a/modules/inpostizi/vendor/maennchen/zipstream-php/.github/FUNDING.yml b/modules/inpostizi/vendor/maennchen/zipstream-php/.github/FUNDING.yml new file mode 100644 index 00000000..d807a3f3 --- /dev/null +++ b/modules/inpostizi/vendor/maennchen/zipstream-php/.github/FUNDING.yml @@ -0,0 +1 @@ +open_collective: zipstream diff --git a/modules/inpostizi/vendor/maennchen/zipstream-php/.github/ISSUE_TEMPLATE.md b/modules/inpostizi/vendor/maennchen/zipstream-php/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..3c2e66d6 --- /dev/null +++ b/modules/inpostizi/vendor/maennchen/zipstream-php/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,12 @@ +# Description of the problem + +Please be very descriptive and include as much details as possible. + +# Example code + +# Informations + +* ZipStream-PHP version: +* PHP version: + +Please include any supplemental information you deem relevant to this issue. diff --git a/modules/inpostizi/vendor/maennchen/zipstream-php/.gitignore b/modules/inpostizi/vendor/maennchen/zipstream-php/.gitignore new file mode 100644 index 00000000..5210738a --- /dev/null +++ b/modules/inpostizi/vendor/maennchen/zipstream-php/.gitignore @@ -0,0 +1,6 @@ +clover.xml +composer.lock +coverage.clover +.idea +phpunit.xml +vendor diff --git a/modules/inpostizi/vendor/maennchen/zipstream-php/.travis.yml b/modules/inpostizi/vendor/maennchen/zipstream-php/.travis.yml new file mode 100644 index 00000000..12c8a640 --- /dev/null +++ b/modules/inpostizi/vendor/maennchen/zipstream-php/.travis.yml @@ -0,0 +1,12 @@ +language: php +dist: trusty +sudo: false +php: + - 7.1 + - 7.2 + - 7.3 +install: composer install +script: ./vendor/bin/phpunit --coverage-clover=coverage.clover +after_script: + - wget https://scrutinizer-ci.com/ocular.phar + - php ocular.phar code-coverage:upload --format=php-clover coverage.clover diff --git a/modules/inpostizi/vendor/maennchen/zipstream-php/CHANGELOG.md b/modules/inpostizi/vendor/maennchen/zipstream-php/CHANGELOG.md new file mode 100644 index 00000000..b1979abc --- /dev/null +++ b/modules/inpostizi/vendor/maennchen/zipstream-php/CHANGELOG.md @@ -0,0 +1,51 @@ +# CHANGELOG for ZipStream-PHP + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + +## [2.1.0] - 2020-06-01 +### Changed +- Don't execute ob_flush() when output buffering is not enabled (#152) +- Fix inconsistent return type on 32-bit systems (#149) Fix #144 +- Use mbstring polyfill (#151) +- Promote 7zip usage over unzip to avoid UTF-8 issues (#147) + +## [2.0.0] - 2020-02-22 +### Breaking change +- Only the self opened streams will be closed (#139) +If you were relying on ZipStream to close streams that the library didn't open, +you'll need to close them yourself now. + +### Changed +- Minor change to data descriptor (#136) + +## [1.2.0] - 2019-07-11 + +### Added +- Option to flush output buffer after every write (#122) + +## [1.1.0] - 2019-04-30 + +### Fixed +- Honor last-modified timestamps set via `ZipStream\Option\File::setTime()` (#106) +- Documentation regarding output of HTTP headers +- Test warnings with PHPUnit (#109) + +### Added +- Test for FileNotReadableException (#114) +- Size attribute to File options (#113) +- Tests on PHP 7.3 (#108) + +## [1.0.0] - 2019-04-17 + +### Breaking changes +- Mininum PHP version is now 7.1 +- Options are now passed to the ZipStream object via the Option\Archive object. See the wiki for available options and code examples + +### Added +- Add large file support with Zip64 headers + +### Changed +- Major refactoring and code cleanup diff --git a/modules/inpostizi/vendor/maennchen/zipstream-php/CONTRIBUTING.md b/modules/inpostizi/vendor/maennchen/zipstream-php/CONTRIBUTING.md new file mode 100644 index 00000000..f8ef5a57 --- /dev/null +++ b/modules/inpostizi/vendor/maennchen/zipstream-php/CONTRIBUTING.md @@ -0,0 +1,25 @@ +# ZipStream Readme for Contributors +## Code styling +### Indention +For spaces are used to indent code. The convention is [K&R](http://en.wikipedia.org/wiki/Indent_style#K&R) + +### Comments +Double Slashes are used for an one line comment. + +Classes, Variables, Methods etc: + +```php +/** + * My comment + * + * @myanotation like @param etc. + */ +``` + +## Pull requests +Feel free to submit pull requests. + +## Testing +For every new feature please write a new PHPUnit test. + +Before every commit execute `./vendor/bin/phpunit` to check if your changes wrecked something: diff --git a/modules/inpostizi/vendor/maennchen/zipstream-php/LICENSE b/modules/inpostizi/vendor/maennchen/zipstream-php/LICENSE new file mode 100644 index 00000000..ebe7fe2f --- /dev/null +++ b/modules/inpostizi/vendor/maennchen/zipstream-php/LICENSE @@ -0,0 +1,24 @@ +MIT License + +Copyright (C) 2007-2009 Paul Duncan +Copyright (C) 2014 Jonatan Männchen +Copyright (C) 2014 Jesse G. Donat +Copyright (C) 2018 Nicolas CARPi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/modules/inpostizi/vendor/maennchen/zipstream-php/README.md b/modules/inpostizi/vendor/maennchen/zipstream-php/README.md new file mode 100644 index 00000000..c2e832b6 --- /dev/null +++ b/modules/inpostizi/vendor/maennchen/zipstream-php/README.md @@ -0,0 +1,123 @@ +# ZipStream-PHP + +[![Build Status](https://travis-ci.org/maennchen/ZipStream-PHP.svg?branch=master)](https://travis-ci.org/maennchen/ZipStream-PHP) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/maennchen/ZipStream-PHP/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/maennchen/ZipStream-PHP/) +[![Code Coverage](https://scrutinizer-ci.com/g/maennchen/ZipStream-PHP/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/maennchen/ZipStream-PHP/) +[![Latest Stable Version](https://poser.pugx.org/maennchen/zipstream-php/v/stable)](https://packagist.org/packages/maennchen/zipstream-php) +[![Total Downloads](https://poser.pugx.org/maennchen/zipstream-php/downloads)](https://packagist.org/packages/maennchen/zipstream-php) +[![Financial Contributors on Open Collective](https://opencollective.com/zipstream/all/badge.svg?label=financial+contributors)](https://opencollective.com/zipstream) [![License](https://img.shields.io/github/license/maennchen/zipstream-php.svg)](LICENSE) + +## Overview + +A fast and simple streaming zip file downloader for PHP. Using this library will save you from having to write the Zip to disk. You can directly send it to the user, which is much faster. It can work with S3 buckets or any PSR7 Stream. + +Please see the [LICENSE](LICENSE) file for licensing and warranty information. + +## Installation + +Simply add a dependency on maennchen/zipstream-php to your project's composer.json file if you use Composer to manage the dependencies of your project. Use following command to add the package to your project's dependencies: + +```bash +composer require maennchen/zipstream-php +``` + +## Usage and options + +Here's a simple example: + +```php +// Autoload the dependencies +require 'vendor/autoload.php'; + +// enable output of HTTP headers +$options = new ZipStream\Option\Archive(); +$options->setSendHttpHeaders(true); + +// create a new zipstream object +$zip = new ZipStream\ZipStream('example.zip', $options); + +// create a file named 'hello.txt' +$zip->addFile('hello.txt', 'This is the contents of hello.txt'); + +// add a file named 'some_image.jpg' from a local file 'path/to/image.jpg' +$zip->addFileFromPath('some_image.jpg', 'path/to/image.jpg'); + +// add a file named 'goodbye.txt' from an open stream resource +$fp = tmpfile(); +fwrite($fp, 'The quick brown fox jumped over the lazy dog.'); +rewind($fp); +$zip->addFileFromStream('goodbye.txt', $fp); +fclose($fp); + +// finish the zip stream +$zip->finish(); +``` + +You can also add comments, modify file timestamps, and customize (or +disable) the HTTP headers. It is also possible to specify the storage method when adding files, +the current default storage method is 'deflate' i.e files are stored with Compression mode 0x08. + +See the [Wiki](https://github.com/maennchen/ZipStream-PHP/wiki) for details. + +## Known issue + +The native Mac OS archive extraction tool might not open archives in some conditions. A workaround is to disable the Zip64 feature with the option `$opt->setEnableZip64(false)`. This limits the archive to 4 Gb and 64k files but will allow Mac OS users to open them without issue. See #116. + +The linux `unzip` utility might not handle properly unicode characters. It is recommended to extract with another tool like [7-zip](https://www.7-zip.org/). See #146. + +## Upgrade to version 2.0.0 + +* Only the self opened streams will be closed (#139) +If you were relying on ZipStream to close streams that the library didn't open, +you'll need to close them yourself now. + +## Upgrade to version 1.0.0 + +* All options parameters to all function have been moved from an `array` to structured option objects. See [the wiki](https://github.com/maennchen/ZipStream-PHP/wiki/Available-options) for examples. +* The whole library has been refactored. The minimal PHP requirement has been raised to PHP 7.1. + +## Usage with Symfony and S3 + +You can find example code on [the wiki](https://github.com/maennchen/ZipStream-PHP/wiki/Symfony-example). + +## Contributing + +ZipStream-PHP is a collaborative project. Please take a look at the [CONTRIBUTING.md](CONTRIBUTING.md) file. + +## About the Authors + +* Paul Duncan - https://pablotron.org/ +* Jonatan Männchen - https://maennchen.dev +* Jesse G. Donat - https://donatstudios.com +* Nicolas CARPi - https://www.deltablot.com +* Nik Barham - https://www.brokencube.co.uk + +## Contributors + +### Code Contributors + +This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)]. + + +### Financial Contributors + +Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/zipstream/contribute)] + +#### Individuals + + + +#### Organizations + +Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/zipstream/contribute)] + + + + + + + + + + + diff --git a/modules/inpostizi/vendor/maennchen/zipstream-php/composer.json b/modules/inpostizi/vendor/maennchen/zipstream-php/composer.json new file mode 100644 index 00000000..103c78c7 --- /dev/null +++ b/modules/inpostizi/vendor/maennchen/zipstream-php/composer.json @@ -0,0 +1,41 @@ +{ + "name": "maennchen/zipstream-php", + "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.", + "keywords": ["zip", "stream"], + "type": "library", + "license": "MIT", + "authors": [{ + "name": "Paul Duncan", + "email": "pabs@pablotron.org" + }, + { + "name": "Jonatan Männchen", + "email": "jonatan@maennchen.ch" + }, + { + "name": "Jesse Donat", + "email": "donatj@gmail.com" + }, + { + "name": "András Kolesár", + "email": "kolesar@kolesar.hu" + } + ], + "require": { + "php": ">= 7.1", + "symfony/polyfill-mbstring": "^1.0", + "psr/http-message": "^1.0", + "myclabs/php-enum": "^1.5" + }, + "require-dev": { + "phpunit/phpunit": ">= 7.5", + "guzzlehttp/guzzle": ">= 6.3", + "ext-zip": "*", + "mikey179/vfsstream": "^1.6" + }, + "autoload": { + "psr-4": { + "ZipStream\\": "src/" + } + } +} diff --git a/modules/inpostizi/vendor/maennchen/zipstream-php/phpunit.xml.dist b/modules/inpostizi/vendor/maennchen/zipstream-php/phpunit.xml.dist new file mode 100644 index 00000000..f6e72279 --- /dev/null +++ b/modules/inpostizi/vendor/maennchen/zipstream-php/phpunit.xml.dist @@ -0,0 +1,17 @@ + + + + test + + + + + + + + + + src + + + diff --git a/modules/inpostizi/vendor/maennchen/zipstream-php/psalm.xml b/modules/inpostizi/vendor/maennchen/zipstream-php/psalm.xml new file mode 100644 index 00000000..42f355b2 --- /dev/null +++ b/modules/inpostizi/vendor/maennchen/zipstream-php/psalm.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/inpostizi/vendor/maennchen/zipstream-php/src/Bigint.php b/modules/inpostizi/vendor/maennchen/zipstream-php/src/Bigint.php new file mode 100644 index 00000000..52ccfd2e --- /dev/null +++ b/modules/inpostizi/vendor/maennchen/zipstream-php/src/Bigint.php @@ -0,0 +1,172 @@ +fillBytes($value, 0, 8); + } + + /** + * Fill the bytes field with int + * + * @param int $value + * @param int $start + * @param int $count + * @return void + */ + protected function fillBytes(int $value, int $start, int $count): void + { + for ($i = 0; $i < $count; $i++) { + $this->bytes[$start + $i] = $i >= PHP_INT_SIZE ? 0 : $value & 0xFF; + $value >>= 8; + } + } + + /** + * Get an instance + * + * @param int $value + * @return Bigint + */ + public static function init(int $value = 0): self + { + return new self($value); + } + + /** + * Fill bytes from low to high + * + * @param int $low + * @param int $high + * @return Bigint + */ + public static function fromLowHigh(int $low, int $high): self + { + $bigint = new Bigint(); + $bigint->fillBytes($low, 0, 4); + $bigint->fillBytes($high, 4, 4); + return $bigint; + } + + /** + * Get high 32 + * + * @return int + */ + public function getHigh32(): int + { + return $this->getValue(4, 4); + } + + /** + * Get value from bytes array + * + * @param int $end + * @param int $length + * @return int + */ + public function getValue(int $end = 0, int $length = 8): int + { + $result = 0; + for ($i = $end + $length - 1; $i >= $end; $i--) { + $result <<= 8; + $result |= $this->bytes[$i]; + } + return $result; + } + + /** + * Get low FF + * + * @param bool $force + * @return float + */ + public function getLowFF(bool $force = false): float + { + if ($force || $this->isOver32()) { + return (float)0xFFFFFFFF; + } + return (float)$this->getLow32(); + } + + /** + * Check if is over 32 + * + * @param bool $force + * @return bool + */ + public function isOver32(bool $force = false): bool + { + // value 0xFFFFFFFF already needs a Zip64 header + return $force || + max(array_slice($this->bytes, 4, 4)) > 0 || + min(array_slice($this->bytes, 0, 4)) === 0xFF; + } + + /** + * Get low 32 + * + * @return int + */ + public function getLow32(): int + { + return $this->getValue(0, 4); + } + + /** + * Get hexadecimal + * + * @return string + */ + public function getHex64(): string + { + $result = '0x'; + for ($i = 7; $i >= 0; $i--) { + $result .= sprintf('%02X', $this->bytes[$i]); + } + return $result; + } + + /** + * Add + * + * @param Bigint $other + * @return Bigint + */ + public function add(Bigint $other): Bigint + { + $result = clone $this; + $overflow = false; + for ($i = 0; $i < 8; $i++) { + $result->bytes[$i] += $other->bytes[$i]; + if ($overflow) { + $result->bytes[$i]++; + $overflow = false; + } + if ($result->bytes[$i] & 0x100) { + $overflow = true; + $result->bytes[$i] &= 0xFF; + } + } + if ($overflow) { + throw new OverflowException; + } + return $result; + } +} diff --git a/modules/inpostizi/vendor/maennchen/zipstream-php/src/DeflateStream.php b/modules/inpostizi/vendor/maennchen/zipstream-php/src/DeflateStream.php new file mode 100644 index 00000000..d6c27288 --- /dev/null +++ b/modules/inpostizi/vendor/maennchen/zipstream-php/src/DeflateStream.php @@ -0,0 +1,70 @@ +filter) { + $this->removeDeflateFilter(); + $this->seek(0); + $this->addDeflateFilter($this->options); + } else { + rewind($this->stream); + } + } + + /** + * Remove the deflate filter + * + * @return void + */ + public function removeDeflateFilter(): void + { + if (!$this->filter) { + return; + } + stream_filter_remove($this->filter); + $this->filter = null; + } + + /** + * Add a deflate filter + * + * @param Option\File $options + * @return void + */ + public function addDeflateFilter(Option\File $options): void + { + $this->options = $options; + // parameter 4 for stream_filter_append expects array + // so we convert the option object in an array + $optionsArr = [ + 'comment' => $options->getComment(), + 'method' => $options->getMethod(), + 'deflateLevel' => $options->getDeflateLevel(), + 'time' => $options->getTime() + ]; + $this->filter = stream_filter_append( + $this->stream, + 'zlib.deflate', + STREAM_FILTER_READ, + $optionsArr + ); + } +} diff --git a/modules/inpostizi/vendor/maennchen/zipstream-php/src/Exception.php b/modules/inpostizi/vendor/maennchen/zipstream-php/src/Exception.php new file mode 100644 index 00000000..18ccfbbd --- /dev/null +++ b/modules/inpostizi/vendor/maennchen/zipstream-php/src/Exception.php @@ -0,0 +1,11 @@ +zip = $zip; + + $this->name = $name; + $this->opt = $opt ?: new FileOptions(); + $this->method = $this->opt->getMethod(); + $this->version = Version::STORE(); + $this->ofs = new Bigint(); + } + + public function processPath(string $path): void + { + if (!is_readable($path)) { + if (!file_exists($path)) { + throw new FileNotFoundException($path); + } + throw new FileNotReadableException($path); + } + if ($this->zip->isLargeFile($path) === false) { + $data = file_get_contents($path); + $this->processData($data); + } else { + $this->method = $this->zip->opt->getLargeFileMethod(); + + $stream = new DeflateStream(fopen($path, 'rb')); + $this->processStream($stream); + $stream->close(); + } + } + + public function processData(string $data): void + { + $this->len = new Bigint(strlen($data)); + $this->crc = crc32($data); + + // compress data if needed + if ($this->method->equals(Method::DEFLATE())) { + $data = gzdeflate($data); + } + + $this->zlen = new Bigint(strlen($data)); + $this->addFileHeader(); + $this->zip->send($data); + $this->addFileFooter(); + } + + /** + * Create and send zip header for this file. + * + * @return void + * @throws \ZipStream\Exception\EncodingException + */ + public function addFileHeader(): void + { + $name = static::filterFilename($this->name); + + // calculate name length + $nameLength = strlen($name); + + // create dos timestamp + $time = static::dosTime($this->opt->getTime()->getTimestamp()); + + $comment = $this->opt->getComment(); + + if (!mb_check_encoding($name, 'ASCII') || + !mb_check_encoding($comment, 'ASCII')) { + // Sets Bit 11: Language encoding flag (EFS). If this bit is set, + // the filename and comment fields for this file + // MUST be encoded using UTF-8. (see APPENDIX D) + if (!mb_check_encoding($name, 'UTF-8') || + !mb_check_encoding($comment, 'UTF-8')) { + throw new EncodingException( + 'File name and comment should use UTF-8 ' . + 'if one of them does not fit into ASCII range.' + ); + } + $this->bits |= self::BIT_EFS_UTF8; + } + + if ($this->method->equals(Method::DEFLATE())) { + $this->version = Version::DEFLATE(); + } + + $force = (boolean)($this->bits & self::BIT_ZERO_HEADER) && + $this->zip->opt->isEnableZip64(); + + $footer = $this->buildZip64ExtraBlock($force); + + // If this file will start over 4GB limit in ZIP file, + // CDR record will have to use Zip64 extension to describe offset + // to keep consistency we use the same value here + if ($this->zip->ofs->isOver32()) { + $this->version = Version::ZIP64(); + } + + $fields = [ + ['V', ZipStream::FILE_HEADER_SIGNATURE], + ['v', $this->version->getValue()], // Version needed to Extract + ['v', $this->bits], // General purpose bit flags - data descriptor flag set + ['v', $this->method->getValue()], // Compression method + ['V', $time], // Timestamp (DOS Format) + ['V', $this->crc], // CRC32 of data (0 -> moved to data descriptor footer) + ['V', $this->zlen->getLowFF($force)], // Length of compressed data (forced to 0xFFFFFFFF for zero header) + ['V', $this->len->getLowFF($force)], // Length of original data (forced to 0xFFFFFFFF for zero header) + ['v', $nameLength], // Length of filename + ['v', strlen($footer)], // Extra data (see above) + ]; + + // pack fields and calculate "total" length + $header = ZipStream::packFields($fields); + + // print header and filename + $data = $header . $name . $footer; + $this->zip->send($data); + + // save header length + $this->hlen = Bigint::init(strlen($data)); + } + + /** + * Strip characters that are not legal in Windows filenames + * to prevent compatibility issues + * + * @param string $filename Unprocessed filename + * @return string + */ + public static function filterFilename(string $filename): string + { + // strip leading slashes from file name + // (fixes bug in windows archive viewer) + $filename = preg_replace('/^\\/+/', '', $filename); + + return str_replace(['\\', ':', '*', '?', '"', '<', '>', '|'], '_', $filename); + } + + /** + * Convert a UNIX timestamp to a DOS timestamp. + * + * @param int $when + * @return int DOS Timestamp + */ + final protected static function dosTime(int $when): int + { + // get date array for timestamp + $d = getdate($when); + + // set lower-bound on dates + if ($d['year'] < 1980) { + $d = array( + 'year' => 1980, + 'mon' => 1, + 'mday' => 1, + 'hours' => 0, + 'minutes' => 0, + 'seconds' => 0 + ); + } + + // remove extra years from 1980 + $d['year'] -= 1980; + + // return date string + return + ($d['year'] << 25) | + ($d['mon'] << 21) | + ($d['mday'] << 16) | + ($d['hours'] << 11) | + ($d['minutes'] << 5) | + ($d['seconds'] >> 1); + } + + protected function buildZip64ExtraBlock(bool $force = false): string + { + + $fields = []; + if ($this->len->isOver32($force)) { + $fields[] = ['P', $this->len]; // Length of original data + } + + if ($this->len->isOver32($force)) { + $fields[] = ['P', $this->zlen]; // Length of compressed data + } + + if ($this->ofs->isOver32()) { + $fields[] = ['P', $this->ofs]; // Offset of local header record + } + + if (!empty($fields)) { + if (!$this->zip->opt->isEnableZip64()) { + throw new OverflowException(); + } + + array_unshift( + $fields, + ['v', 0x0001], // 64 bit extension + ['v', count($fields) * 8] // Length of data block + ); + $this->version = Version::ZIP64(); + } + + return ZipStream::packFields($fields); + } + + /** + * Create and send data descriptor footer for this file. + * + * @return void + */ + + public function addFileFooter(): void + { + + if ($this->bits & self::BIT_ZERO_HEADER) { + // compressed and uncompressed size + $sizeFormat = 'V'; + if ($this->zip->opt->isEnableZip64()) { + $sizeFormat = 'P'; + } + $fields = [ + ['V', ZipStream::DATA_DESCRIPTOR_SIGNATURE], + ['V', $this->crc], // CRC32 + [$sizeFormat, $this->zlen], // Length of compressed data + [$sizeFormat, $this->len], // Length of original data + ]; + + $footer = ZipStream::packFields($fields); + $this->zip->send($footer); + } else { + $footer = ''; + } + $this->totalLength = $this->hlen->add($this->zlen)->add(Bigint::init(strlen($footer))); + $this->zip->addToCdr($this); + } + + public function processStream(StreamInterface $stream): void + { + $this->zlen = new Bigint(); + $this->len = new Bigint(); + + if ($this->zip->opt->isZeroHeader()) { + $this->processStreamWithZeroHeader($stream); + } else { + $this->processStreamWithComputedHeader($stream); + } + } + + protected function processStreamWithZeroHeader(StreamInterface $stream): void + { + $this->bits |= self::BIT_ZERO_HEADER; + $this->addFileHeader(); + $this->readStream($stream, self::COMPUTE | self::SEND); + $this->addFileFooter(); + } + + protected function readStream(StreamInterface $stream, ?int $options = null): void + { + $this->deflateInit(); + $total = 0; + $size = $this->opt->getSize(); + while (!$stream->eof() && ($size === 0 || $total < $size)) { + $data = $stream->read(self::CHUNKED_READ_BLOCK_SIZE); + $total += strlen($data); + if ($size > 0 && $total > $size) { + $data = substr($data, 0 , strlen($data)-($total - $size)); + } + $this->deflateData($stream, $data, $options); + if ($options & self::SEND) { + $this->zip->send($data); + } + } + $this->deflateFinish($options); + } + + protected function deflateInit(): void + { + $this->hash = hash_init(self::HASH_ALGORITHM); + if ($this->method->equals(Method::DEFLATE())) { + $this->deflate = deflate_init( + ZLIB_ENCODING_RAW, + ['level' => $this->opt->getDeflateLevel()] + ); + } + } + + protected function deflateData(StreamInterface $stream, string &$data, ?int $options = null): void + { + if ($options & self::COMPUTE) { + $this->len = $this->len->add(Bigint::init(strlen($data))); + hash_update($this->hash, $data); + } + if ($this->deflate) { + $data = deflate_add( + $this->deflate, + $data, + $stream->eof() + ? ZLIB_FINISH + : ZLIB_NO_FLUSH + ); + } + if ($options & self::COMPUTE) { + $this->zlen = $this->zlen->add(Bigint::init(strlen($data))); + } + } + + protected function deflateFinish(?int $options = null): void + { + if ($options & self::COMPUTE) { + $this->crc = hexdec(hash_final($this->hash)); + } + } + + protected function processStreamWithComputedHeader(StreamInterface $stream): void + { + $this->readStream($stream, self::COMPUTE); + $stream->rewind(); + + // incremental compression with deflate_add + // makes this second read unnecessary + // but it is only available from PHP 7.0 + if (!$this->deflate && $stream instanceof DeflateStream && $this->method->equals(Method::DEFLATE())) { + $stream->addDeflateFilter($this->opt); + $this->zlen = new Bigint(); + while (!$stream->eof()) { + $data = $stream->read(self::CHUNKED_READ_BLOCK_SIZE); + $this->zlen = $this->zlen->add(Bigint::init(strlen($data))); + } + $stream->rewind(); + } + + $this->addFileHeader(); + $this->readStream($stream, self::SEND); + $this->addFileFooter(); + } + + /** + * Send CDR record for specified file. + * + * @return string + */ + public function getCdrFile(): string + { + $name = static::filterFilename($this->name); + + // get attributes + $comment = $this->opt->getComment(); + + // get dos timestamp + $time = static::dosTime($this->opt->getTime()->getTimestamp()); + + $footer = $this->buildZip64ExtraBlock(); + + $fields = [ + ['V', ZipStream::CDR_FILE_SIGNATURE], // Central file header signature + ['v', ZipStream::ZIP_VERSION_MADE_BY], // Made by version + ['v', $this->version->getValue()], // Extract by version + ['v', $this->bits], // General purpose bit flags - data descriptor flag set + ['v', $this->method->getValue()], // Compression method + ['V', $time], // Timestamp (DOS Format) + ['V', $this->crc], // CRC32 + ['V', $this->zlen->getLowFF()], // Compressed Data Length + ['V', $this->len->getLowFF()], // Original Data Length + ['v', strlen($name)], // Length of filename + ['v', strlen($footer)], // Extra data len (see above) + ['v', strlen($comment)], // Length of comment + ['v', 0], // Disk number + ['v', 0], // Internal File Attributes + ['V', 32], // External File Attributes + ['V', $this->ofs->getLowFF()] // Relative offset of local header + ]; + + // pack fields, then append name and comment + $header = ZipStream::packFields($fields); + + return $header . $name . $footer . $comment; + } + + /** + * @return Bigint + */ + public function getTotalLength(): Bigint + { + return $this->totalLength; + } +} diff --git a/modules/inpostizi/vendor/maennchen/zipstream-php/src/Option/Archive.php b/modules/inpostizi/vendor/maennchen/zipstream-php/src/Option/Archive.php new file mode 100644 index 00000000..b6b95cce --- /dev/null +++ b/modules/inpostizi/vendor/maennchen/zipstream-php/src/Option/Archive.php @@ -0,0 +1,261 @@ + 4 GB or file count > 64k) + * + * @var bool + */ + private $enableZip64 = true; + /** + * Enable streaming files with single read where + * general purpose bit 3 indicates local file header + * contain zero values in crc and size fields, + * these appear only after file contents + * in data descriptor block. + * + * @var bool + */ + private $zeroHeader = false; + /** + * Enable reading file stat for determining file size. + * When a 32-bit system reads file size that is + * over 2 GB, invalid value appears in file size + * due to integer overflow. Should be disabled on + * 32-bit systems with method addFileFromPath + * if any file may exceed 2 GB. In this case file + * will be read in blocks and correct size will be + * determined from content. + * + * @var bool + */ + private $statFiles = true; + /** + * Enable flush after every write to output stream. + * @var bool + */ + private $flushOutput = false; + /** + * HTTP Content-Disposition. Defaults to + * 'attachment', where + * FILENAME is the specified filename. + * + * Note that this does nothing if you are + * not sending HTTP headers. + * + * @var string + */ + private $contentDisposition = 'attachment'; + /** + * Note that this does nothing if you are + * not sending HTTP headers. + * + * @var string + */ + private $contentType = 'application/x-zip'; + /** + * @var int + */ + private $deflateLevel = 6; + + /** + * @var resource + */ + private $outputStream; + + /** + * Options constructor. + */ + public function __construct() + { + $this->largeFileMethod = Method::STORE(); + $this->outputStream = fopen('php://output', 'wb'); + } + + public function getComment(): string + { + return $this->comment; + } + + public function setComment(string $comment): void + { + $this->comment = $comment; + } + + public function getLargeFileSize(): int + { + return $this->largeFileSize; + } + + public function setLargeFileSize(int $largeFileSize): void + { + $this->largeFileSize = $largeFileSize; + } + + public function getLargeFileMethod(): Method + { + return $this->largeFileMethod; + } + + public function setLargeFileMethod(Method $largeFileMethod): void + { + $this->largeFileMethod = $largeFileMethod; + } + + public function isSendHttpHeaders(): bool + { + return $this->sendHttpHeaders; + } + + public function setSendHttpHeaders(bool $sendHttpHeaders): void + { + $this->sendHttpHeaders = $sendHttpHeaders; + } + + public function getHttpHeaderCallback(): Callable + { + return $this->httpHeaderCallback; + } + + public function setHttpHeaderCallback(Callable $httpHeaderCallback): void + { + $this->httpHeaderCallback = $httpHeaderCallback; + } + + public function isEnableZip64(): bool + { + return $this->enableZip64; + } + + public function setEnableZip64(bool $enableZip64): void + { + $this->enableZip64 = $enableZip64; + } + + public function isZeroHeader(): bool + { + return $this->zeroHeader; + } + + public function setZeroHeader(bool $zeroHeader): void + { + $this->zeroHeader = $zeroHeader; + } + + public function isFlushOutput(): bool + { + return $this->flushOutput; + } + + public function setFlushOutput(bool $flushOutput): void + { + $this->flushOutput = $flushOutput; + } + + public function isStatFiles(): bool + { + return $this->statFiles; + } + + public function setStatFiles(bool $statFiles): void + { + $this->statFiles = $statFiles; + } + + public function getContentDisposition(): string + { + return $this->contentDisposition; + } + + public function setContentDisposition(string $contentDisposition): void + { + $this->contentDisposition = $contentDisposition; + } + + public function getContentType(): string + { + return $this->contentType; + } + + public function setContentType(string $contentType): void + { + $this->contentType = $contentType; + } + + /** + * @return resource + */ + public function getOutputStream() + { + return $this->outputStream; + } + + /** + * @param resource $outputStream + */ + public function setOutputStream($outputStream): void + { + $this->outputStream = $outputStream; + } + + /** + * @return int + */ + public function getDeflateLevel(): int + { + return $this->deflateLevel; + } + + /** + * @param int $deflateLevel + */ + public function setDeflateLevel(int $deflateLevel): void + { + $this->deflateLevel = $deflateLevel; + } +} diff --git a/modules/inpostizi/vendor/maennchen/zipstream-php/src/Option/File.php b/modules/inpostizi/vendor/maennchen/zipstream-php/src/Option/File.php new file mode 100644 index 00000000..7fd29ea5 --- /dev/null +++ b/modules/inpostizi/vendor/maennchen/zipstream-php/src/Option/File.php @@ -0,0 +1,116 @@ +deflateLevel = $this->deflateLevel ?: $archiveOptions->getDeflateLevel(); + $this->time = $this->time ?: new DateTime(); + } + + /** + * @return string + */ + public function getComment(): string + { + return $this->comment; + } + + /** + * @param string $comment + */ + public function setComment(string $comment): void + { + $this->comment = $comment; + } + + /** + * @return Method + */ + public function getMethod(): Method + { + return $this->method ?: Method::DEFLATE(); + } + + /** + * @param Method $method + */ + public function setMethod(Method $method): void + { + $this->method = $method; + } + + /** + * @return int + */ + public function getDeflateLevel(): int + { + return $this->deflateLevel ?: Archive::DEFAULT_DEFLATE_LEVEL; + } + + /** + * @param int $deflateLevel + */ + public function setDeflateLevel(int $deflateLevel): void + { + $this->deflateLevel = $deflateLevel; + } + + /** + * @return DateTime + */ + public function getTime(): DateTime + { + return $this->time; + } + + /** + * @param DateTime $time + */ + public function setTime(DateTime $time): void + { + $this->time = $time; + } + + /** + * @return int + */ + public function getSize(): int + { + return $this->size; + } + + /** + * @param int $size + */ + public function setSize(int $size): void + { + $this->size = $size; + } +} diff --git a/modules/inpostizi/vendor/maennchen/zipstream-php/src/Option/Method.php b/modules/inpostizi/vendor/maennchen/zipstream-php/src/Option/Method.php new file mode 100644 index 00000000..bbec84c0 --- /dev/null +++ b/modules/inpostizi/vendor/maennchen/zipstream-php/src/Option/Method.php @@ -0,0 +1,19 @@ +stream = $stream; + } + + /** + * Closes the stream and any underlying resources. + * + * @return void + */ + public function close(): void + { + if (is_resource($this->stream)) { + fclose($this->stream); + } + $this->detach(); + } + + /** + * Separates any underlying resources from the stream. + * + * After the stream has been detached, the stream is in an unusable state. + * + * @return resource|null Underlying PHP stream, if any + */ + public function detach() + { + $result = $this->stream; + $this->stream = null; + return $result; + } + + /** + * Reads all data from the stream into a string, from the beginning to end. + * + * This method MUST attempt to seek to the beginning of the stream before + * reading data and read the stream until the end is reached. + * + * Warning: This could attempt to load a large amount of data into memory. + * + * This method MUST NOT raise an exception in order to conform with PHP's + * string casting operations. + * + * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring + * @return string + */ + public function __toString(): string + { + try { + $this->seek(0); + } catch (\RuntimeException $e) {} + return (string) stream_get_contents($this->stream); + } + + /** + * Seek to a position in the stream. + * + * @link http://www.php.net/manual/en/function.fseek.php + * @param int $offset Stream offset + * @param int $whence Specifies how the cursor position will be calculated + * based on the seek offset. Valid values are identical to the built-in + * PHP $whence values for `fseek()`. SEEK_SET: Set position equal to + * offset bytes SEEK_CUR: Set position to current location plus offset + * SEEK_END: Set position to end-of-stream plus offset. + * @throws \RuntimeException on failure. + */ + public function seek($offset, $whence = SEEK_SET): void + { + if (!$this->isSeekable()) { + throw new RuntimeException; + } + if (fseek($this->stream, $offset, $whence) !== 0) { + throw new RuntimeException; + } + } + + /** + * Returns whether or not the stream is seekable. + * + * @return bool + */ + public function isSeekable(): bool + { + return (bool)$this->getMetadata('seekable'); + } + + /** + * Get stream metadata as an associative array or retrieve a specific key. + * + * The keys returned are identical to the keys returned from PHP's + * stream_get_meta_data() function. + * + * @link http://php.net/manual/en/function.stream-get-meta-data.php + * @param string $key Specific metadata to retrieve. + * @return array|mixed|null Returns an associative array if no key is + * provided. Returns a specific key value if a key is provided and the + * value is found, or null if the key is not found. + */ + public function getMetadata($key = null) + { + $metadata = stream_get_meta_data($this->stream); + return $key !== null ? @$metadata[$key] : $metadata; + } + + /** + * Get the size of the stream if known. + * + * @return int|null Returns the size in bytes if known, or null if unknown. + */ + public function getSize(): ?int + { + $stats = fstat($this->stream); + return $stats['size']; + } + + /** + * Returns the current position of the file read/write pointer + * + * @return int Position of the file pointer + * @throws \RuntimeException on error. + */ + public function tell(): int + { + $position = ftell($this->stream); + if ($position === false) { + throw new RuntimeException; + } + return $position; + } + + /** + * Returns true if the stream is at the end of the stream. + * + * @return bool + */ + public function eof(): bool + { + return feof($this->stream); + } + + /** + * Seek to the beginning of the stream. + * + * If the stream is not seekable, this method will raise an exception; + * otherwise, it will perform a seek(0). + * + * @see seek() + * @link http://www.php.net/manual/en/function.fseek.php + * @throws \RuntimeException on failure. + */ + public function rewind(): void + { + $this->seek(0); + } + + /** + * Write data to the stream. + * + * @param string $string The string that is to be written. + * @return int Returns the number of bytes written to the stream. + * @throws \RuntimeException on failure. + */ + public function write($string): int + { + if (!$this->isWritable()) { + throw new RuntimeException; + } + if (fwrite($this->stream, $string) === false) { + throw new RuntimeException; + } + return \mb_strlen($string); + } + + /** + * Returns whether or not the stream is writable. + * + * @return bool + */ + public function isWritable(): bool + { + return preg_match('/[waxc+]/', $this->getMetadata('mode')) === 1; + } + + /** + * Read data from the stream. + * + * @param int $length Read up to $length bytes from the object and return + * them. Fewer than $length bytes may be returned if underlying stream + * call returns fewer bytes. + * @return string Returns the data read from the stream, or an empty string + * if no bytes are available. + * @throws \RuntimeException if an error occurs. + */ + public function read($length): string + { + if (!$this->isReadable()) { + throw new RuntimeException; + } + $result = fread($this->stream, $length); + if ($result === false) { + throw new RuntimeException; + } + return $result; + } + + /** + * Returns whether or not the stream is readable. + * + * @return bool + */ + public function isReadable(): bool + { + return preg_match('/[r+]/', $this->getMetadata('mode')) === 1; + } + + /** + * Returns the remaining contents in a string + * + * @return string + * @throws \RuntimeException if unable to read or an error occurs while + * reading. + */ + public function getContents(): string + { + if (!$this->isReadable()) { + throw new RuntimeException; + } + $result = stream_get_contents($this->stream); + if ($result === false) { + throw new RuntimeException; + } + return $result; + } +} diff --git a/modules/inpostizi/vendor/maennchen/zipstream-php/src/ZipStream.php b/modules/inpostizi/vendor/maennchen/zipstream-php/src/ZipStream.php new file mode 100644 index 00000000..e83038cf --- /dev/null +++ b/modules/inpostizi/vendor/maennchen/zipstream-php/src/ZipStream.php @@ -0,0 +1,599 @@ +addFile('some_file.gif', $data); + * + * * add second file + * $data = file_get_contents('some_file.gif'); + * $zip->addFile('another_file.png', $data); + * + * 3. Finish the zip stream: + * + * $zip->finish(); + * + * You can also add an archive comment, add comments to individual files, + * and adjust the timestamp of files. See the API documentation for each + * method below for additional information. + * + * Example: + * + * // create a new zip stream object + * $zip = new ZipStream('some_files.zip'); + * + * // list of local files + * $files = array('foo.txt', 'bar.jpg'); + * + * // read and add each file to the archive + * foreach ($files as $path) + * $zip->addFile($path, file_get_contents($path)); + * + * // write archive footer to stream + * $zip->finish(); + */ +class ZipStream +{ + /** + * This number corresponds to the ZIP version/OS used (2 bytes) + * From: https://www.iana.org/assignments/media-types/application/zip + * The upper byte (leftmost one) indicates the host system (OS) for the + * file. Software can use this information to determine + * the line record format for text files etc. The current + * mappings are: + * + * 0 - MS-DOS and OS/2 (F.A.T. file systems) + * 1 - Amiga 2 - VAX/VMS + * 3 - *nix 4 - VM/CMS + * 5 - Atari ST 6 - OS/2 H.P.F.S. + * 7 - Macintosh 8 - Z-System + * 9 - CP/M 10 thru 255 - unused + * + * The lower byte (rightmost one) indicates the version number of the + * software used to encode the file. The value/10 + * indicates the major version number, and the value + * mod 10 is the minor version number. + * Here we are using 6 for the OS, indicating OS/2 H.P.F.S. + * to prevent file permissions issues upon extract (see #84) + * 0x603 is 00000110 00000011 in binary, so 6 and 3 + */ + const ZIP_VERSION_MADE_BY = 0x603; + + /** + * The following signatures end with 0x4b50, which in ASCII is PK, + * the initials of the inventor Phil Katz. + * See https://en.wikipedia.org/wiki/Zip_(file_format)#File_headers + */ + const FILE_HEADER_SIGNATURE = 0x04034b50; + const CDR_FILE_SIGNATURE = 0x02014b50; + const CDR_EOF_SIGNATURE = 0x06054b50; + const DATA_DESCRIPTOR_SIGNATURE = 0x08074b50; + const ZIP64_CDR_EOF_SIGNATURE = 0x06064b50; + const ZIP64_CDR_LOCATOR_SIGNATURE = 0x07064b50; + + /** + * Global Options + * + * @var ArchiveOptions + */ + public $opt; + + /** + * @var array + */ + public $files = []; + + /** + * @var Bigint + */ + public $cdr_ofs; + + /** + * @var Bigint + */ + public $ofs; + + /** + * @var bool + */ + protected $need_headers; + + /** + * @var null|String + */ + protected $output_name; + + /** + * Create a new ZipStream object. + * + * Parameters: + * + * @param String $name - Name of output file (optional). + * @param ArchiveOptions $opt - Archive Options + * + * Large File Support: + * + * By default, the method addFileFromPath() will send send files + * larger than 20 megabytes along raw rather than attempting to + * compress them. You can change both the maximum size and the + * compression behavior using the largeFile* options above, with the + * following caveats: + * + * * For "small" files (e.g. files smaller than largeFileSize), the + * memory use can be up to twice that of the actual file. In other + * words, adding a 10 megabyte file to the archive could potentially + * occupy 20 megabytes of memory. + * + * * Enabling compression on large files (e.g. files larger than + * large_file_size) is extremely slow, because ZipStream has to pass + * over the large file once to calculate header information, and then + * again to compress and send the actual data. + * + * Examples: + * + * // create a new zip file named 'foo.zip' + * $zip = new ZipStream('foo.zip'); + * + * // create a new zip file named 'bar.zip' with a comment + * $opt->setComment = 'this is a comment for the zip file.'; + * $zip = new ZipStream('bar.zip', $opt); + * + * Notes: + * + * In order to let this library send HTTP headers, a filename must be given + * _and_ the option `sendHttpHeaders` must be `true`. This behavior is to + * allow software to send its own headers (including the filename), and + * still use this library. + */ + public function __construct(?string $name = null, ?ArchiveOptions $opt = null) + { + $this->opt = $opt ?: new ArchiveOptions(); + + $this->output_name = $name; + $this->need_headers = $name && $this->opt->isSendHttpHeaders(); + + $this->cdr_ofs = new Bigint(); + $this->ofs = new Bigint(); + } + + /** + * addFile + * + * Add a file to the archive. + * + * @param String $name - path of file in archive (including directory). + * @param String $data - contents of file + * @param FileOptions $options + * + * File Options: + * time - Last-modified timestamp (seconds since the epoch) of + * this file. Defaults to the current time. + * comment - Comment related to this file. + * method - Storage method for file ("store" or "deflate") + * + * Examples: + * + * // add a file named 'foo.txt' + * $data = file_get_contents('foo.txt'); + * $zip->addFile('foo.txt', $data); + * + * // add a file named 'bar.jpg' with a comment and a last-modified + * // time of two hours ago + * $data = file_get_contents('bar.jpg'); + * $opt->setTime = time() - 2 * 3600; + * $opt->setComment = 'this is a comment about bar.jpg'; + * $zip->addFile('bar.jpg', $data, $opt); + */ + public function addFile(string $name, string $data, ?FileOptions $options = null): void + { + $options = $options ?: new FileOptions(); + $options->defaultTo($this->opt); + + $file = new File($this, $name, $options); + $file->processData($data); + } + + /** + * addFileFromPath + * + * Add a file at path to the archive. + * + * Note that large files may be compressed differently than smaller + * files; see the "Large File Support" section above for more + * information. + * + * @param String $name - name of file in archive (including directory path). + * @param String $path - path to file on disk (note: paths should be encoded using + * UNIX-style forward slashes -- e.g '/path/to/some/file'). + * @param FileOptions $options + * + * File Options: + * time - Last-modified timestamp (seconds since the epoch) of + * this file. Defaults to the current time. + * comment - Comment related to this file. + * method - Storage method for file ("store" or "deflate") + * + * Examples: + * + * // add a file named 'foo.txt' from the local file '/tmp/foo.txt' + * $zip->addFileFromPath('foo.txt', '/tmp/foo.txt'); + * + * // add a file named 'bigfile.rar' from the local file + * // '/usr/share/bigfile.rar' with a comment and a last-modified + * // time of two hours ago + * $path = '/usr/share/bigfile.rar'; + * $opt->setTime = time() - 2 * 3600; + * $opt->setComment = 'this is a comment about bar.jpg'; + * $zip->addFileFromPath('bigfile.rar', $path, $opt); + * + * @return void + * @throws \ZipStream\Exception\FileNotFoundException + * @throws \ZipStream\Exception\FileNotReadableException + */ + public function addFileFromPath(string $name, string $path, ?FileOptions $options = null): void + { + $options = $options ?: new FileOptions(); + $options->defaultTo($this->opt); + + $file = new File($this, $name, $options); + $file->processPath($path); + } + + /** + * addFileFromStream + * + * Add an open stream to the archive. + * + * @param String $name - path of file in archive (including directory). + * @param resource $stream - contents of file as a stream resource + * @param FileOptions $options + * + * File Options: + * time - Last-modified timestamp (seconds since the epoch) of + * this file. Defaults to the current time. + * comment - Comment related to this file. + * + * Examples: + * + * // create a temporary file stream and write text to it + * $fp = tmpfile(); + * fwrite($fp, 'The quick brown fox jumped over the lazy dog.'); + * + * // add a file named 'streamfile.txt' from the content of the stream + * $x->addFileFromStream('streamfile.txt', $fp); + * + * @return void + */ + public function addFileFromStream(string $name, $stream, ?FileOptions $options = null): void + { + $options = $options ?: new FileOptions(); + $options->defaultTo($this->opt); + + $file = new File($this, $name, $options); + $file->processStream(new DeflateStream($stream)); + } + + /** + * addFileFromPsr7Stream + * + * Add an open stream to the archive. + * + * @param String $name - path of file in archive (including directory). + * @param StreamInterface $stream - contents of file as a stream resource + * @param FileOptions $options + * + * File Options: + * time - Last-modified timestamp (seconds since the epoch) of + * this file. Defaults to the current time. + * comment - Comment related to this file. + * + * Examples: + * + * // create a temporary file stream and write text to it + * $fp = tmpfile(); + * fwrite($fp, 'The quick brown fox jumped over the lazy dog.'); + * + * // add a file named 'streamfile.txt' from the content of the stream + * $x->addFileFromPsr7Stream('streamfile.txt', $fp); + * + * @return void + */ + public function addFileFromPsr7Stream( + string $name, + StreamInterface $stream, + ?FileOptions $options = null + ): void { + $options = $options ?: new FileOptions(); + $options->defaultTo($this->opt); + + $file = new File($this, $name, $options); + $file->processStream($stream); + } + + /** + * finish + * + * Write zip footer to stream. + * + * Example: + * + * // add a list of files to the archive + * $files = array('foo.txt', 'bar.jpg'); + * foreach ($files as $path) + * $zip->addFile($path, file_get_contents($path)); + * + * // write footer to stream + * $zip->finish(); + * @return void + * + * @throws OverflowException + */ + public function finish(): void + { + // add trailing cdr file records + foreach ($this->files as $cdrFile) { + $this->send($cdrFile); + $this->cdr_ofs = $this->cdr_ofs->add(Bigint::init(strlen($cdrFile))); + } + + // Add 64bit headers (if applicable) + if (count($this->files) >= 0xFFFF || + $this->cdr_ofs->isOver32() || + $this->ofs->isOver32()) { + if (!$this->opt->isEnableZip64()) { + throw new OverflowException(); + } + + $this->addCdr64Eof(); + $this->addCdr64Locator(); + } + + // add trailing cdr eof record + $this->addCdrEof(); + + // The End + $this->clear(); + } + + /** + * Send ZIP64 CDR EOF (Central Directory Record End-of-File) record. + * + * @return void + */ + protected function addCdr64Eof(): void + { + $num_files = count($this->files); + $cdr_length = $this->cdr_ofs; + $cdr_offset = $this->ofs; + + $fields = [ + ['V', static::ZIP64_CDR_EOF_SIGNATURE], // ZIP64 end of central file header signature + ['P', 44], // Length of data below this header (length of block - 12) = 44 + ['v', static::ZIP_VERSION_MADE_BY], // Made by version + ['v', Version::ZIP64], // Extract by version + ['V', 0x00], // disk number + ['V', 0x00], // no of disks + ['P', $num_files], // no of entries on disk + ['P', $num_files], // no of entries in cdr + ['P', $cdr_length], // CDR size + ['P', $cdr_offset], // CDR offset + ]; + + $ret = static::packFields($fields); + $this->send($ret); + } + + /** + * Create a format string and argument list for pack(), then call + * pack() and return the result. + * + * @param array $fields + * @return string + */ + public static function packFields(array $fields): string + { + $fmt = ''; + $args = []; + + // populate format string and argument list + foreach ($fields as [$format, $value]) { + if ($format === 'P') { + $fmt .= 'VV'; + if ($value instanceof Bigint) { + $args[] = $value->getLow32(); + $args[] = $value->getHigh32(); + } else { + $args[] = $value; + $args[] = 0; + } + } else { + if ($value instanceof Bigint) { + $value = $value->getLow32(); + } + $fmt .= $format; + $args[] = $value; + } + } + + // prepend format string to argument list + array_unshift($args, $fmt); + + // build output string from header and compressed data + return pack(...$args); + } + + /** + * Send string, sending HTTP headers if necessary. + * Flush output after write if configure option is set. + * + * @param String $str + * @return void + */ + public function send(string $str): void + { + if ($this->need_headers) { + $this->sendHttpHeaders(); + } + $this->need_headers = false; + + fwrite($this->opt->getOutputStream(), $str); + + if ($this->opt->isFlushOutput()) { + // flush output buffer if it is on and flushable + $status = ob_get_status(); + if (isset($status['flags']) && ($status['flags'] & PHP_OUTPUT_HANDLER_FLUSHABLE)) { + ob_flush(); + } + + // Flush system buffers after flushing userspace output buffer + flush(); + } + } + + /** + * Send HTTP headers for this stream. + * + * @return void + */ + protected function sendHttpHeaders(): void + { + // grab content disposition + $disposition = $this->opt->getContentDisposition(); + + if ($this->output_name) { + // Various different browsers dislike various characters here. Strip them all for safety. + $safe_output = trim(str_replace(['"', "'", '\\', ';', "\n", "\r"], '', $this->output_name)); + + // Check if we need to UTF-8 encode the filename + $urlencoded = rawurlencode($safe_output); + $disposition .= "; filename*=UTF-8''{$urlencoded}"; + } + + $headers = array( + 'Content-Type' => $this->opt->getContentType(), + 'Content-Disposition' => $disposition, + 'Pragma' => 'public', + 'Cache-Control' => 'public, must-revalidate', + 'Content-Transfer-Encoding' => 'binary' + ); + + $call = $this->opt->getHttpHeaderCallback(); + foreach ($headers as $key => $val) { + $call("$key: $val"); + } + } + + /** + * Send ZIP64 CDR Locator (Central Directory Record Locator) record. + * + * @return void + */ + protected function addCdr64Locator(): void + { + $cdr_offset = $this->ofs->add($this->cdr_ofs); + + $fields = [ + ['V', static::ZIP64_CDR_LOCATOR_SIGNATURE], // ZIP64 end of central file header signature + ['V', 0x00], // Disc number containing CDR64EOF + ['P', $cdr_offset], // CDR offset + ['V', 1], // Total number of disks + ]; + + $ret = static::packFields($fields); + $this->send($ret); + } + + /** + * Send CDR EOF (Central Directory Record End-of-File) record. + * + * @return void + */ + protected function addCdrEof(): void + { + $num_files = count($this->files); + $cdr_length = $this->cdr_ofs; + $cdr_offset = $this->ofs; + + // grab comment (if specified) + $comment = $this->opt->getComment(); + + $fields = [ + ['V', static::CDR_EOF_SIGNATURE], // end of central file header signature + ['v', 0x00], // disk number + ['v', 0x00], // no of disks + ['v', min($num_files, 0xFFFF)], // no of entries on disk + ['v', min($num_files, 0xFFFF)], // no of entries in cdr + ['V', $cdr_length->getLowFF()], // CDR size + ['V', $cdr_offset->getLowFF()], // CDR offset + ['v', strlen($comment)], // Zip Comment size + ]; + + $ret = static::packFields($fields) . $comment; + $this->send($ret); + } + + /** + * Clear all internal variables. Note that the stream object is not + * usable after this. + * + * @return void + */ + protected function clear(): void + { + $this->files = []; + $this->ofs = new Bigint(); + $this->cdr_ofs = new Bigint(); + $this->opt = new ArchiveOptions(); + } + + /** + * Is this file larger than large_file_size? + * + * @param string $path + * @return bool + */ + public function isLargeFile(string $path): bool + { + if (!$this->opt->isStatFiles()) { + return false; + } + $stat = stat($path); + return $stat['size'] > $this->opt->getLargeFileSize(); + } + + /** + * Save file attributes for trailing CDR record. + * + * @param File $file + * @return void + */ + public function addToCdr(File $file): void + { + $file->ofs = $this->ofs; + $this->ofs = $this->ofs->add($file->getTotalLength()); + $this->files[] = $file->getCdrFile(); + } +} diff --git a/modules/inpostizi/vendor/maennchen/zipstream-php/test/BigintTest.php b/modules/inpostizi/vendor/maennchen/zipstream-php/test/BigintTest.php new file mode 100644 index 00000000..ac9c7c28 --- /dev/null +++ b/modules/inpostizi/vendor/maennchen/zipstream-php/test/BigintTest.php @@ -0,0 +1,65 @@ +assertSame('0x0000000012345678', $bigint->getHex64()); + $this->assertSame(0x12345678, $bigint->getLow32()); + $this->assertSame(0, $bigint->getHigh32()); + } + + public function testConstructLarge(): void + { + $bigint = new Bigint(0x87654321); + $this->assertSame('0x0000000087654321', $bigint->getHex64()); + $this->assertSame('87654321', bin2hex(pack('N', $bigint->getLow32()))); + $this->assertSame(0, $bigint->getHigh32()); + } + + public function testAddSmallValue(): void + { + $bigint = new Bigint(1); + $bigint = $bigint->add(Bigint::init(2)); + $this->assertSame(3, $bigint->getLow32()); + $this->assertFalse($bigint->isOver32()); + $this->assertTrue($bigint->isOver32(true)); + $this->assertSame($bigint->getLowFF(), (float)$bigint->getLow32()); + $this->assertSame($bigint->getLowFF(true), (float)0xFFFFFFFF); + } + + public function testAddWithOverflowAtLowestByte(): void + { + $bigint = new Bigint(0xFF); + $bigint = $bigint->add(Bigint::init(0x01)); + $this->assertSame(0x100, $bigint->getLow32()); + } + + public function testAddWithOverflowAtInteger32(): void + { + $bigint = new Bigint(0xFFFFFFFE); + $this->assertFalse($bigint->isOver32()); + $bigint = $bigint->add(Bigint::init(0x01)); + $this->assertTrue($bigint->isOver32()); + $bigint = $bigint->add(Bigint::init(0x01)); + $this->assertSame('0x0000000100000000', $bigint->getHex64()); + $this->assertTrue($bigint->isOver32()); + $this->assertSame((float)0xFFFFFFFF, $bigint->getLowFF()); + } + + public function testAddWithOverflowAtInteger64(): void + { + $bigint = Bigint::fromLowHigh(0xFFFFFFFF, 0xFFFFFFFF); + $this->assertSame('0xFFFFFFFFFFFFFFFF', $bigint->getHex64()); + $this->expectException(OverflowException::class); + $bigint->add(Bigint::init(1)); + } +} diff --git a/modules/inpostizi/vendor/maennchen/zipstream-php/test/ZipStreamTest.php b/modules/inpostizi/vendor/maennchen/zipstream-php/test/ZipStreamTest.php new file mode 100644 index 00000000..6549b0b4 --- /dev/null +++ b/modules/inpostizi/vendor/maennchen/zipstream-php/test/ZipStreamTest.php @@ -0,0 +1,586 @@ +expectException(\ZipStream\Exception\FileNotFoundException::class); + // Get ZipStream Object + $zip = new ZipStream(); + + // Trigger error by adding a file which doesn't exist + $zip->addFileFromPath('foobar.php', '/foo/bar/foobar.php'); + } + + public function testFileNotReadableException(): void + { + // create new virtual filesystem + $root = vfsStream::setup('vfs'); + // create a virtual file with no permissions + $file = vfsStream::newFile('foo.txt', 0000)->at($root)->setContent('bar'); + $zip = new ZipStream(); + $this->expectException(\ZipStream\Exception\FileNotReadableException::class); + $zip->addFileFromPath('foo.txt', $file->url()); + } + + public function testDostime(): void + { + // Allows testing of protected method + $class = new \ReflectionClass(File::class); + $method = $class->getMethod('dostime'); + $method->setAccessible(true); + + $this->assertSame($method->invoke(null, 1416246368), 1165069764); + + // January 1 1980 - DOS Epoch. + $this->assertSame($method->invoke(null, 315532800), 2162688); + + // January 1 1970 -> January 1 1980 due to minimum DOS Epoch. @todo Throw Exception? + $this->assertSame($method->invoke(null, 0), 2162688); + } + + public function testAddFile(): void + { + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); + + $zip->addFile('sample.txt', 'Sample String Data'); + $zip->addFile('test/sample.txt', 'More Simple Sample Data'); + + $zip->finish(); + fclose($stream); + + $tmpDir = $this->validateAndExtractZip($tmp); + + $files = $this->getRecursiveFileList($tmpDir); + $this->assertEquals(['sample.txt', 'test/sample.txt'], $files); + + $this->assertStringEqualsFile($tmpDir . '/sample.txt', 'Sample String Data'); + $this->assertStringEqualsFile($tmpDir . '/test/sample.txt', 'More Simple Sample Data'); + } + + /** + * @return array + */ + protected function getTmpFileStream(): array + { + $tmp = tempnam(sys_get_temp_dir(), 'zipstreamtest'); + $stream = fopen($tmp, 'wb+'); + + return array($tmp, $stream); + } + + /** + * @param string $tmp + * @return string + */ + protected function validateAndExtractZip($tmp): string + { + $tmpDir = $this->getTmpDir(); + + $zipArch = new \ZipArchive; + $res = $zipArch->open($tmp); + + if ($res !== true) { + $this->fail("Failed to open {$tmp}. Code: $res"); + + return $tmpDir; + } + + $this->assertEquals(0, $zipArch->status); + $this->assertEquals(0, $zipArch->statusSys); + + $zipArch->extractTo($tmpDir); + $zipArch->close(); + + return $tmpDir; + } + + protected function getTmpDir(): string + { + $tmp = tempnam(sys_get_temp_dir(), 'zipstreamtest'); + unlink($tmp); + mkdir($tmp) or $this->fail('Failed to make directory'); + + return $tmp; + } + + /** + * @param string $path + * @return string[] + */ + protected function getRecursiveFileList(string $path): array + { + $data = array(); + $path = (string)realpath($path); + $files = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path)); + + $pathLen = strlen($path); + foreach ($files as $file) { + $filePath = $file->getRealPath(); + if (!is_dir($filePath)) { + $data[] = substr($filePath, $pathLen + 1); + } + } + + sort($data); + + return $data; + } + + public function testAddFileUtf8NameComment(): void + { + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); + + $name = 'árvíztűrő tükörfúrógép.txt'; + $content = 'Sample String Data'; + $comment = + 'Filename has every special characters ' . + 'from Hungarian language in lowercase. ' . + 'In uppercase: ÁÍŰŐÜÖÚÓÉ'; + + $fileOptions = new FileOptions(); + $fileOptions->setComment($comment); + + $zip->addFile($name, $content, $fileOptions); + $zip->finish(); + fclose($stream); + + $tmpDir = $this->validateAndExtractZip($tmp); + + $files = $this->getRecursiveFileList($tmpDir); + $this->assertEquals(array($name), $files); + $this->assertStringEqualsFile($tmpDir . '/' . $name, $content); + + $zipArch = new \ZipArchive(); + $zipArch->open($tmp); + $this->assertEquals($comment, $zipArch->getCommentName($name)); + } + + public function testAddFileUtf8NameNonUtfComment(): void + { + $this->expectException(\ZipStream\Exception\EncodingException::class); + + $stream = $this->getTmpFileStream()[1]; + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); + + $name = 'á.txt'; + $content = 'any'; + $comment = 'á'; + + $fileOptions = new FileOptions(); + $fileOptions->setComment(mb_convert_encoding($comment, 'ISO-8859-2', 'UTF-8')); + + $zip->addFile($name, $content, $fileOptions); + } + + public function testAddFileNonUtf8NameUtfComment(): void + { + $this->expectException(\ZipStream\Exception\EncodingException::class); + + $stream = $this->getTmpFileStream()[1]; + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); + + $name = 'á.txt'; + $content = 'any'; + $comment = 'á'; + + $fileOptions = new FileOptions(); + $fileOptions->setComment($comment); + + $zip->addFile(mb_convert_encoding($name, 'ISO-8859-2', 'UTF-8'), $content, $fileOptions); + } + + public function testAddFileWithStorageMethod(): void + { + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); + + $fileOptions = new FileOptions(); + $fileOptions->setMethod(Method::STORE()); + + $zip->addFile('sample.txt', 'Sample String Data', $fileOptions); + $zip->addFile('test/sample.txt', 'More Simple Sample Data'); + $zip->finish(); + fclose($stream); + + $zipArch = new \ZipArchive(); + $zipArch->open($tmp); + + $sample1 = $zipArch->statName('sample.txt'); + $sample12 = $zipArch->statName('test/sample.txt'); + $this->assertEquals($sample1['comp_method'], Method::STORE); + $this->assertEquals($sample12['comp_method'], Method::DEFLATE); + + $zipArch->close(); + } + + public function testDecompressFileWithMacUnarchiver(): void + { + if (!file_exists(self::OSX_ARCHIVE_UTILITY)) { + $this->markTestSkipped('The Mac OSX Archive Utility is not available.'); + } + + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); + + $folder = uniqid('', true); + + $zip->addFile($folder . '/sample.txt', 'Sample Data'); + $zip->finish(); + fclose($stream); + + exec(escapeshellarg(self::OSX_ARCHIVE_UTILITY) . ' ' . escapeshellarg($tmp), $output, $returnStatus); + + $this->assertEquals(0, $returnStatus); + $this->assertCount(0, $output); + + $this->assertFileExists(dirname($tmp) . '/' . $folder . '/sample.txt'); + $this->assertStringEqualsFile(dirname($tmp) . '/' . $folder . '/sample.txt', 'Sample Data'); + } + + public function testAddFileFromPath(): void + { + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); + + [$tmpExample, $streamExample] = $this->getTmpFileStream(); + fwrite($streamExample, 'Sample String Data'); + fclose($streamExample); + $zip->addFileFromPath('sample.txt', $tmpExample); + + [$tmpExample, $streamExample] = $this->getTmpFileStream(); + fwrite($streamExample, 'More Simple Sample Data'); + fclose($streamExample); + $zip->addFileFromPath('test/sample.txt', $tmpExample); + + $zip->finish(); + fclose($stream); + + $tmpDir = $this->validateAndExtractZip($tmp); + + $files = $this->getRecursiveFileList($tmpDir); + $this->assertEquals(array('sample.txt', 'test/sample.txt'), $files); + + $this->assertStringEqualsFile($tmpDir . '/sample.txt', 'Sample String Data'); + $this->assertStringEqualsFile($tmpDir . '/test/sample.txt', 'More Simple Sample Data'); + } + + public function testAddFileFromPathWithStorageMethod(): void + { + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); + + $fileOptions = new FileOptions(); + $fileOptions->setMethod(Method::STORE()); + + [$tmpExample, $streamExample] = $this->getTmpFileStream(); + fwrite($streamExample, 'Sample String Data'); + fclose($streamExample); + $zip->addFileFromPath('sample.txt', $tmpExample, $fileOptions); + + [$tmpExample, $streamExample] = $this->getTmpFileStream(); + fwrite($streamExample, 'More Simple Sample Data'); + fclose($streamExample); + $zip->addFileFromPath('test/sample.txt', $tmpExample); + + $zip->finish(); + fclose($stream); + + $zipArch = new \ZipArchive(); + $zipArch->open($tmp); + + $sample1 = $zipArch->statName('sample.txt'); + $this->assertEquals(Method::STORE, $sample1['comp_method']); + + $sample2 = $zipArch->statName('test/sample.txt'); + $this->assertEquals(Method::DEFLATE, $sample2['comp_method']); + + $zipArch->close(); + } + + public function testAddLargeFileFromPath(): void + { + $methods = [Method::DEFLATE(), Method::STORE()]; + $falseTrue = [false, true]; + foreach ($methods as $method) { + foreach ($falseTrue as $zeroHeader) { + foreach ($falseTrue as $zip64) { + if ($zeroHeader && $method->equals(Method::DEFLATE())) { + continue; + } + $this->addLargeFileFileFromPath($method, $zeroHeader, $zip64); + } + } + } + } + + protected function addLargeFileFileFromPath($method, $zeroHeader, $zip64): void + { + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + $options->setLargeFileMethod($method); + $options->setLargeFileSize(5); + $options->setZeroHeader($zeroHeader); + $options->setEnableZip64($zip64); + + $zip = new ZipStream(null, $options); + + [$tmpExample, $streamExample] = $this->getTmpFileStream(); + for ($i = 0; $i <= 10000; $i++) { + fwrite($streamExample, sha1((string)$i)); + if ($i % 100 === 0) { + fwrite($streamExample, "\n"); + } + } + fclose($streamExample); + $shaExample = sha1_file($tmpExample); + $zip->addFileFromPath('sample.txt', $tmpExample); + unlink($tmpExample); + + $zip->finish(); + fclose($stream); + + $tmpDir = $this->validateAndExtractZip($tmp); + + $files = $this->getRecursiveFileList($tmpDir); + $this->assertEquals(array('sample.txt'), $files); + + $this->assertEquals(sha1_file($tmpDir . '/sample.txt'), $shaExample, "SHA-1 Mismatch Method: {$method}"); + } + + public function testAddFileFromStream(): void + { + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); + + // In this test we can't use temporary stream to feed data + // because zlib.deflate filter gives empty string before PHP 7 + // it works fine with file stream + $streamExample = fopen(__FILE__, 'rb'); + $zip->addFileFromStream('sample.txt', $streamExample); +// fclose($streamExample); + + $fileOptions = new FileOptions(); + $fileOptions->setMethod(Method::STORE()); + + $streamExample2 = fopen('php://temp', 'wb+'); + fwrite($streamExample2, 'More Simple Sample Data'); + rewind($streamExample2); // move the pointer back to the beginning of file. + $zip->addFileFromStream('test/sample.txt', $streamExample2, $fileOptions); +// fclose($streamExample2); + + $zip->finish(); + fclose($stream); + + $tmpDir = $this->validateAndExtractZip($tmp); + + $files = $this->getRecursiveFileList($tmpDir); + $this->assertEquals(array('sample.txt', 'test/sample.txt'), $files); + + $this->assertStringEqualsFile(__FILE__, file_get_contents($tmpDir . '/sample.txt')); + $this->assertStringEqualsFile($tmpDir . '/test/sample.txt', 'More Simple Sample Data'); + } + + public function testAddFileFromStreamWithStorageMethod(): void + { + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); + + $fileOptions = new FileOptions(); + $fileOptions->setMethod(Method::STORE()); + + $streamExample = fopen('php://temp', 'wb+'); + fwrite($streamExample, 'Sample String Data'); + rewind($streamExample); // move the pointer back to the beginning of file. + $zip->addFileFromStream('sample.txt', $streamExample, $fileOptions); +// fclose($streamExample); + + $streamExample2 = fopen('php://temp', 'bw+'); + fwrite($streamExample2, 'More Simple Sample Data'); + rewind($streamExample2); // move the pointer back to the beginning of file. + $zip->addFileFromStream('test/sample.txt', $streamExample2); +// fclose($streamExample2); + + $zip->finish(); + fclose($stream); + + $zipArch = new \ZipArchive(); + $zipArch->open($tmp); + + $sample1 = $zipArch->statName('sample.txt'); + $this->assertEquals(Method::STORE, $sample1['comp_method']); + + $sample2 = $zipArch->statName('test/sample.txt'); + $this->assertEquals(Method::DEFLATE, $sample2['comp_method']); + + $zipArch->close(); + } + + public function testAddFileFromPsr7Stream(): void + { + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); + + $body = 'Sample String Data'; + $response = new Response(200, [], $body); + + $fileOptions = new FileOptions(); + $fileOptions->setMethod(Method::STORE()); + + $zip->addFileFromPsr7Stream('sample.json', $response->getBody(), $fileOptions); + $zip->finish(); + fclose($stream); + + $tmpDir = $this->validateAndExtractZip($tmp); + + $files = $this->getRecursiveFileList($tmpDir); + $this->assertEquals(array('sample.json'), $files); + $this->assertStringEqualsFile($tmpDir . '/sample.json', $body); + } + + public function testAddFileFromPsr7StreamWithFileSizeSet(): void + { + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); + + $body = 'Sample String Data'; + $fileSize = strlen($body); + // Add fake padding + $fakePadding = "\0\0\0\0\0\0"; + $response = new Response(200, [], $body . $fakePadding); + + $fileOptions = new FileOptions(); + $fileOptions->setMethod(Method::STORE()); + $fileOptions->setSize($fileSize); + $zip->addFileFromPsr7Stream('sample.json', $response->getBody(), $fileOptions); + $zip->finish(); + fclose($stream); + + $tmpDir = $this->validateAndExtractZip($tmp); + + $files = $this->getRecursiveFileList($tmpDir); + $this->assertEquals(array('sample.json'), $files); + $this->assertStringEqualsFile($tmpDir . '/sample.json', $body); + } + + public function testCreateArchiveWithFlushOptionSet(): void + { + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + $options->setFlushOutput(true); + + $zip = new ZipStream(null, $options); + + $zip->addFile('sample.txt', 'Sample String Data'); + $zip->addFile('test/sample.txt', 'More Simple Sample Data'); + + $zip->finish(); + fclose($stream); + + $tmpDir = $this->validateAndExtractZip($tmp); + + $files = $this->getRecursiveFileList($tmpDir); + $this->assertEquals(['sample.txt', 'test/sample.txt'], $files); + + $this->assertStringEqualsFile($tmpDir . '/sample.txt', 'Sample String Data'); + $this->assertStringEqualsFile($tmpDir . '/test/sample.txt', 'More Simple Sample Data'); + } + + public function testCreateArchiveWithOutputBufferingOffAndFlushOptionSet(): void + { + // WORKAROUND (1/2): remove phpunit's output buffer in order to run test without any buffering + ob_end_flush(); + $this->assertEquals(0, ob_get_level()); + + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + $options->setFlushOutput(true); + + $zip = new ZipStream(null, $options); + + $zip->addFile('sample.txt', 'Sample String Data'); + + $zip->finish(); + fclose($stream); + + $tmpDir = $this->validateAndExtractZip($tmp); + $this->assertStringEqualsFile($tmpDir . '/sample.txt', 'Sample String Data'); + + // WORKAROUND (2/2): add back output buffering so that PHPUnit doesn't complain that it is missing + ob_start(); + } +} diff --git a/modules/inpostizi/vendor/maennchen/zipstream-php/test/bootstrap.php b/modules/inpostizi/vendor/maennchen/zipstream-php/test/bootstrap.php new file mode 100644 index 00000000..b43c9bd6 --- /dev/null +++ b/modules/inpostizi/vendor/maennchen/zipstream-php/test/bootstrap.php @@ -0,0 +1,6 @@ +setOutputStream(fopen('php://memory', 'wb')); + $fileOpt->setTime(clone $expectedTime); + + $zip = new ZipStream(null, $archiveOpt); + + $zip->addFile('sample.txt', 'Sample', $fileOpt); + + $zip->finish(); + + $this->assertEquals($expectedTime, $fileOpt->getTime()); + } +} diff --git a/modules/inpostizi/vendor/myclabs/php-enum/LICENSE b/modules/inpostizi/vendor/myclabs/php-enum/LICENSE new file mode 100644 index 00000000..2a8cf22e --- /dev/null +++ b/modules/inpostizi/vendor/myclabs/php-enum/LICENSE @@ -0,0 +1,18 @@ +The MIT License (MIT) + +Copyright (c) 2015 My C-Labs + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/modules/inpostizi/vendor/myclabs/php-enum/README.md b/modules/inpostizi/vendor/myclabs/php-enum/README.md new file mode 100644 index 00000000..bf1c91dd --- /dev/null +++ b/modules/inpostizi/vendor/myclabs/php-enum/README.md @@ -0,0 +1,134 @@ +# PHP Enum implementation inspired from SplEnum + +[![Build Status](https://travis-ci.org/myclabs/php-enum.png?branch=master)](https://travis-ci.org/myclabs/php-enum) +[![Latest Stable Version](https://poser.pugx.org/myclabs/php-enum/version.png)](https://packagist.org/packages/myclabs/php-enum) +[![Total Downloads](https://poser.pugx.org/myclabs/php-enum/downloads.png)](https://packagist.org/packages/myclabs/php-enum) +[![psalm](https://shepherd.dev/github/myclabs/php-enum/coverage.svg)](https://shepherd.dev/github/myclabs/php-enum) + +Maintenance for this project is [supported via Tidelift](https://tidelift.com/subscription/pkg/packagist-myclabs-php-enum?utm_source=packagist-myclabs-php-enum&utm_medium=referral&utm_campaign=readme). + +## Why? + +First, and mainly, `SplEnum` is not integrated to PHP, you have to install the extension separately. + +Using an enum instead of class constants provides the following advantages: + +- You can use an enum as a parameter type: `function setAction(Action $action) {` +- You can use an enum as a return type: `function getAction() : Action {` +- You can enrich the enum with methods (e.g. `format`, `parse`, …) +- You can extend the enum to add new values (make your enum `final` to prevent it) +- You can get a list of all the possible values (see below) + +This Enum class is not intended to replace class constants, but only to be used when it makes sense. + +## Installation + +``` +composer require myclabs/php-enum +``` + +## Declaration + +```php +use MyCLabs\Enum\Enum; + +/** + * Action enum + */ +class Action extends Enum +{ + private const VIEW = 'view'; + private const EDIT = 'edit'; +} +``` + +## Usage + +```php +$action = Action::VIEW(); + +// or with a dynamic key: +$action = Action::$key(); +// or with a dynamic value: +$action = new Action($value); +``` + +As you can see, static methods are automatically implemented to provide quick access to an enum value. + +One advantage over using class constants is to be able to use an enum as a parameter type: + +```php +function setAction(Action $action) { + // ... +} +``` + +## Documentation + +- `__construct()` The constructor checks that the value exist in the enum +- `__toString()` You can `echo $myValue`, it will display the enum value (value of the constant) +- `getValue()` Returns the current value of the enum +- `getKey()` Returns the key of the current value on Enum +- `equals()` Tests whether enum instances are equal (returns `true` if enum values are equal, `false` otherwise) + +Static methods: + +- `toArray()` method Returns all possible values as an array (constant name in key, constant value in value) +- `keys()` Returns the names (keys) of all constants in the Enum class +- `values()` Returns instances of the Enum class of all Enum constants (constant name in key, Enum instance in value) +- `isValid()` Check if tested value is valid on enum set +- `isValidKey()` Check if tested key is valid on enum set +- `search()` Return key for searched value + +### Static methods + +```php +class Action extends Enum +{ + private const VIEW = 'view'; + private const EDIT = 'edit'; +} + +// Static method: +$action = Action::VIEW(); +$action = Action::EDIT(); +``` + +Static method helpers are implemented using [`__callStatic()`](http://www.php.net/manual/en/language.oop5.overloading.php#object.callstatic). + +If you care about IDE autocompletion, you can either implement the static methods yourself: + +```php +class Action extends Enum +{ + private const VIEW = 'view'; + + /** + * @return Action + */ + public static function VIEW() { + return new Action(self::VIEW); + } +} +``` + +or you can use phpdoc (this is supported in PhpStorm for example): + +```php +/** + * @method static Action VIEW() + * @method static Action EDIT() + */ +class Action extends Enum +{ + private const VIEW = 'view'; + private const EDIT = 'edit'; +} +``` + +## Related projects + +- [Doctrine enum mapping](https://github.com/acelaya/doctrine-enum-type) +- [Symfony ParamConverter integration](https://github.com/Ex3v/MyCLabsEnumParamConverter) +- [PHPStan integration](https://github.com/timeweb/phpstan-enum) +- [Yii2 enum mapping](https://github.com/KartaviK/yii2-enum) diff --git a/modules/inpostizi/vendor/myclabs/php-enum/SECURITY.md b/modules/inpostizi/vendor/myclabs/php-enum/SECURITY.md new file mode 100644 index 00000000..84fd4e32 --- /dev/null +++ b/modules/inpostizi/vendor/myclabs/php-enum/SECURITY.md @@ -0,0 +1,11 @@ +# Security Policy + +## Supported Versions + +Only the latest stable release is supported. + +## Reporting a Vulnerability + +To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). + +Tidelift will coordinate the fix and disclosure. diff --git a/modules/inpostizi/vendor/myclabs/php-enum/composer.json b/modules/inpostizi/vendor/myclabs/php-enum/composer.json new file mode 100644 index 00000000..6861a5ce --- /dev/null +++ b/modules/inpostizi/vendor/myclabs/php-enum/composer.json @@ -0,0 +1,33 @@ +{ + "name": "myclabs/php-enum", + "type": "library", + "description": "PHP Enum implementation", + "keywords": ["enum"], + "homepage": "http://github.com/myclabs/php-enum", + "license": "MIT", + "authors": [ + { + "name": "PHP Enum contributors", + "homepage": "https://github.com/myclabs/php-enum/graphs/contributors" + } + ], + "autoload": { + "psr-4": { + "MyCLabs\\Enum\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "MyCLabs\\Tests\\Enum\\": "tests/" + } + }, + "require": { + "php": ">=7.1", + "ext-json": "*" + }, + "require-dev": { + "phpunit/phpunit": "^7", + "squizlabs/php_codesniffer": "1.*", + "vimeo/psalm": "^3.8" + } +} diff --git a/modules/inpostizi/vendor/myclabs/php-enum/psalm.xml b/modules/inpostizi/vendor/myclabs/php-enum/psalm.xml new file mode 100644 index 00000000..b07e9294 --- /dev/null +++ b/modules/inpostizi/vendor/myclabs/php-enum/psalm.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + diff --git a/modules/inpostizi/vendor/myclabs/php-enum/src/Enum.php b/modules/inpostizi/vendor/myclabs/php-enum/src/Enum.php new file mode 100644 index 00000000..b8b93277 --- /dev/null +++ b/modules/inpostizi/vendor/myclabs/php-enum/src/Enum.php @@ -0,0 +1,250 @@ + + * @author Daniel Costa + * @author Mirosław Filip + * + * @psalm-template T + * @psalm-immutable + */ +abstract class Enum implements \JsonSerializable +{ + /** + * Enum value + * + * @var mixed + * @psalm-var T + */ + protected $value; + + /** + * Store existing constants in a static cache per object. + * + * + * @var array + * @psalm-var array> + */ + protected static $cache = []; + + /** + * Cache of instances of the Enum class + * + * @var array + * @psalm-var array> + */ + protected static $instances = []; + + /** + * Creates a new value of some type + * + * @psalm-pure + * @param mixed $value + * + * @psalm-param static|T $value + * @throws \UnexpectedValueException if incompatible type is given. + */ + public function __construct($value) + { + if ($value instanceof static) { + /** @psalm-var T */ + $value = $value->getValue(); + } + + if (!$this->isValid($value)) { + /** @psalm-suppress InvalidCast */ + throw new \UnexpectedValueException("Value '$value' is not part of the enum " . static::class); + } + + /** @psalm-var T */ + $this->value = $value; + } + + /** + * @psalm-pure + * @return mixed + * @psalm-return T + */ + public function getValue() + { + return $this->value; + } + + /** + * Returns the enum key (i.e. the constant name). + * + * @psalm-pure + * @return mixed + */ + public function getKey() + { + return static::search($this->value); + } + + /** + * @psalm-pure + * @psalm-suppress InvalidCast + * @return string + */ + public function __toString() + { + return (string)$this->value; + } + + /** + * Determines if Enum should be considered equal with the variable passed as a parameter. + * Returns false if an argument is an object of different class or not an object. + * + * This method is final, for more information read https://github.com/myclabs/php-enum/issues/4 + * + * @psalm-pure + * @psalm-param mixed $variable + * @return bool + */ + final public function equals($variable = null): bool + { + return $variable instanceof self + && $this->getValue() === $variable->getValue() + && static::class === \get_class($variable); + } + + /** + * Returns the names (keys) of all constants in the Enum class + * + * @psalm-pure + * @psalm-return list + * @return array + */ + public static function keys() + { + return \array_keys(static::toArray()); + } + + /** + * Returns instances of the Enum class of all Enum constants + * + * @psalm-pure + * @psalm-return array + * @return static[] Constant name in key, Enum instance in value + */ + public static function values() + { + $values = array(); + + /** @psalm-var T $value */ + foreach (static::toArray() as $key => $value) { + $values[$key] = new static($value); + } + + return $values; + } + + /** + * Returns all possible values as an array + * + * @psalm-pure + * @psalm-suppress ImpureStaticProperty + * + * @psalm-return array + * @return array Constant name in key, constant value in value + */ + public static function toArray() + { + $class = static::class; + + if (!isset(static::$cache[$class])) { + $reflection = new \ReflectionClass($class); + static::$cache[$class] = $reflection->getConstants(); + } + + return static::$cache[$class]; + } + + /** + * Check if is valid enum value + * + * @param $value + * @psalm-param mixed $value + * @psalm-pure + * @return bool + */ + public static function isValid($value) + { + return \in_array($value, static::toArray(), true); + } + + /** + * Check if is valid enum key + * + * @param $key + * @psalm-param string $key + * @psalm-pure + * @return bool + */ + public static function isValidKey($key) + { + $array = static::toArray(); + + return isset($array[$key]) || \array_key_exists($key, $array); + } + + /** + * Return key for value + * + * @param $value + * + * @psalm-param mixed $value + * @psalm-pure + * @return mixed + */ + public static function search($value) + { + return \array_search($value, static::toArray(), true); + } + + /** + * Returns a value when called statically like so: MyEnum::SOME_VALUE() given SOME_VALUE is a class constant + * + * @param string $name + * @param array $arguments + * + * @return static + * @throws \BadMethodCallException + */ + public static function __callStatic($name, $arguments) + { + $class = static::class; + if (!isset(self::$instances[$class][$name])) { + $array = static::toArray(); + if (!isset($array[$name]) && !\array_key_exists($name, $array)) { + $message = "No static method or enum constant '$name' in class " . static::class; + throw new \BadMethodCallException($message); + } + return self::$instances[$class][$name] = new static($array[$name]); + } + return clone self::$instances[$class][$name]; + } + + /** + * Specify data which should be serialized to JSON. This method returns data that can be serialized by json_encode() + * natively. + * + * @return mixed + * @link http://php.net/manual/en/jsonserializable.jsonserialize.php + * @psalm-pure + */ + public function jsonSerialize() + { + return $this->getValue(); + } +} diff --git a/modules/inpostizi/vendor/myclabs/php-enum/src/PHPUnit/Comparator.php b/modules/inpostizi/vendor/myclabs/php-enum/src/PHPUnit/Comparator.php new file mode 100644 index 00000000..302bf80e --- /dev/null +++ b/modules/inpostizi/vendor/myclabs/php-enum/src/PHPUnit/Comparator.php @@ -0,0 +1,54 @@ +register(new \MyCLabs\Enum\PHPUnit\Comparator()); + */ +final class Comparator extends \SebastianBergmann\Comparator\Comparator +{ + public function accepts($expected, $actual) + { + return $expected instanceof Enum && ( + $actual instanceof Enum || $actual === null + ); + } + + /** + * @param Enum $expected + * @param Enum|null $actual + * + * @return void + */ + public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false) + { + if ($expected->equals($actual)) { + return; + } + + throw new ComparisonFailure( + $expected, + $actual, + $this->formatEnum($expected), + $this->formatEnum($actual), + false, + 'Failed asserting that two Enums are equal.' + ); + } + + private function formatEnum(Enum $enum = null) + { + if ($enum === null) { + return "null"; + } + + return get_class($enum)."::{$enum->getKey()}()"; + } +} diff --git a/modules/inpostizi/vendor/nyholm/psr7/.php-cs-fixer.dist.php b/modules/inpostizi/vendor/nyholm/psr7/.php-cs-fixer.dist.php new file mode 100644 index 00000000..04765dec --- /dev/null +++ b/modules/inpostizi/vendor/nyholm/psr7/.php-cs-fixer.dist.php @@ -0,0 +1,25 @@ +in(__DIR__.'/src') + ->in(__DIR__.'/tests'); + +$config = new PhpCsFixer\Config(); + +return $config->setRules([ + '@Symfony' => true, + '@Symfony:risky' => true, + 'native_function_invocation' => ['include'=> ['@all']], + 'native_constant_invocation' => true, + 'ordered_imports' => true, + 'declare_strict_types' => false, + 'linebreak_after_opening_tag' => false, + 'single_import_per_statement' => false, + 'blank_line_after_opening_tag' => false, + 'concat_space' => ['spacing'=>'one'], + 'phpdoc_align' => ['align'=>'left'], +]) + ->setRiskyAllowed(true) + ->setFinder($finder); diff --git a/modules/inpostizi/vendor/nyholm/psr7/CHANGELOG.md b/modules/inpostizi/vendor/nyholm/psr7/CHANGELOG.md new file mode 100644 index 00000000..fecd1b91 --- /dev/null +++ b/modules/inpostizi/vendor/nyholm/psr7/CHANGELOG.md @@ -0,0 +1,153 @@ +# Changelog + +All notable changes to this project will be documented in this file, in reverse chronological order by release. + +## 1.6.1 + +- Security fix: CVE-2023-29197 + +## 1.6.0 + +### Changed + +- Seek to the begining of the string when using Stream::create() +- Populate ServerRequest::getQueryParams() on instantiation +- Encode [reserved characters](https://www.rfc-editor.org/rfc/rfc3986#appendix-A) in userinfo in Uri +- Normalize leading slashes for Uri::getPath() +- Make Stream's constructor public +- Add some missing type checks on arguments + +## 1.5.1 + +### Fixed + +- Fixed deprecations on PHP 8.1 + +## 1.5.0 + +### Added + +- Add explicit `@return mixed` +- Add explicit return types to HttplugFactory + +### Fixed + +- Improve error handling with streams + +## 1.4.1 + +### Fixed + +- `Psr17Factory::createStreamFromFile`, `UploadedFile::moveTo`, and + `UploadedFile::getStream` no longer throw `ValueError` in PHP 8. + +## 1.4.0 + +### Removed + +The `final` keyword was replaced by `@final` annotation. + +## 1.3.2 + +### Fixed + +- `Stream::read()` must not return boolean. +- Improved exception message when using wrong HTTP status code. + +## 1.3.1 + +### Fixed + +- Allow installation on PHP8 + +## 1.3.0 + +### Added + +- Make Stream::__toString() compatible with throwing exceptions on PHP 7.4. + +### Fixed + +- Support for UTF-8 hostnames +- Support for numeric header names + +## 1.2.1 + +### Changed + +- Added `.github` and `phpstan.neon.dist` to `.gitattributes`. + +## 1.2.0 + +### Changed + +- Change minimal port number to 0 (unix socket) +- Updated `Psr17Factory::createResponse` to respect the specification. If second + argument is not used, a standard reason phrase. If an empty string is passed, + then the reason phrase will be empty. + +### Fixed + +- Check for seekable on the stream resource. +- Fixed the `Response::$reason` should never be null. + +## 1.1.0 + +### Added + +- Improved performance +- More tests for `UploadedFile` and `HttplugFactory` + +### Removed + +- Dead code + +## 1.0.1 + +### Fixed + +- Handle `fopen` failing in createStreamFromFile according to PSR-7. +- Reduce execution path to speed up performance. +- Fixed typos. +- Code style. + +## 1.0.0 + +### Added + +- Support for final PSR-17 (HTTP factories). (`Psr17Factory`) +- Support for numeric header values. +- Support for empty header values. +- All classes are final +- `HttplugFactory` that implements factory interfaces from HTTPlug. + +### Changed + +- `ServerRequest` does not extend `Request`. + +### Removed + +- The HTTPlug discovery strategy was removed since it is included in php-http/discovery 1.4. +- `UploadedFileFactory()` was removed in favor for `Psr17Factory`. +- `ServerRequestFactory()` was removed in favor for `Psr17Factory`. +- `StreamFactory`, `UriFactory`, abd `MessageFactory`. Use `HttplugFactory` instead. +- `ServerRequestFactory::createServerRequestFromArray`, `ServerRequestFactory::createServerRequestFromArrays` and + `ServerRequestFactory::createServerRequestFromGlobals`. Please use the new `nyholm/psr7-server` instead. + +## 0.3.0 + +### Added + +- Return types. +- Many `InvalidArgumentException`s are thrown when you use invalid arguments. +- Integration tests for `UploadedFile` and `ServerRequest`. + +### Changed + +- We dropped PHP7.0 support. +- PSR-17 factories have been marked as internal. They do not fall under our BC promise until PSR-17 is accepted. +- `UploadedFileFactory::createUploadedFile` does not accept a string file path. + +## 0.2.3 + +No changelog before this release diff --git a/modules/inpostizi/vendor/nyholm/psr7/LICENSE b/modules/inpostizi/vendor/nyholm/psr7/LICENSE new file mode 100644 index 00000000..d6c52312 --- /dev/null +++ b/modules/inpostizi/vendor/nyholm/psr7/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/modules/inpostizi/vendor/nyholm/psr7/README.md b/modules/inpostizi/vendor/nyholm/psr7/README.md new file mode 100644 index 00000000..0ca26717 --- /dev/null +++ b/modules/inpostizi/vendor/nyholm/psr7/README.md @@ -0,0 +1,107 @@ +# PSR-7 implementation + +[![Latest Version](https://img.shields.io/github/release/Nyholm/psr7.svg?style=flat-square)](https://github.com/Nyholm/psr7/releases) +[![Total Downloads](https://poser.pugx.org/nyholm/psr7/downloads)](https://packagist.org/packages/nyholm/psr7) +[![Monthly Downloads](https://poser.pugx.org/nyholm/psr7/d/monthly.png)](https://packagist.org/packages/nyholm/psr7) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) + + +A super lightweight PSR-7 implementation. Very strict and very fast. + +| Description | Guzzle | Laminas | Slim | Nyholm | +| ---- | ------ | ---- | ---- | ------ | +| Lines of code | 3.300 | 3.100 | 1.900 | 1.000 | +| PSR-7* | 66% | 100% | 75% | 100% | +| PSR-17 | No | Yes | Yes | Yes | +| HTTPlug | No | No | No | Yes | +| Performance (runs per second)** | 14.553 | 14.703 | 13.416 | 17.734 | + +\* Percent of completed tests in https://github.com/php-http/psr7-integration-tests + +\** Benchmark with 50.000 runs. See https://github.com/devanych/psr-http-benchmark (higher is better) + +## Installation + +```bash +composer require nyholm/psr7 +``` + +If you are using Symfony Flex then you get all message factories registered as services. + +## Usage + +The PSR-7 objects do not contain any other public methods than those defined in +the [PSR-7 specification](https://www.php-fig.org/psr/psr-7/). + +### Create objects + +Use the PSR-17 factory to create requests, streams, URIs etc. + +```php +$psr17Factory = new \Nyholm\Psr7\Factory\Psr17Factory(); +$request = $psr17Factory->createRequest('GET', 'http://tnyholm.se'); +$stream = $psr17Factory->createStream('foobar'); +``` + +### Sending a request + +With [HTTPlug](http://httplug.io/) or any other PSR-18 (HTTP client) you may send +requests like: + +```bash +composer require kriswallsmith/buzz +``` + +```php +$psr17Factory = new \Nyholm\Psr7\Factory\Psr17Factory(); +$psr18Client = new \Buzz\Client\Curl($psr17Factory); + +$request = $psr17Factory->createRequest('GET', 'http://tnyholm.se'); +$response = $psr18Client->sendRequest($request); +``` + +### Create server requests + +The [`nyholm/psr7-server`](https://github.com/Nyholm/psr7-server) package can be used +to create server requests from PHP superglobals. + +```bash +composer require nyholm/psr7-server +``` + +```php +$psr17Factory = new \Nyholm\Psr7\Factory\Psr17Factory(); + +$creator = new \Nyholm\Psr7Server\ServerRequestCreator( + $psr17Factory, // ServerRequestFactory + $psr17Factory, // UriFactory + $psr17Factory, // UploadedFileFactory + $psr17Factory // StreamFactory +); + +$serverRequest = $creator->fromGlobals(); +``` + +### Emitting a response + +```bash +composer require laminas/laminas-httphandlerrunner +``` + +```php +$psr17Factory = new \Nyholm\Psr7\Factory\Psr17Factory(); + +$responseBody = $psr17Factory->createStream('Hello world'); +$response = $psr17Factory->createResponse(200)->withBody($responseBody); +(new \Laminas\HttpHandlerRunner\Emitter\SapiEmitter())->emit($response); +``` + +## Our goal + +This package is currently maintained by [Tobias Nyholm](http://nyholm.se) and +[Martijn van der Ven](https://vanderven.se/martijn/). They have decided that the +goal of this library should be to provide a super strict implementation of +[PSR-7](https://www.php-fig.org/psr/psr-7/) that is blazing fast. + +The package will never include any extra features nor helper methods. All our classes +and functions exist because they are required to fulfill the PSR-7 specification. diff --git a/modules/inpostizi/vendor/nyholm/psr7/composer.json b/modules/inpostizi/vendor/nyholm/psr7/composer.json new file mode 100644 index 00000000..680459c6 --- /dev/null +++ b/modules/inpostizi/vendor/nyholm/psr7/composer.json @@ -0,0 +1,49 @@ +{ + "name": "nyholm/psr7", + "description": "A fast PHP7 implementation of PSR-7", + "license": "MIT", + "keywords": ["psr-7", "psr-17"], + "homepage": "https://tnyholm.se", + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + }, + { + "name": "Martijn van der Ven", + "email": "martijn@vanderven.se" + } + ], + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0", + "php-http/message-factory": "^1.0", + "psr/http-factory": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || 8.5 || 9.4", + "php-http/psr7-integration-tests": "^1.0", + "http-interop/http-factory-tests": "^0.9", + "symfony/error-handler": "^4.4" + }, + "provide": { + "php-http/message-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0", + "psr/http-factory-implementation": "1.0" + }, + "autoload": { + "psr-4": { + "Nyholm\\Psr7\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Tests\\Nyholm\\Psr7\\": "tests/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.6-dev" + } + } +} diff --git a/modules/inpostizi/vendor/nyholm/psr7/phpstan-baseline.neon b/modules/inpostizi/vendor/nyholm/psr7/phpstan-baseline.neon new file mode 100644 index 00000000..80991e80 --- /dev/null +++ b/modules/inpostizi/vendor/nyholm/psr7/phpstan-baseline.neon @@ -0,0 +1,41 @@ +parameters: + ignoreErrors: + - + message: "#^Result of && is always false\\.$#" + count: 1 + path: src/Response.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and string will always evaluate to false\\.$#" + count: 1 + path: src/Response.php + + - + message: "#^Result of && is always false\\.$#" + count: 1 + path: src/ServerRequest.php + + - + message: "#^Strict comparison using \\!\\=\\= between null and null will always evaluate to false\\.$#" + count: 1 + path: src/ServerRequest.php + + - + message: "#^Parameter \\#1 \\$callback of function set_error_handler expects \\(callable\\(int, string, string, int\\)\\: bool\\)\\|null, 'var_dump' given\\.$#" + count: 1 + path: src/Stream.php + + - + message: "#^Result of && is always false\\.$#" + count: 1 + path: src/Stream.php + + - + message: "#^Result of && is always false\\.$#" + count: 2 + path: src/UploadedFile.php + + - + message: "#^Strict comparison using \\=\\=\\= between false and true will always evaluate to false\\.$#" + count: 2 + path: src/UploadedFile.php diff --git a/modules/inpostizi/vendor/nyholm/psr7/psalm.baseline.xml b/modules/inpostizi/vendor/nyholm/psr7/psalm.baseline.xml new file mode 100644 index 00000000..fe5b92e7 --- /dev/null +++ b/modules/inpostizi/vendor/nyholm/psr7/psalm.baseline.xml @@ -0,0 +1,8 @@ + + + + + return \trigger_error((string) $e, \E_USER_ERROR); + + + diff --git a/modules/inpostizi/vendor/nyholm/psr7/src/Factory/HttplugFactory.php b/modules/inpostizi/vendor/nyholm/psr7/src/Factory/HttplugFactory.php new file mode 100644 index 00000000..4cf8e27e --- /dev/null +++ b/modules/inpostizi/vendor/nyholm/psr7/src/Factory/HttplugFactory.php @@ -0,0 +1,45 @@ + + * @author Martijn van der Ven + * + * @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md + */ +class HttplugFactory implements MessageFactory, StreamFactory, UriFactory +{ + public function createRequest($method, $uri, array $headers = [], $body = null, $protocolVersion = '1.1'): RequestInterface + { + return new Request($method, $uri, $headers, $body, $protocolVersion); + } + + public function createResponse($statusCode = 200, $reasonPhrase = null, array $headers = [], $body = null, $version = '1.1'): ResponseInterface + { + return new Response((int) $statusCode, $headers, $body, $version, $reasonPhrase); + } + + public function createStream($body = null): StreamInterface + { + return Stream::create($body ?? ''); + } + + public function createUri($uri = ''): UriInterface + { + if ($uri instanceof UriInterface) { + return $uri; + } + + return new Uri($uri); + } +} diff --git a/modules/inpostizi/vendor/nyholm/psr7/src/Factory/Psr17Factory.php b/modules/inpostizi/vendor/nyholm/psr7/src/Factory/Psr17Factory.php new file mode 100644 index 00000000..440bec34 --- /dev/null +++ b/modules/inpostizi/vendor/nyholm/psr7/src/Factory/Psr17Factory.php @@ -0,0 +1,78 @@ + + * @author Martijn van der Ven + * + * @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md + */ +class Psr17Factory implements RequestFactoryInterface, ResponseFactoryInterface, ServerRequestFactoryInterface, StreamFactoryInterface, UploadedFileFactoryInterface, UriFactoryInterface +{ + public function createRequest(string $method, $uri): RequestInterface + { + return new Request($method, $uri); + } + + public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface + { + if (2 > \func_num_args()) { + // This will make the Response class to use a custom reasonPhrase + $reasonPhrase = null; + } + + return new Response($code, [], null, '1.1', $reasonPhrase); + } + + public function createStream(string $content = ''): StreamInterface + { + return Stream::create($content); + } + + public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface + { + if ('' === $filename) { + throw new \RuntimeException('Path cannot be empty'); + } + + if (false === $resource = @\fopen($filename, $mode)) { + if ('' === $mode || false === \in_array($mode[0], ['r', 'w', 'a', 'x', 'c'], true)) { + throw new \InvalidArgumentException(\sprintf('The mode "%s" is invalid.', $mode)); + } + + throw new \RuntimeException(\sprintf('The file "%s" cannot be opened: %s', $filename, \error_get_last()['message'] ?? '')); + } + + return Stream::create($resource); + } + + public function createStreamFromResource($resource): StreamInterface + { + return Stream::create($resource); + } + + public function createUploadedFile(StreamInterface $stream, int $size = null, int $error = \UPLOAD_ERR_OK, string $clientFilename = null, string $clientMediaType = null): UploadedFileInterface + { + if (null === $size) { + $size = $stream->getSize(); + } + + return new UploadedFile($stream, $size, $error, $clientFilename, $clientMediaType); + } + + public function createUri(string $uri = ''): UriInterface + { + return new Uri($uri); + } + + public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface + { + return new ServerRequest($method, $uri, [], null, '1.1', $serverParams); + } +} diff --git a/modules/inpostizi/vendor/nyholm/psr7/src/MessageTrait.php b/modules/inpostizi/vendor/nyholm/psr7/src/MessageTrait.php new file mode 100644 index 00000000..da34e58b --- /dev/null +++ b/modules/inpostizi/vendor/nyholm/psr7/src/MessageTrait.php @@ -0,0 +1,219 @@ + + * @author Martijn van der Ven + * + * @internal should not be used outside of Nyholm/Psr7 as it does not fall under our BC promise + */ +trait MessageTrait +{ + /** @var array Map of all registered headers, as original name => array of values */ + private $headers = []; + + /** @var array Map of lowercase header name => original name at registration */ + private $headerNames = []; + + /** @var string */ + private $protocol = '1.1'; + + /** @var StreamInterface|null */ + private $stream; + + public function getProtocolVersion(): string + { + return $this->protocol; + } + + public function withProtocolVersion($version): self + { + if (!\is_scalar($version)) { + throw new \InvalidArgumentException('Protocol version must be a string'); + } + + if ($this->protocol === $version) { + return $this; + } + + $new = clone $this; + $new->protocol = (string) $version; + + return $new; + } + + public function getHeaders(): array + { + return $this->headers; + } + + public function hasHeader($header): bool + { + return isset($this->headerNames[\strtr($header, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')]); + } + + public function getHeader($header): array + { + if (!\is_string($header)) { + throw new \InvalidArgumentException('Header name must be an RFC 7230 compatible string'); + } + + $header = \strtr($header, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + if (!isset($this->headerNames[$header])) { + return []; + } + + $header = $this->headerNames[$header]; + + return $this->headers[$header]; + } + + public function getHeaderLine($header): string + { + return \implode(', ', $this->getHeader($header)); + } + + public function withHeader($header, $value): self + { + $value = $this->validateAndTrimHeader($header, $value); + $normalized = \strtr($header, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + + $new = clone $this; + if (isset($new->headerNames[$normalized])) { + unset($new->headers[$new->headerNames[$normalized]]); + } + $new->headerNames[$normalized] = $header; + $new->headers[$header] = $value; + + return $new; + } + + public function withAddedHeader($header, $value): self + { + if (!\is_string($header) || '' === $header) { + throw new \InvalidArgumentException('Header name must be an RFC 7230 compatible string'); + } + + $new = clone $this; + $new->setHeaders([$header => $value]); + + return $new; + } + + public function withoutHeader($header): self + { + if (!\is_string($header)) { + throw new \InvalidArgumentException('Header name must be an RFC 7230 compatible string'); + } + + $normalized = \strtr($header, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + if (!isset($this->headerNames[$normalized])) { + return $this; + } + + $header = $this->headerNames[$normalized]; + $new = clone $this; + unset($new->headers[$header], $new->headerNames[$normalized]); + + return $new; + } + + public function getBody(): StreamInterface + { + if (null === $this->stream) { + $this->stream = Stream::create(''); + } + + return $this->stream; + } + + public function withBody(StreamInterface $body): self + { + if ($body === $this->stream) { + return $this; + } + + $new = clone $this; + $new->stream = $body; + + return $new; + } + + private function setHeaders(array $headers): void + { + foreach ($headers as $header => $value) { + if (\is_int($header)) { + // If a header name was set to a numeric string, PHP will cast the key to an int. + // We must cast it back to a string in order to comply with validation. + $header = (string) $header; + } + $value = $this->validateAndTrimHeader($header, $value); + $normalized = \strtr($header, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + if (isset($this->headerNames[$normalized])) { + $header = $this->headerNames[$normalized]; + $this->headers[$header] = \array_merge($this->headers[$header], $value); + } else { + $this->headerNames[$normalized] = $header; + $this->headers[$header] = $value; + } + } + } + + /** + * Make sure the header complies with RFC 7230. + * + * Header names must be a non-empty string consisting of token characters. + * + * Header values must be strings consisting of visible characters with all optional + * leading and trailing whitespace stripped. This method will always strip such + * optional whitespace. Note that the method does not allow folding whitespace within + * the values as this was deprecated for almost all instances by the RFC. + * + * header-field = field-name ":" OWS field-value OWS + * field-name = 1*( "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / "^" + * / "_" / "`" / "|" / "~" / %x30-39 / ( %x41-5A / %x61-7A ) ) + * OWS = *( SP / HTAB ) + * field-value = *( ( %x21-7E / %x80-FF ) [ 1*( SP / HTAB ) ( %x21-7E / %x80-FF ) ] ) + * + * @see https://tools.ietf.org/html/rfc7230#section-3.2.4 + */ + private function validateAndTrimHeader($header, $values): array + { + if (!\is_string($header) || 1 !== \preg_match("@^[!#$%&'*+.^_`|~0-9A-Za-z-]+$@D", $header)) { + throw new \InvalidArgumentException('Header name must be an RFC 7230 compatible string'); + } + + if (!\is_array($values)) { + // This is simple, just one value. + if ((!\is_numeric($values) && !\is_string($values)) || 1 !== \preg_match("@^[ \t\x21-\x7E\x80-\xFF]*$@", (string) $values)) { + throw new \InvalidArgumentException('Header values must be RFC 7230 compatible strings'); + } + + return [\trim((string) $values, " \t")]; + } + + if (empty($values)) { + throw new \InvalidArgumentException('Header values must be a string or an array of strings, empty array given'); + } + + // Assert Non empty array + $returnValues = []; + foreach ($values as $v) { + if ((!\is_numeric($v) && !\is_string($v)) || 1 !== \preg_match("@^[ \t\x21-\x7E\x80-\xFF]*$@D", (string) $v)) { + throw new \InvalidArgumentException('Header values must be RFC 7230 compatible strings'); + } + + $returnValues[] = \trim((string) $v, " \t"); + } + + return $returnValues; + } +} diff --git a/modules/inpostizi/vendor/nyholm/psr7/src/Request.php b/modules/inpostizi/vendor/nyholm/psr7/src/Request.php new file mode 100644 index 00000000..d50744ee --- /dev/null +++ b/modules/inpostizi/vendor/nyholm/psr7/src/Request.php @@ -0,0 +1,47 @@ + + * @author Martijn van der Ven + * + * @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md + */ +class Request implements RequestInterface +{ + use MessageTrait; + use RequestTrait; + + /** + * @param string $method HTTP method + * @param string|UriInterface $uri URI + * @param array $headers Request headers + * @param string|resource|StreamInterface|null $body Request body + * @param string $version Protocol version + */ + public function __construct(string $method, $uri, array $headers = [], $body = null, string $version = '1.1') + { + if (!($uri instanceof UriInterface)) { + $uri = new Uri($uri); + } + + $this->method = $method; + $this->uri = $uri; + $this->setHeaders($headers); + $this->protocol = $version; + + if (!$this->hasHeader('Host')) { + $this->updateHostFromUri(); + } + + // If we got no body, defer initialization of the stream until Request::getBody() + if ('' !== $body && null !== $body) { + $this->stream = Stream::create($body); + } + } +} diff --git a/modules/inpostizi/vendor/nyholm/psr7/src/RequestTrait.php b/modules/inpostizi/vendor/nyholm/psr7/src/RequestTrait.php new file mode 100644 index 00000000..7c39bbb6 --- /dev/null +++ b/modules/inpostizi/vendor/nyholm/psr7/src/RequestTrait.php @@ -0,0 +1,117 @@ + + * @author Martijn van der Ven + * + * @internal should not be used outside of Nyholm/Psr7 as it does not fall under our BC promise + */ +trait RequestTrait +{ + /** @var string */ + private $method; + + /** @var string|null */ + private $requestTarget; + + /** @var UriInterface|null */ + private $uri; + + public function getRequestTarget(): string + { + if (null !== $this->requestTarget) { + return $this->requestTarget; + } + + if ('' === $target = $this->uri->getPath()) { + $target = '/'; + } + if ('' !== $this->uri->getQuery()) { + $target .= '?' . $this->uri->getQuery(); + } + + return $target; + } + + public function withRequestTarget($requestTarget): self + { + if (!\is_string($requestTarget)) { + throw new \InvalidArgumentException('Request target must be a string'); + } + + if (\preg_match('#\s#', $requestTarget)) { + throw new \InvalidArgumentException('Invalid request target provided; cannot contain whitespace'); + } + + $new = clone $this; + $new->requestTarget = $requestTarget; + + return $new; + } + + public function getMethod(): string + { + return $this->method; + } + + public function withMethod($method): self + { + if (!\is_string($method)) { + throw new \InvalidArgumentException('Method must be a string'); + } + + $new = clone $this; + $new->method = $method; + + return $new; + } + + public function getUri(): UriInterface + { + return $this->uri; + } + + public function withUri(UriInterface $uri, $preserveHost = false): self + { + if ($uri === $this->uri) { + return $this; + } + + $new = clone $this; + $new->uri = $uri; + + if (!$preserveHost || !$this->hasHeader('Host')) { + $new->updateHostFromUri(); + } + + return $new; + } + + private function updateHostFromUri(): void + { + if ('' === $host = $this->uri->getHost()) { + return; + } + + if (null !== ($port = $this->uri->getPort())) { + $host .= ':' . $port; + } + + if (isset($this->headerNames['host'])) { + $header = $this->headerNames['host']; + } else { + $this->headerNames['host'] = $header = 'Host'; + } + + // Ensure Host is the first header. + // See: http://tools.ietf.org/html/rfc7230#section-5.4 + $this->headers = [$header => [$host]] + $this->headers; + } +} diff --git a/modules/inpostizi/vendor/nyholm/psr7/src/Response.php b/modules/inpostizi/vendor/nyholm/psr7/src/Response.php new file mode 100644 index 00000000..9a26d2cb --- /dev/null +++ b/modules/inpostizi/vendor/nyholm/psr7/src/Response.php @@ -0,0 +1,90 @@ + + * @author Martijn van der Ven + * + * @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md + */ +class Response implements ResponseInterface +{ + use MessageTrait; + + /** @var array Map of standard HTTP status code/reason phrases */ + private const PHRASES = [ + 100 => 'Continue', 101 => 'Switching Protocols', 102 => 'Processing', + 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 207 => 'Multi-status', 208 => 'Already Reported', + 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 306 => 'Switch Proxy', 307 => 'Temporary Redirect', + 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Time-out', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Large', 415 => 'Unsupported Media Type', 416 => 'Requested range not satisfiable', 417 => 'Expectation Failed', 418 => 'I\'m a teapot', 422 => 'Unprocessable Entity', 423 => 'Locked', 424 => 'Failed Dependency', 425 => 'Unordered Collection', 426 => 'Upgrade Required', 428 => 'Precondition Required', 429 => 'Too Many Requests', 431 => 'Request Header Fields Too Large', 451 => 'Unavailable For Legal Reasons', + 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Time-out', 505 => 'HTTP Version not supported', 506 => 'Variant Also Negotiates', 507 => 'Insufficient Storage', 508 => 'Loop Detected', 511 => 'Network Authentication Required', + ]; + + /** @var string */ + private $reasonPhrase = ''; + + /** @var int */ + private $statusCode; + + /** + * @param int $status Status code + * @param array $headers Response headers + * @param string|resource|StreamInterface|null $body Response body + * @param string $version Protocol version + * @param string|null $reason Reason phrase (when empty a default will be used based on the status code) + */ + public function __construct(int $status = 200, array $headers = [], $body = null, string $version = '1.1', string $reason = null) + { + // If we got no body, defer initialization of the stream until Response::getBody() + if ('' !== $body && null !== $body) { + $this->stream = Stream::create($body); + } + + $this->statusCode = $status; + $this->setHeaders($headers); + if (null === $reason && isset(self::PHRASES[$this->statusCode])) { + $this->reasonPhrase = self::PHRASES[$status]; + } else { + $this->reasonPhrase = $reason ?? ''; + } + + $this->protocol = $version; + } + + public function getStatusCode(): int + { + return $this->statusCode; + } + + public function getReasonPhrase(): string + { + return $this->reasonPhrase; + } + + public function withStatus($code, $reasonPhrase = ''): self + { + if (!\is_int($code) && !\is_string($code)) { + throw new \InvalidArgumentException('Status code has to be an integer'); + } + + $code = (int) $code; + if ($code < 100 || $code > 599) { + throw new \InvalidArgumentException(\sprintf('Status code has to be an integer between 100 and 599. A status code of %d was given', $code)); + } + + $new = clone $this; + $new->statusCode = $code; + if ((null === $reasonPhrase || '' === $reasonPhrase) && isset(self::PHRASES[$new->statusCode])) { + $reasonPhrase = self::PHRASES[$new->statusCode]; + } + $new->reasonPhrase = $reasonPhrase; + + return $new; + } +} diff --git a/modules/inpostizi/vendor/nyholm/psr7/src/ServerRequest.php b/modules/inpostizi/vendor/nyholm/psr7/src/ServerRequest.php new file mode 100644 index 00000000..7f5022e4 --- /dev/null +++ b/modules/inpostizi/vendor/nyholm/psr7/src/ServerRequest.php @@ -0,0 +1,195 @@ + + * @author Martijn van der Ven + * + * @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md + */ +class ServerRequest implements ServerRequestInterface +{ + use MessageTrait; + use RequestTrait; + + /** @var array */ + private $attributes = []; + + /** @var array */ + private $cookieParams = []; + + /** @var array|object|null */ + private $parsedBody; + + /** @var array */ + private $queryParams = []; + + /** @var array */ + private $serverParams; + + /** @var UploadedFileInterface[] */ + private $uploadedFiles = []; + + /** + * @param string $method HTTP method + * @param string|UriInterface $uri URI + * @param array $headers Request headers + * @param string|resource|StreamInterface|null $body Request body + * @param string $version Protocol version + * @param array $serverParams Typically the $_SERVER superglobal + */ + public function __construct(string $method, $uri, array $headers = [], $body = null, string $version = '1.1', array $serverParams = []) + { + $this->serverParams = $serverParams; + + if (!($uri instanceof UriInterface)) { + $uri = new Uri($uri); + } + + $this->method = $method; + $this->uri = $uri; + $this->setHeaders($headers); + $this->protocol = $version; + \parse_str($uri->getQuery(), $this->queryParams); + + if (!$this->hasHeader('Host')) { + $this->updateHostFromUri(); + } + + // If we got no body, defer initialization of the stream until ServerRequest::getBody() + if ('' !== $body && null !== $body) { + $this->stream = Stream::create($body); + } + } + + public function getServerParams(): array + { + return $this->serverParams; + } + + public function getUploadedFiles(): array + { + return $this->uploadedFiles; + } + + /** + * @return static + */ + public function withUploadedFiles(array $uploadedFiles) + { + $new = clone $this; + $new->uploadedFiles = $uploadedFiles; + + return $new; + } + + public function getCookieParams(): array + { + return $this->cookieParams; + } + + /** + * @return static + */ + public function withCookieParams(array $cookies) + { + $new = clone $this; + $new->cookieParams = $cookies; + + return $new; + } + + public function getQueryParams(): array + { + return $this->queryParams; + } + + /** + * @return static + */ + public function withQueryParams(array $query) + { + $new = clone $this; + $new->queryParams = $query; + + return $new; + } + + /** + * @return array|object|null + */ + public function getParsedBody() + { + return $this->parsedBody; + } + + /** + * @return static + */ + public function withParsedBody($data) + { + if (!\is_array($data) && !\is_object($data) && null !== $data) { + throw new \InvalidArgumentException('First parameter to withParsedBody MUST be object, array or null'); + } + + $new = clone $this; + $new->parsedBody = $data; + + return $new; + } + + public function getAttributes(): array + { + return $this->attributes; + } + + /** + * @return mixed + */ + public function getAttribute($attribute, $default = null) + { + if (!\is_string($attribute)) { + throw new \InvalidArgumentException('Attribute name must be a string'); + } + + if (false === \array_key_exists($attribute, $this->attributes)) { + return $default; + } + + return $this->attributes[$attribute]; + } + + public function withAttribute($attribute, $value): self + { + if (!\is_string($attribute)) { + throw new \InvalidArgumentException('Attribute name must be a string'); + } + + $new = clone $this; + $new->attributes[$attribute] = $value; + + return $new; + } + + public function withoutAttribute($attribute): self + { + if (!\is_string($attribute)) { + throw new \InvalidArgumentException('Attribute name must be a string'); + } + + if (false === \array_key_exists($attribute, $this->attributes)) { + return $this; + } + + $new = clone $this; + unset($new->attributes[$attribute]); + + return $new; + } +} diff --git a/modules/inpostizi/vendor/nyholm/psr7/src/Stream.php b/modules/inpostizi/vendor/nyholm/psr7/src/Stream.php new file mode 100644 index 00000000..d173f356 --- /dev/null +++ b/modules/inpostizi/vendor/nyholm/psr7/src/Stream.php @@ -0,0 +1,316 @@ + + * @author Martijn van der Ven + * + * @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md + */ +class Stream implements StreamInterface +{ + /** @var resource|null A resource reference */ + private $stream; + + /** @var bool */ + private $seekable; + + /** @var bool */ + private $readable; + + /** @var bool */ + private $writable; + + /** @var array|mixed|void|bool|null */ + private $uri; + + /** @var int|null */ + private $size; + + /** @var array Hash of readable and writable stream types */ + private const READ_WRITE_HASH = [ + 'read' => [ + 'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true, + 'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true, + 'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true, + 'x+t' => true, 'c+t' => true, 'a+' => true, + ], + 'write' => [ + 'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true, + 'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true, + 'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true, + 'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true, + ], + ]; + + /** + * @param resource $body + */ + public function __construct($body) + { + if (!\is_resource($body)) { + throw new \InvalidArgumentException('First argument to Stream::__construct() must be resource'); + } + + $this->stream = $body; + $meta = \stream_get_meta_data($this->stream); + $this->seekable = $meta['seekable'] && 0 === \fseek($this->stream, 0, \SEEK_CUR); + $this->readable = isset(self::READ_WRITE_HASH['read'][$meta['mode']]); + $this->writable = isset(self::READ_WRITE_HASH['write'][$meta['mode']]); + } + + /** + * Creates a new PSR-7 stream. + * + * @param string|resource|StreamInterface $body + * + * @throws \InvalidArgumentException + */ + public static function create($body = ''): StreamInterface + { + if ($body instanceof StreamInterface) { + return $body; + } + + if (\is_string($body)) { + $resource = \fopen('php://temp', 'rw+'); + \fwrite($resource, $body); + \fseek($resource, 0); + $body = $resource; + } + + if (!\is_resource($body)) { + throw new \InvalidArgumentException('First argument to Stream::create() must be a string, resource or StreamInterface'); + } + + return new self($body); + } + + /** + * Closes the stream when the destructed. + */ + public function __destruct() + { + $this->close(); + } + + /** + * @return string + */ + public function __toString() + { + try { + if ($this->isSeekable()) { + $this->seek(0); + } + + return $this->getContents(); + } catch (\Throwable $e) { + if (\PHP_VERSION_ID >= 70400) { + throw $e; + } + + if (\is_array($errorHandler = \set_error_handler('var_dump'))) { + $errorHandler = $errorHandler[0] ?? null; + } + \restore_error_handler(); + + if ($e instanceof \Error || $errorHandler instanceof SymfonyErrorHandler || $errorHandler instanceof SymfonyLegacyErrorHandler) { + return \trigger_error((string) $e, \E_USER_ERROR); + } + + return ''; + } + } + + public function close(): void + { + if (isset($this->stream)) { + if (\is_resource($this->stream)) { + \fclose($this->stream); + } + $this->detach(); + } + } + + public function detach() + { + if (!isset($this->stream)) { + return null; + } + + $result = $this->stream; + unset($this->stream); + $this->size = $this->uri = null; + $this->readable = $this->writable = $this->seekable = false; + + return $result; + } + + private function getUri() + { + if (false !== $this->uri) { + $this->uri = $this->getMetadata('uri') ?? false; + } + + return $this->uri; + } + + public function getSize(): ?int + { + if (null !== $this->size) { + return $this->size; + } + + if (!isset($this->stream)) { + return null; + } + + // Clear the stat cache if the stream has a URI + if ($uri = $this->getUri()) { + \clearstatcache(true, $uri); + } + + $stats = \fstat($this->stream); + if (isset($stats['size'])) { + $this->size = $stats['size']; + + return $this->size; + } + + return null; + } + + public function tell(): int + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + + if (false === $result = @\ftell($this->stream)) { + throw new \RuntimeException('Unable to determine stream position: ' . (\error_get_last()['message'] ?? '')); + } + + return $result; + } + + public function eof(): bool + { + return !isset($this->stream) || \feof($this->stream); + } + + public function isSeekable(): bool + { + return $this->seekable; + } + + public function seek($offset, $whence = \SEEK_SET): void + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + + if (!$this->seekable) { + throw new \RuntimeException('Stream is not seekable'); + } + + if (-1 === \fseek($this->stream, $offset, $whence)) { + throw new \RuntimeException('Unable to seek to stream position "' . $offset . '" with whence ' . \var_export($whence, true)); + } + } + + public function rewind(): void + { + $this->seek(0); + } + + public function isWritable(): bool + { + return $this->writable; + } + + public function write($string): int + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + + if (!$this->writable) { + throw new \RuntimeException('Cannot write to a non-writable stream'); + } + + // We can't know the size after writing anything + $this->size = null; + + if (false === $result = @\fwrite($this->stream, $string)) { + throw new \RuntimeException('Unable to write to stream: ' . (\error_get_last()['message'] ?? '')); + } + + return $result; + } + + public function isReadable(): bool + { + return $this->readable; + } + + public function read($length): string + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + + if (!$this->readable) { + throw new \RuntimeException('Cannot read from non-readable stream'); + } + + if (false === $result = @\fread($this->stream, $length)) { + throw new \RuntimeException('Unable to read from stream: ' . (\error_get_last()['message'] ?? '')); + } + + return $result; + } + + public function getContents(): string + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + + if (false === $contents = @\stream_get_contents($this->stream)) { + throw new \RuntimeException('Unable to read stream contents: ' . (\error_get_last()['message'] ?? '')); + } + + return $contents; + } + + /** + * @return mixed + */ + public function getMetadata($key = null) + { + if (null !== $key && !\is_string($key)) { + throw new \InvalidArgumentException('Metadata key must be a string'); + } + + if (!isset($this->stream)) { + return $key ? null : []; + } + + $meta = \stream_get_meta_data($this->stream); + + if (null === $key) { + return $meta; + } + + return $meta[$key] ?? null; + } +} diff --git a/modules/inpostizi/vendor/nyholm/psr7/src/UploadedFile.php b/modules/inpostizi/vendor/nyholm/psr7/src/UploadedFile.php new file mode 100644 index 00000000..c77dca43 --- /dev/null +++ b/modules/inpostizi/vendor/nyholm/psr7/src/UploadedFile.php @@ -0,0 +1,179 @@ + + * @author Martijn van der Ven + * + * @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md + */ +class UploadedFile implements UploadedFileInterface +{ + /** @var array */ + private const ERRORS = [ + \UPLOAD_ERR_OK => 1, + \UPLOAD_ERR_INI_SIZE => 1, + \UPLOAD_ERR_FORM_SIZE => 1, + \UPLOAD_ERR_PARTIAL => 1, + \UPLOAD_ERR_NO_FILE => 1, + \UPLOAD_ERR_NO_TMP_DIR => 1, + \UPLOAD_ERR_CANT_WRITE => 1, + \UPLOAD_ERR_EXTENSION => 1, + ]; + + /** @var string */ + private $clientFilename; + + /** @var string */ + private $clientMediaType; + + /** @var int */ + private $error; + + /** @var string|null */ + private $file; + + /** @var bool */ + private $moved = false; + + /** @var int */ + private $size; + + /** @var StreamInterface|null */ + private $stream; + + /** + * @param StreamInterface|string|resource $streamOrFile + * @param int $size + * @param int $errorStatus + * @param string|null $clientFilename + * @param string|null $clientMediaType + */ + public function __construct($streamOrFile, $size, $errorStatus, $clientFilename = null, $clientMediaType = null) + { + if (false === \is_int($errorStatus) || !isset(self::ERRORS[$errorStatus])) { + throw new \InvalidArgumentException('Upload file error status must be an integer value and one of the "UPLOAD_ERR_*" constants'); + } + + if (false === \is_int($size)) { + throw new \InvalidArgumentException('Upload file size must be an integer'); + } + + if (null !== $clientFilename && !\is_string($clientFilename)) { + throw new \InvalidArgumentException('Upload file client filename must be a string or null'); + } + + if (null !== $clientMediaType && !\is_string($clientMediaType)) { + throw new \InvalidArgumentException('Upload file client media type must be a string or null'); + } + + $this->error = $errorStatus; + $this->size = $size; + $this->clientFilename = $clientFilename; + $this->clientMediaType = $clientMediaType; + + if (\UPLOAD_ERR_OK === $this->error) { + // Depending on the value set file or stream variable. + if (\is_string($streamOrFile) && '' !== $streamOrFile) { + $this->file = $streamOrFile; + } elseif (\is_resource($streamOrFile)) { + $this->stream = Stream::create($streamOrFile); + } elseif ($streamOrFile instanceof StreamInterface) { + $this->stream = $streamOrFile; + } else { + throw new \InvalidArgumentException('Invalid stream or file provided for UploadedFile'); + } + } + } + + /** + * @throws \RuntimeException if is moved or not ok + */ + private function validateActive(): void + { + if (\UPLOAD_ERR_OK !== $this->error) { + throw new \RuntimeException('Cannot retrieve stream due to upload error'); + } + + if ($this->moved) { + throw new \RuntimeException('Cannot retrieve stream after it has already been moved'); + } + } + + public function getStream(): StreamInterface + { + $this->validateActive(); + + if ($this->stream instanceof StreamInterface) { + return $this->stream; + } + + if (false === $resource = @\fopen($this->file, 'r')) { + throw new \RuntimeException(\sprintf('The file "%s" cannot be opened: %s', $this->file, \error_get_last()['message'] ?? '')); + } + + return Stream::create($resource); + } + + public function moveTo($targetPath): void + { + $this->validateActive(); + + if (!\is_string($targetPath) || '' === $targetPath) { + throw new \InvalidArgumentException('Invalid path provided for move operation; must be a non-empty string'); + } + + if (null !== $this->file) { + $this->moved = 'cli' === \PHP_SAPI ? @\rename($this->file, $targetPath) : @\move_uploaded_file($this->file, $targetPath); + + if (false === $this->moved) { + throw new \RuntimeException(\sprintf('Uploaded file could not be moved to "%s": %s', $targetPath, \error_get_last()['message'] ?? '')); + } + } else { + $stream = $this->getStream(); + if ($stream->isSeekable()) { + $stream->rewind(); + } + + if (false === $resource = @\fopen($targetPath, 'w')) { + throw new \RuntimeException(\sprintf('The file "%s" cannot be opened: %s', $targetPath, \error_get_last()['message'] ?? '')); + } + + $dest = Stream::create($resource); + + while (!$stream->eof()) { + if (!$dest->write($stream->read(1048576))) { + break; + } + } + + $this->moved = true; + } + } + + public function getSize(): int + { + return $this->size; + } + + public function getError(): int + { + return $this->error; + } + + public function getClientFilename(): ?string + { + return $this->clientFilename; + } + + public function getClientMediaType(): ?string + { + return $this->clientMediaType; + } +} diff --git a/modules/inpostizi/vendor/nyholm/psr7/src/Uri.php b/modules/inpostizi/vendor/nyholm/psr7/src/Uri.php new file mode 100644 index 00000000..0d2c9754 --- /dev/null +++ b/modules/inpostizi/vendor/nyholm/psr7/src/Uri.php @@ -0,0 +1,335 @@ + + * @author Martijn van der Ven + * + * @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md + */ +class Uri implements UriInterface +{ + private const SCHEMES = ['http' => 80, 'https' => 443]; + + private const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~'; + + private const CHAR_SUB_DELIMS = '!\$&\'\(\)\*\+,;='; + + private const CHAR_GEN_DELIMS = ':\/\?#\[\]@'; + + /** @var string Uri scheme. */ + private $scheme = ''; + + /** @var string Uri user info. */ + private $userInfo = ''; + + /** @var string Uri host. */ + private $host = ''; + + /** @var int|null Uri port. */ + private $port; + + /** @var string Uri path. */ + private $path = ''; + + /** @var string Uri query string. */ + private $query = ''; + + /** @var string Uri fragment. */ + private $fragment = ''; + + public function __construct(string $uri = '') + { + if ('' !== $uri) { + if (false === $parts = \parse_url($uri)) { + throw new \InvalidArgumentException(\sprintf('Unable to parse URI: "%s"', $uri)); + } + + // Apply parse_url parts to a URI. + $this->scheme = isset($parts['scheme']) ? \strtr($parts['scheme'], 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') : ''; + $this->userInfo = $parts['user'] ?? ''; + $this->host = isset($parts['host']) ? \strtr($parts['host'], 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') : ''; + $this->port = isset($parts['port']) ? $this->filterPort($parts['port']) : null; + $this->path = isset($parts['path']) ? $this->filterPath($parts['path']) : ''; + $this->query = isset($parts['query']) ? $this->filterQueryAndFragment($parts['query']) : ''; + $this->fragment = isset($parts['fragment']) ? $this->filterQueryAndFragment($parts['fragment']) : ''; + if (isset($parts['pass'])) { + $this->userInfo .= ':' . $parts['pass']; + } + } + } + + public function __toString(): string + { + return self::createUriString($this->scheme, $this->getAuthority(), $this->path, $this->query, $this->fragment); + } + + public function getScheme(): string + { + return $this->scheme; + } + + public function getAuthority(): string + { + if ('' === $this->host) { + return ''; + } + + $authority = $this->host; + if ('' !== $this->userInfo) { + $authority = $this->userInfo . '@' . $authority; + } + + if (null !== $this->port) { + $authority .= ':' . $this->port; + } + + return $authority; + } + + public function getUserInfo(): string + { + return $this->userInfo; + } + + public function getHost(): string + { + return $this->host; + } + + public function getPort(): ?int + { + return $this->port; + } + + public function getPath(): string + { + $path = $this->path; + + if ('' !== $path && '/' !== $path[0]) { + if ('' !== $this->host) { + // If the path is rootless and an authority is present, the path MUST be prefixed by "/" + $path = '/' . $path; + } + } elseif (isset($path[1]) && '/' === $path[1]) { + // If the path is starting with more than one "/", the + // starting slashes MUST be reduced to one. + $path = '/' . \ltrim($path, '/'); + } + + return $path; + } + + public function getQuery(): string + { + return $this->query; + } + + public function getFragment(): string + { + return $this->fragment; + } + + public function withScheme($scheme): self + { + if (!\is_string($scheme)) { + throw new \InvalidArgumentException('Scheme must be a string'); + } + + if ($this->scheme === $scheme = \strtr($scheme, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')) { + return $this; + } + + $new = clone $this; + $new->scheme = $scheme; + $new->port = $new->filterPort($new->port); + + return $new; + } + + public function withUserInfo($user, $password = null): self + { + if (!\is_string($user)) { + throw new \InvalidArgumentException('User must be a string'); + } + + $info = \preg_replace_callback('/[' . self::CHAR_GEN_DELIMS . self::CHAR_SUB_DELIMS . ']++/', [__CLASS__, 'rawurlencodeMatchZero'], $user); + if (null !== $password && '' !== $password) { + if (!\is_string($password)) { + throw new \InvalidArgumentException('Password must be a string'); + } + + $info .= ':' . \preg_replace_callback('/[' . self::CHAR_GEN_DELIMS . self::CHAR_SUB_DELIMS . ']++/', [__CLASS__, 'rawurlencodeMatchZero'], $password); + } + + if ($this->userInfo === $info) { + return $this; + } + + $new = clone $this; + $new->userInfo = $info; + + return $new; + } + + public function withHost($host): self + { + if (!\is_string($host)) { + throw new \InvalidArgumentException('Host must be a string'); + } + + if ($this->host === $host = \strtr($host, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')) { + return $this; + } + + $new = clone $this; + $new->host = $host; + + return $new; + } + + public function withPort($port): self + { + if ($this->port === $port = $this->filterPort($port)) { + return $this; + } + + $new = clone $this; + $new->port = $port; + + return $new; + } + + public function withPath($path): self + { + if ($this->path === $path = $this->filterPath($path)) { + return $this; + } + + $new = clone $this; + $new->path = $path; + + return $new; + } + + public function withQuery($query): self + { + if ($this->query === $query = $this->filterQueryAndFragment($query)) { + return $this; + } + + $new = clone $this; + $new->query = $query; + + return $new; + } + + public function withFragment($fragment): self + { + if ($this->fragment === $fragment = $this->filterQueryAndFragment($fragment)) { + return $this; + } + + $new = clone $this; + $new->fragment = $fragment; + + return $new; + } + + /** + * Create a URI string from its various parts. + */ + private static function createUriString(string $scheme, string $authority, string $path, string $query, string $fragment): string + { + $uri = ''; + if ('' !== $scheme) { + $uri .= $scheme . ':'; + } + + if ('' !== $authority) { + $uri .= '//' . $authority; + } + + if ('' !== $path) { + if ('/' !== $path[0]) { + if ('' !== $authority) { + // If the path is rootless and an authority is present, the path MUST be prefixed by "/" + $path = '/' . $path; + } + } elseif (isset($path[1]) && '/' === $path[1]) { + if ('' === $authority) { + // If the path is starting with more than one "/" and no authority is present, the + // starting slashes MUST be reduced to one. + $path = '/' . \ltrim($path, '/'); + } + } + + $uri .= $path; + } + + if ('' !== $query) { + $uri .= '?' . $query; + } + + if ('' !== $fragment) { + $uri .= '#' . $fragment; + } + + return $uri; + } + + /** + * Is a given port non-standard for the current scheme? + */ + private static function isNonStandardPort(string $scheme, int $port): bool + { + return !isset(self::SCHEMES[$scheme]) || $port !== self::SCHEMES[$scheme]; + } + + private function filterPort($port): ?int + { + if (null === $port) { + return null; + } + + $port = (int) $port; + if (0 > $port || 0xFFFF < $port) { + throw new \InvalidArgumentException(\sprintf('Invalid port: %d. Must be between 0 and 65535', $port)); + } + + return self::isNonStandardPort($this->scheme, $port) ? $port : null; + } + + private function filterPath($path): string + { + if (!\is_string($path)) { + throw new \InvalidArgumentException('Path must be a string'); + } + + return \preg_replace_callback('/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/', [__CLASS__, 'rawurlencodeMatchZero'], $path); + } + + private function filterQueryAndFragment($str): string + { + if (!\is_string($str)) { + throw new \InvalidArgumentException('Query and fragment must be a string'); + } + + return \preg_replace_callback('/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/', [__CLASS__, 'rawurlencodeMatchZero'], $str); + } + + private static function rawurlencodeMatchZero(array $match): string + { + return \rawurlencode($match[0]); + } +} diff --git a/modules/inpostizi/vendor/php-http/message-factory/CHANGELOG.md b/modules/inpostizi/vendor/php-http/message-factory/CHANGELOG.md new file mode 100644 index 00000000..25f34f45 --- /dev/null +++ b/modules/inpostizi/vendor/php-http/message-factory/CHANGELOG.md @@ -0,0 +1,72 @@ +# Change Log + + +## 1.1.0 - 2023-04-14 + +### Changed + +- Allow `psr/http-message` v2 in addition to v1 +- Deprecate all interfaces in favor of [PSR-17](https://www.php-fig.org/psr/psr-17/) + +## 1.0.2 - 2015-12-19 + +### Added + +- Request and Response factory binding types to Puli + + +## 1.0.1 - 2015-12-17 + +### Added + +- Puli configuration and binding types + + +## 1.0.0 - 2015-12-15 + +### Added + +- Response Factory in order to be reused in Message and Server Message factories +- Request Factory + +### Changed + +- Message Factory extends Request and Response factories + + +## 1.0.0-RC1 - 2015-12-14 + +### Added + +- CS check + +### Changed + +- RuntimeException is thrown when the StreamFactory cannot write to the underlying stream + + +## 0.3.0 - 2015-11-16 + +### Removed + +- Client Context Factory +- Factory Awares and Templates + + +## 0.2.0 - 2015-11-16 + +### Changed + +- Reordered the parameters when creating a message to have the protocol last, +as its the least likely to need to be changed. + + +## 0.1.0 - 2015-06-01 + +### Added + +- Initial release + +### Changed + +- Helpers are renamed to templates diff --git a/modules/inpostizi/vendor/php-http/message-factory/CONTRIBUTING b/modules/inpostizi/vendor/php-http/message-factory/CONTRIBUTING new file mode 100644 index 00000000..841914ae --- /dev/null +++ b/modules/inpostizi/vendor/php-http/message-factory/CONTRIBUTING @@ -0,0 +1 @@ +Please see http://docs.php-http.org/en/latest/development/contributing.html diff --git a/modules/inpostizi/vendor/php-http/message-factory/LICENSE b/modules/inpostizi/vendor/php-http/message-factory/LICENSE new file mode 100644 index 00000000..4558d6f0 --- /dev/null +++ b/modules/inpostizi/vendor/php-http/message-factory/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-2016 PHP HTTP Team + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/modules/inpostizi/vendor/php-http/message-factory/README.md b/modules/inpostizi/vendor/php-http/message-factory/README.md new file mode 100644 index 00000000..6c3b7450 --- /dev/null +++ b/modules/inpostizi/vendor/php-http/message-factory/README.md @@ -0,0 +1,42 @@ +# PSR-7 Message Factory + +[![Latest Version](https://img.shields.io/github/release/php-http/message-factory.svg?style=flat-square)](https://github.com/php-http/message-factory/releases) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) +[![Total Downloads](https://img.shields.io/packagist/dt/php-http/message-factory.svg?style=flat-square)](https://packagist.org/packages/php-http/message-factory) + +**Factory interfaces for PSR-7 HTTP Message.** + +## Obsolete + +The PHP-HTTP factories have become obsolete with the [PSR-17](https://www.php-fig.org/psr/psr-17/) factories standard. +All major HTTP client implementors provide [PSR-17 factories](https://packagist.org/packages/psr/http-factory). + +This package will remain available for the time being to not break legacy code, but we encourage everybody to move to PSR-17. + +## Install + +Via Composer + +``` bash +$ composer require php-http/message-factory +``` + + +## Documentation + +Please see the [official documentation](http://docs.php-http.org/en/latest/message/message-factory.html). + + +## Contributing + +Please see our [contributing guide](http://docs.php-http.org/en/latest/development/contributing.html). + + +## Security + +If you discover any security related issues, please contact us at [security@php-http.org](mailto:security@php-http.org). + + +## License + +The MIT License (MIT). Please see [License File](LICENSE) for more information. diff --git a/modules/inpostizi/vendor/php-http/message-factory/composer.json b/modules/inpostizi/vendor/php-http/message-factory/composer.json new file mode 100644 index 00000000..57bdb4b9 --- /dev/null +++ b/modules/inpostizi/vendor/php-http/message-factory/composer.json @@ -0,0 +1,27 @@ +{ + "name": "php-http/message-factory", + "description": "Factory interfaces for PSR-7 HTTP Message", + "license": "MIT", + "keywords": ["http", "factory", "message", "stream", "uri"], + "homepage": "http://php-http.org", + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "require": { + "php": ">=5.4", + "psr/http-message": "^1.0 || ^2.0" + }, + "autoload": { + "psr-4": { + "Http\\Message\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + } +} diff --git a/modules/inpostizi/vendor/php-http/message-factory/puli.json b/modules/inpostizi/vendor/php-http/message-factory/puli.json new file mode 100644 index 00000000..fac317b0 --- /dev/null +++ b/modules/inpostizi/vendor/php-http/message-factory/puli.json @@ -0,0 +1,44 @@ +{ + "version": "1.0", + "name": "php-http/message-factory", + "binding-types": { + "Http\\Message\\MessageFactory": { + "description": "PSR-7 Message Factory", + "parameters": { + "depends": { + "description": "Optional class dependency which can be checked by consumers" + } + } + }, + "Http\\Message\\RequestFactory": { + "parameters": { + "depends": { + "description": "Optional class dependency which can be checked by consumers" + } + } + }, + "Http\\Message\\ResponseFactory": { + "parameters": { + "depends": { + "description": "Optional class dependency which can be checked by consumers" + } + } + }, + "Http\\Message\\StreamFactory": { + "description": "PSR-7 Stream Factory", + "parameters": { + "depends": { + "description": "Optional class dependency which can be checked by consumers" + } + } + }, + "Http\\Message\\UriFactory": { + "description": "PSR-7 URI Factory", + "parameters": { + "depends": { + "description": "Optional class dependency which can be checked by consumers" + } + } + } + } +} diff --git a/modules/inpostizi/vendor/php-http/message-factory/src/MessageFactory.php b/modules/inpostizi/vendor/php-http/message-factory/src/MessageFactory.php new file mode 100644 index 00000000..2ed7e453 --- /dev/null +++ b/modules/inpostizi/vendor/php-http/message-factory/src/MessageFactory.php @@ -0,0 +1,14 @@ + + * + * @deprecated since version 1.1, use Psr\Http\Message\RequestFactoryInterface and Psr\Http\Message\ResponseFactoryInterface instead. + */ +interface MessageFactory extends RequestFactory, ResponseFactory +{ +} diff --git a/modules/inpostizi/vendor/php-http/message-factory/src/RequestFactory.php b/modules/inpostizi/vendor/php-http/message-factory/src/RequestFactory.php new file mode 100644 index 00000000..09879f15 --- /dev/null +++ b/modules/inpostizi/vendor/php-http/message-factory/src/RequestFactory.php @@ -0,0 +1,36 @@ + + * + * @deprecated since version 1.1, use Psr\Http\Message\RequestFactoryInterface instead. + */ +interface RequestFactory +{ + /** + * Creates a new PSR-7 request. + * + * @param string $method + * @param string|UriInterface $uri + * @param array $headers + * @param resource|string|StreamInterface|null $body + * @param string $protocolVersion + * + * @return RequestInterface + */ + public function createRequest( + $method, + $uri, + array $headers = [], + $body = null, + $protocolVersion = '1.1' + ); +} diff --git a/modules/inpostizi/vendor/php-http/message-factory/src/ResponseFactory.php b/modules/inpostizi/vendor/php-http/message-factory/src/ResponseFactory.php new file mode 100644 index 00000000..eb9bde77 --- /dev/null +++ b/modules/inpostizi/vendor/php-http/message-factory/src/ResponseFactory.php @@ -0,0 +1,37 @@ + + * + * @deprecated since version 1.1, use Psr\Http\Message\ResponseFactoryInterface instead. + */ +interface ResponseFactory +{ + /** + * Creates a new PSR-7 response. + * + * @param int $statusCode + * @param string|null $reasonPhrase + * @param array $headers + * @param resource|string|StreamInterface|null $body + * @param string $protocolVersion + * + * @return ResponseInterface + */ + public function createResponse( + $statusCode = 200, + $reasonPhrase = null, + array $headers = [], + $body = null, + $protocolVersion = '1.1' + ); +} diff --git a/modules/inpostizi/vendor/php-http/message-factory/src/StreamFactory.php b/modules/inpostizi/vendor/php-http/message-factory/src/StreamFactory.php new file mode 100644 index 00000000..bd949a4a --- /dev/null +++ b/modules/inpostizi/vendor/php-http/message-factory/src/StreamFactory.php @@ -0,0 +1,27 @@ + + * + * @deprecated since version 1.1, use Psr\Http\Message\StreamFactoryInterface instead. + */ +interface StreamFactory +{ + /** + * Creates a new PSR-7 stream. + * + * @param string|resource|StreamInterface|null $body + * + * @return StreamInterface + * + * @throws \InvalidArgumentException if the stream body is invalid + * @throws \RuntimeException if creating the stream from $body fails + */ + public function createStream($body = null); +} diff --git a/modules/inpostizi/vendor/php-http/message-factory/src/UriFactory.php b/modules/inpostizi/vendor/php-http/message-factory/src/UriFactory.php new file mode 100644 index 00000000..96416b74 --- /dev/null +++ b/modules/inpostizi/vendor/php-http/message-factory/src/UriFactory.php @@ -0,0 +1,26 @@ + + * + * @deprecated since version 1.1, use Psr\Http\Message\UriFactoryInterface instead. + */ +interface UriFactory +{ + /** + * Creates an PSR-7 URI. + * + * @param string|UriInterface $uri + * + * @return UriInterface + * + * @throws \InvalidArgumentException if the $uri argument can not be converted into a valid URI + */ + public function createUri($uri); +} diff --git a/modules/inpostizi/vendor/psr/clock/CHANGELOG.md b/modules/inpostizi/vendor/psr/clock/CHANGELOG.md new file mode 100644 index 00000000..3cd6b9b7 --- /dev/null +++ b/modules/inpostizi/vendor/psr/clock/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog + +All notable changes to this project will be documented in this file, in reverse chronological order by release. + +## 1.0.0 + +First stable release after PSR-20 acceptance + +## 0.1.0 + +First release diff --git a/modules/inpostizi/vendor/psr/clock/LICENSE b/modules/inpostizi/vendor/psr/clock/LICENSE new file mode 100644 index 00000000..be683421 --- /dev/null +++ b/modules/inpostizi/vendor/psr/clock/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2017 PHP Framework Interoperability Group + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/modules/inpostizi/vendor/psr/clock/README.md b/modules/inpostizi/vendor/psr/clock/README.md new file mode 100644 index 00000000..6ca877ee --- /dev/null +++ b/modules/inpostizi/vendor/psr/clock/README.md @@ -0,0 +1,61 @@ +# PSR Clock + +This repository holds the interface for [PSR-20][psr-url]. + +Note that this is not a clock of its own. It is merely an interface that +describes a clock. See the specification for more details. + +## Installation + +```bash +composer require psr/clock +``` + +## Usage + +If you need a clock, you can use the interface like this: + +```php +clock = $clock; + } + + public function doSomething() + { + /** @var DateTimeImmutable $currentDateAndTime */ + $currentDateAndTime = $this->clock->now(); + // do something useful with that information + } +} +``` + +You can then pick one of the [implementations][implementation-url] of the interface to get a clock. + +If you want to implement the interface, you can require this package and +implement `Psr\Clock\ClockInterface` in your code. + +Don't forget to add `psr/clock-implementation` to your `composer.json`s `provides`-section like this: + +```json +{ + "provides": { + "psr/clock-implementation": "1.0" + } +} +``` + +And please read the [specification text][specification-url] for details on the interface. + +[psr-url]: https://www.php-fig.org/psr/psr-20 +[package-url]: https://packagist.org/packages/psr/clock +[implementation-url]: https://packagist.org/providers/psr/clock-implementation +[specification-url]: https://github.com/php-fig/fig-standards/blob/master/proposed/clock.md diff --git a/modules/inpostizi/vendor/psr/clock/composer.json b/modules/inpostizi/vendor/psr/clock/composer.json new file mode 100644 index 00000000..77992eda --- /dev/null +++ b/modules/inpostizi/vendor/psr/clock/composer.json @@ -0,0 +1,21 @@ +{ + "name": "psr/clock", + "description": "Common interface for reading the clock.", + "keywords": ["psr", "psr-20", "time", "clock", "now"], + "homepage": "https://github.com/php-fig/clock", + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "require": { + "php": "^7.0 || ^8.0" + }, + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + } +} diff --git a/modules/inpostizi/vendor/psr/clock/src/ClockInterface.php b/modules/inpostizi/vendor/psr/clock/src/ClockInterface.php new file mode 100644 index 00000000..7b6d8d8a --- /dev/null +++ b/modules/inpostizi/vendor/psr/clock/src/ClockInterface.php @@ -0,0 +1,13 @@ +=5.3.0" + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/modules/inpostizi/vendor/psr/container/src/ContainerExceptionInterface.php b/modules/inpostizi/vendor/psr/container/src/ContainerExceptionInterface.php new file mode 100644 index 00000000..d35c6b4d --- /dev/null +++ b/modules/inpostizi/vendor/psr/container/src/ContainerExceptionInterface.php @@ -0,0 +1,13 @@ +=7.0.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/modules/inpostizi/vendor/psr/http-factory/src/RequestFactoryInterface.php b/modules/inpostizi/vendor/psr/http-factory/src/RequestFactoryInterface.php new file mode 100644 index 00000000..cb39a08b --- /dev/null +++ b/modules/inpostizi/vendor/psr/http-factory/src/RequestFactoryInterface.php @@ -0,0 +1,18 @@ +=5.3.0" + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/modules/inpostizi/vendor/psr/http-message/src/MessageInterface.php b/modules/inpostizi/vendor/psr/http-message/src/MessageInterface.php new file mode 100644 index 00000000..dd46e5ec --- /dev/null +++ b/modules/inpostizi/vendor/psr/http-message/src/MessageInterface.php @@ -0,0 +1,187 @@ +getHeaders() as $name => $values) { + * echo $name . ": " . implode(", ", $values); + * } + * + * // Emit headers iteratively: + * foreach ($message->getHeaders() as $name => $values) { + * foreach ($values as $value) { + * header(sprintf('%s: %s', $name, $value), false); + * } + * } + * + * While header names are not case-sensitive, getHeaders() will preserve the + * exact case in which headers were originally specified. + * + * @return string[][] Returns an associative array of the message's headers. Each + * key MUST be a header name, and each value MUST be an array of strings + * for that header. + */ + public function getHeaders(); + + /** + * Checks if a header exists by the given case-insensitive name. + * + * @param string $name Case-insensitive header field name. + * @return bool Returns true if any header names match the given header + * name using a case-insensitive string comparison. Returns false if + * no matching header name is found in the message. + */ + public function hasHeader($name); + + /** + * Retrieves a message header value by the given case-insensitive name. + * + * This method returns an array of all the header values of the given + * case-insensitive header name. + * + * If the header does not appear in the message, this method MUST return an + * empty array. + * + * @param string $name Case-insensitive header field name. + * @return string[] An array of string values as provided for the given + * header. If the header does not appear in the message, this method MUST + * return an empty array. + */ + public function getHeader($name); + + /** + * Retrieves a comma-separated string of the values for a single header. + * + * This method returns all of the header values of the given + * case-insensitive header name as a string concatenated together using + * a comma. + * + * NOTE: Not all header values may be appropriately represented using + * comma concatenation. For such headers, use getHeader() instead + * and supply your own delimiter when concatenating. + * + * If the header does not appear in the message, this method MUST return + * an empty string. + * + * @param string $name Case-insensitive header field name. + * @return string A string of values as provided for the given header + * concatenated together using a comma. If the header does not appear in + * the message, this method MUST return an empty string. + */ + public function getHeaderLine($name); + + /** + * Return an instance with the provided value replacing the specified header. + * + * While header names are case-insensitive, the casing of the header will + * be preserved by this function, and returned from getHeaders(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new and/or updated header and value. + * + * @param string $name Case-insensitive header field name. + * @param string|string[] $value Header value(s). + * @return static + * @throws \InvalidArgumentException for invalid header names or values. + */ + public function withHeader($name, $value); + + /** + * Return an instance with the specified header appended with the given value. + * + * Existing values for the specified header will be maintained. The new + * value(s) will be appended to the existing list. If the header did not + * exist previously, it will be added. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new header and/or value. + * + * @param string $name Case-insensitive header field name to add. + * @param string|string[] $value Header value(s). + * @return static + * @throws \InvalidArgumentException for invalid header names or values. + */ + public function withAddedHeader($name, $value); + + /** + * Return an instance without the specified header. + * + * Header resolution MUST be done without case-sensitivity. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that removes + * the named header. + * + * @param string $name Case-insensitive header field name to remove. + * @return static + */ + public function withoutHeader($name); + + /** + * Gets the body of the message. + * + * @return StreamInterface Returns the body as a stream. + */ + public function getBody(); + + /** + * Return an instance with the specified message body. + * + * The body MUST be a StreamInterface object. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return a new instance that has the + * new body stream. + * + * @param StreamInterface $body Body. + * @return static + * @throws \InvalidArgumentException When the body is not valid. + */ + public function withBody(StreamInterface $body); +} diff --git a/modules/inpostizi/vendor/psr/http-message/src/RequestInterface.php b/modules/inpostizi/vendor/psr/http-message/src/RequestInterface.php new file mode 100644 index 00000000..a96d4fd6 --- /dev/null +++ b/modules/inpostizi/vendor/psr/http-message/src/RequestInterface.php @@ -0,0 +1,129 @@ +getQuery()` + * or from the `QUERY_STRING` server param. + * + * @return array + */ + public function getQueryParams(); + + /** + * Return an instance with the specified query string arguments. + * + * These values SHOULD remain immutable over the course of the incoming + * request. They MAY be injected during instantiation, such as from PHP's + * $_GET superglobal, or MAY be derived from some other value such as the + * URI. In cases where the arguments are parsed from the URI, the data + * MUST be compatible with what PHP's parse_str() would return for + * purposes of how duplicate query parameters are handled, and how nested + * sets are handled. + * + * Setting query string arguments MUST NOT change the URI stored by the + * request, nor the values in the server params. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated query string arguments. + * + * @param array $query Array of query string arguments, typically from + * $_GET. + * @return static + */ + public function withQueryParams(array $query); + + /** + * Retrieve normalized file upload data. + * + * This method returns upload metadata in a normalized tree, with each leaf + * an instance of Psr\Http\Message\UploadedFileInterface. + * + * These values MAY be prepared from $_FILES or the message body during + * instantiation, or MAY be injected via withUploadedFiles(). + * + * @return array An array tree of UploadedFileInterface instances; an empty + * array MUST be returned if no data is present. + */ + public function getUploadedFiles(); + + /** + * Create a new instance with the specified uploaded files. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated body parameters. + * + * @param array $uploadedFiles An array tree of UploadedFileInterface instances. + * @return static + * @throws \InvalidArgumentException if an invalid structure is provided. + */ + public function withUploadedFiles(array $uploadedFiles); + + /** + * Retrieve any parameters provided in the request body. + * + * If the request Content-Type is either application/x-www-form-urlencoded + * or multipart/form-data, and the request method is POST, this method MUST + * return the contents of $_POST. + * + * Otherwise, this method may return any results of deserializing + * the request body content; as parsing returns structured content, the + * potential types MUST be arrays or objects only. A null value indicates + * the absence of body content. + * + * @return null|array|object The deserialized body parameters, if any. + * These will typically be an array or object. + */ + public function getParsedBody(); + + /** + * Return an instance with the specified body parameters. + * + * These MAY be injected during instantiation. + * + * If the request Content-Type is either application/x-www-form-urlencoded + * or multipart/form-data, and the request method is POST, use this method + * ONLY to inject the contents of $_POST. + * + * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of + * deserializing the request body content. Deserialization/parsing returns + * structured data, and, as such, this method ONLY accepts arrays or objects, + * or a null value if nothing was available to parse. + * + * As an example, if content negotiation determines that the request data + * is a JSON payload, this method could be used to create a request + * instance with the deserialized parameters. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated body parameters. + * + * @param null|array|object $data The deserialized body data. This will + * typically be in an array or object. + * @return static + * @throws \InvalidArgumentException if an unsupported argument type is + * provided. + */ + public function withParsedBody($data); + + /** + * Retrieve attributes derived from the request. + * + * The request "attributes" may be used to allow injection of any + * parameters derived from the request: e.g., the results of path + * match operations; the results of decrypting cookies; the results of + * deserializing non-form-encoded message bodies; etc. Attributes + * will be application and request specific, and CAN be mutable. + * + * @return array Attributes derived from the request. + */ + public function getAttributes(); + + /** + * Retrieve a single derived request attribute. + * + * Retrieves a single derived request attribute as described in + * getAttributes(). If the attribute has not been previously set, returns + * the default value as provided. + * + * This method obviates the need for a hasAttribute() method, as it allows + * specifying a default value to return if the attribute is not found. + * + * @see getAttributes() + * @param string $name The attribute name. + * @param mixed $default Default value to return if the attribute does not exist. + * @return mixed + */ + public function getAttribute($name, $default = null); + + /** + * Return an instance with the specified derived request attribute. + * + * This method allows setting a single derived request attribute as + * described in getAttributes(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated attribute. + * + * @see getAttributes() + * @param string $name The attribute name. + * @param mixed $value The value of the attribute. + * @return static + */ + public function withAttribute($name, $value); + + /** + * Return an instance that removes the specified derived request attribute. + * + * This method allows removing a single derived request attribute as + * described in getAttributes(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that removes + * the attribute. + * + * @see getAttributes() + * @param string $name The attribute name. + * @return static + */ + public function withoutAttribute($name); +} diff --git a/modules/inpostizi/vendor/psr/http-message/src/StreamInterface.php b/modules/inpostizi/vendor/psr/http-message/src/StreamInterface.php new file mode 100644 index 00000000..f68f3912 --- /dev/null +++ b/modules/inpostizi/vendor/psr/http-message/src/StreamInterface.php @@ -0,0 +1,158 @@ + + * [user-info@]host[:port] + * + * + * If the port component is not set or is the standard port for the current + * scheme, it SHOULD NOT be included. + * + * @see https://tools.ietf.org/html/rfc3986#section-3.2 + * @return string The URI authority, in "[user-info@]host[:port]" format. + */ + public function getAuthority(); + + /** + * Retrieve the user information component of the URI. + * + * If no user information is present, this method MUST return an empty + * string. + * + * If a user is present in the URI, this will return that value; + * additionally, if the password is also present, it will be appended to the + * user value, with a colon (":") separating the values. + * + * The trailing "@" character is not part of the user information and MUST + * NOT be added. + * + * @return string The URI user information, in "username[:password]" format. + */ + public function getUserInfo(); + + /** + * Retrieve the host component of the URI. + * + * If no host is present, this method MUST return an empty string. + * + * The value returned MUST be normalized to lowercase, per RFC 3986 + * Section 3.2.2. + * + * @see http://tools.ietf.org/html/rfc3986#section-3.2.2 + * @return string The URI host. + */ + public function getHost(); + + /** + * Retrieve the port component of the URI. + * + * If a port is present, and it is non-standard for the current scheme, + * this method MUST return it as an integer. If the port is the standard port + * used with the current scheme, this method SHOULD return null. + * + * If no port is present, and no scheme is present, this method MUST return + * a null value. + * + * If no port is present, but a scheme is present, this method MAY return + * the standard port for that scheme, but SHOULD return null. + * + * @return null|int The URI port. + */ + public function getPort(); + + /** + * Retrieve the path component of the URI. + * + * The path can either be empty or absolute (starting with a slash) or + * rootless (not starting with a slash). Implementations MUST support all + * three syntaxes. + * + * Normally, the empty path "" and absolute path "/" are considered equal as + * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically + * do this normalization because in contexts with a trimmed base path, e.g. + * the front controller, this difference becomes significant. It's the task + * of the user to handle both "" and "/". + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.3. + * + * As an example, if the value should include a slash ("/") not intended as + * delimiter between path segments, that value MUST be passed in encoded + * form (e.g., "%2F") to the instance. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.3 + * @return string The URI path. + */ + public function getPath(); + + /** + * Retrieve the query string of the URI. + * + * If no query string is present, this method MUST return an empty string. + * + * The leading "?" character is not part of the query and MUST NOT be + * added. + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.4. + * + * As an example, if a value in a key/value pair of the query string should + * include an ampersand ("&") not intended as a delimiter between values, + * that value MUST be passed in encoded form (e.g., "%26") to the instance. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.4 + * @return string The URI query string. + */ + public function getQuery(); + + /** + * Retrieve the fragment component of the URI. + * + * If no fragment is present, this method MUST return an empty string. + * + * The leading "#" character is not part of the fragment and MUST NOT be + * added. + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.5. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.5 + * @return string The URI fragment. + */ + public function getFragment(); + + /** + * Return an instance with the specified scheme. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified scheme. + * + * Implementations MUST support the schemes "http" and "https" case + * insensitively, and MAY accommodate other schemes if required. + * + * An empty scheme is equivalent to removing the scheme. + * + * @param string $scheme The scheme to use with the new instance. + * @return static A new instance with the specified scheme. + * @throws \InvalidArgumentException for invalid or unsupported schemes. + */ + public function withScheme($scheme); + + /** + * Return an instance with the specified user information. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified user information. + * + * Password is optional, but the user information MUST include the + * user; an empty string for the user is equivalent to removing user + * information. + * + * @param string $user The user name to use for authority. + * @param null|string $password The password associated with $user. + * @return static A new instance with the specified user information. + */ + public function withUserInfo($user, $password = null); + + /** + * Return an instance with the specified host. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified host. + * + * An empty host value is equivalent to removing the host. + * + * @param string $host The hostname to use with the new instance. + * @return static A new instance with the specified host. + * @throws \InvalidArgumentException for invalid hostnames. + */ + public function withHost($host); + + /** + * Return an instance with the specified port. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified port. + * + * Implementations MUST raise an exception for ports outside the + * established TCP and UDP port ranges. + * + * A null value provided for the port is equivalent to removing the port + * information. + * + * @param null|int $port The port to use with the new instance; a null value + * removes the port information. + * @return static A new instance with the specified port. + * @throws \InvalidArgumentException for invalid ports. + */ + public function withPort($port); + + /** + * Return an instance with the specified path. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified path. + * + * The path can either be empty or absolute (starting with a slash) or + * rootless (not starting with a slash). Implementations MUST support all + * three syntaxes. + * + * If the path is intended to be domain-relative rather than path relative then + * it must begin with a slash ("/"). Paths not starting with a slash ("/") + * are assumed to be relative to some base path known to the application or + * consumer. + * + * Users can provide both encoded and decoded path characters. + * Implementations ensure the correct encoding as outlined in getPath(). + * + * @param string $path The path to use with the new instance. + * @return static A new instance with the specified path. + * @throws \InvalidArgumentException for invalid paths. + */ + public function withPath($path); + + /** + * Return an instance with the specified query string. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified query string. + * + * Users can provide both encoded and decoded query characters. + * Implementations ensure the correct encoding as outlined in getQuery(). + * + * An empty query string value is equivalent to removing the query string. + * + * @param string $query The query string to use with the new instance. + * @return static A new instance with the specified query string. + * @throws \InvalidArgumentException for invalid query strings. + */ + public function withQuery($query); + + /** + * Return an instance with the specified URI fragment. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified URI fragment. + * + * Users can provide both encoded and decoded fragment characters. + * Implementations ensure the correct encoding as outlined in getFragment(). + * + * An empty fragment value is equivalent to removing the fragment. + * + * @param string $fragment The fragment to use with the new instance. + * @return static A new instance with the specified fragment. + */ + public function withFragment($fragment); + + /** + * Return the string representation as a URI reference. + * + * Depending on which components of the URI are present, the resulting + * string is either a full URI or relative reference according to RFC 3986, + * Section 4.1. The method concatenates the various components of the URI, + * using the appropriate delimiters: + * + * - If a scheme is present, it MUST be suffixed by ":". + * - If an authority is present, it MUST be prefixed by "//". + * - The path can be concatenated without delimiters. But there are two + * cases where the path has to be adjusted to make the URI reference + * valid as PHP does not allow to throw an exception in __toString(): + * - If the path is rootless and an authority is present, the path MUST + * be prefixed by "/". + * - If the path is starting with more than one "/" and no authority is + * present, the starting slashes MUST be reduced to one. + * - If a query is present, it MUST be prefixed by "?". + * - If a fragment is present, it MUST be prefixed by "#". + * + * @see http://tools.ietf.org/html/rfc3986#section-4.1 + * @return string + */ + public function __toString(); +} diff --git a/modules/inpostizi/vendor/psr/simple-cache/.editorconfig b/modules/inpostizi/vendor/psr/simple-cache/.editorconfig new file mode 100644 index 00000000..48542cbb --- /dev/null +++ b/modules/inpostizi/vendor/psr/simple-cache/.editorconfig @@ -0,0 +1,12 @@ +; This file is for unifying the coding style for different editors and IDEs. +; More information at http://editorconfig.org + +root = true + +[*] +charset = utf-8 +indent_size = 4 +indent_style = space +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/modules/inpostizi/vendor/psr/simple-cache/LICENSE.md b/modules/inpostizi/vendor/psr/simple-cache/LICENSE.md new file mode 100644 index 00000000..e49a7c85 --- /dev/null +++ b/modules/inpostizi/vendor/psr/simple-cache/LICENSE.md @@ -0,0 +1,21 @@ +# The MIT License (MIT) + +Copyright (c) 2016 PHP Framework Interoperability Group + +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. diff --git a/modules/inpostizi/vendor/psr/simple-cache/README.md b/modules/inpostizi/vendor/psr/simple-cache/README.md new file mode 100644 index 00000000..43641d17 --- /dev/null +++ b/modules/inpostizi/vendor/psr/simple-cache/README.md @@ -0,0 +1,8 @@ +PHP FIG Simple Cache PSR +======================== + +This repository holds all interfaces related to PSR-16. + +Note that this is not a cache implementation of its own. It is merely an interface that describes a cache implementation. See [the specification](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-16-simple-cache.md) for more details. + +You can find implementations of the specification by looking for packages providing the [psr/simple-cache-implementation](https://packagist.org/providers/psr/simple-cache-implementation) virtual package. diff --git a/modules/inpostizi/vendor/psr/simple-cache/composer.json b/modules/inpostizi/vendor/psr/simple-cache/composer.json new file mode 100644 index 00000000..2978fa55 --- /dev/null +++ b/modules/inpostizi/vendor/psr/simple-cache/composer.json @@ -0,0 +1,25 @@ +{ + "name": "psr/simple-cache", + "description": "Common interfaces for simple caching", + "keywords": ["psr", "psr-16", "cache", "simple-cache", "caching"], + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "require": { + "php": ">=5.3.0" + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/modules/inpostizi/vendor/psr/simple-cache/src/CacheException.php b/modules/inpostizi/vendor/psr/simple-cache/src/CacheException.php new file mode 100644 index 00000000..eba53815 --- /dev/null +++ b/modules/inpostizi/vendor/psr/simple-cache/src/CacheException.php @@ -0,0 +1,10 @@ + value pairs. Cache keys that do not exist or are stale will have $default as value. + * + * @throws \Psr\SimpleCache\InvalidArgumentException + * MUST be thrown if $keys is neither an array nor a Traversable, + * or if any of the $keys are not a legal value. + */ + public function getMultiple($keys, $default = null); + + /** + * Persists a set of key => value pairs in the cache, with an optional TTL. + * + * @param iterable $values A list of key => value pairs for a multiple-set operation. + * @param null|int|\DateInterval $ttl Optional. The TTL value of this item. If no value is sent and + * the driver supports TTL then the library may set a default value + * for it or let the driver take care of that. + * + * @return bool True on success and false on failure. + * + * @throws \Psr\SimpleCache\InvalidArgumentException + * MUST be thrown if $values is neither an array nor a Traversable, + * or if any of the $values are not a legal value. + */ + public function setMultiple($values, $ttl = null); + + /** + * Deletes multiple cache items in a single operation. + * + * @param iterable $keys A list of string-based keys to be deleted. + * + * @return bool True if the items were successfully removed. False if there was an error. + * + * @throws \Psr\SimpleCache\InvalidArgumentException + * MUST be thrown if $keys is neither an array nor a Traversable, + * or if any of the $keys are not a legal value. + */ + public function deleteMultiple($keys); + + /** + * Determines whether an item is present in the cache. + * + * NOTE: It is recommended that has() is only to be used for cache warming type purposes + * and not to be used within your live applications operations for get/set, as this method + * is subject to a race condition where your has() will return true and immediately after, + * another script can remove it making the state of your app out of date. + * + * @param string $key The cache item key. + * + * @return bool + * + * @throws \Psr\SimpleCache\InvalidArgumentException + * MUST be thrown if the $key string is not a legal value. + */ + public function has($key); +} diff --git a/modules/inpostizi/vendor/psr/simple-cache/src/InvalidArgumentException.php b/modules/inpostizi/vendor/psr/simple-cache/src/InvalidArgumentException.php new file mode 100644 index 00000000..6a9524a2 --- /dev/null +++ b/modules/inpostizi/vendor/psr/simple-cache/src/InvalidArgumentException.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Mbstring; + +/** + * Partial mbstring implementation in PHP, iconv based, UTF-8 centric. + * + * Implemented: + * - mb_chr - Returns a specific character from its Unicode code point + * - mb_convert_encoding - Convert character encoding + * - mb_convert_variables - Convert character code in variable(s) + * - mb_decode_mimeheader - Decode string in MIME header field + * - mb_encode_mimeheader - Encode string for MIME header XXX NATIVE IMPLEMENTATION IS REALLY BUGGED + * - mb_decode_numericentity - Decode HTML numeric string reference to character + * - mb_encode_numericentity - Encode character to HTML numeric string reference + * - mb_convert_case - Perform case folding on a string + * - mb_detect_encoding - Detect character encoding + * - mb_get_info - Get internal settings of mbstring + * - mb_http_input - Detect HTTP input character encoding + * - mb_http_output - Set/Get HTTP output character encoding + * - mb_internal_encoding - Set/Get internal character encoding + * - mb_list_encodings - Returns an array of all supported encodings + * - mb_ord - Returns the Unicode code point of a character + * - mb_output_handler - Callback function converts character encoding in output buffer + * - mb_scrub - Replaces ill-formed byte sequences with substitute characters + * - mb_strlen - Get string length + * - mb_strpos - Find position of first occurrence of string in a string + * - mb_strrpos - Find position of last occurrence of a string in a string + * - mb_str_split - Convert a string to an array + * - mb_strtolower - Make a string lowercase + * - mb_strtoupper - Make a string uppercase + * - mb_substitute_character - Set/Get substitution character + * - mb_substr - Get part of string + * - mb_stripos - Finds position of first occurrence of a string within another, case insensitive + * - mb_stristr - Finds first occurrence of a string within another, case insensitive + * - mb_strrchr - Finds the last occurrence of a character in a string within another + * - mb_strrichr - Finds the last occurrence of a character in a string within another, case insensitive + * - mb_strripos - Finds position of last occurrence of a string within another, case insensitive + * - mb_strstr - Finds first occurrence of a string within another + * - mb_strwidth - Return width of string + * - mb_substr_count - Count the number of substring occurrences + * + * Not implemented: + * - mb_convert_kana - Convert "kana" one from another ("zen-kaku", "han-kaku" and more) + * - mb_ereg_* - Regular expression with multibyte support + * - mb_parse_str - Parse GET/POST/COOKIE data and set global variable + * - mb_preferred_mime_name - Get MIME charset string + * - mb_regex_encoding - Returns current encoding for multibyte regex as string + * - mb_regex_set_options - Set/Get the default options for mbregex functions + * - mb_send_mail - Send encoded mail + * - mb_split - Split multibyte string using regular expression + * - mb_strcut - Get part of string + * - mb_strimwidth - Get truncated string with specified width + * + * @author Nicolas Grekas + * + * @internal + */ +final class Mbstring +{ + public const MB_CASE_FOLD = \PHP_INT_MAX; + + private const SIMPLE_CASE_FOLD = [ + ['µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"], + ['μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1", 'ι'], + ]; + + private static $encodingList = ['ASCII', 'UTF-8']; + private static $language = 'neutral'; + private static $internalEncoding = 'UTF-8'; + + public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null) + { + if (\is_array($fromEncoding) || (null !== $fromEncoding && false !== strpos($fromEncoding, ','))) { + $fromEncoding = self::mb_detect_encoding($s, $fromEncoding); + } else { + $fromEncoding = self::getEncoding($fromEncoding); + } + + $toEncoding = self::getEncoding($toEncoding); + + if ('BASE64' === $fromEncoding) { + $s = base64_decode($s); + $fromEncoding = $toEncoding; + } + + if ('BASE64' === $toEncoding) { + return base64_encode($s); + } + + if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) { + if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) { + $fromEncoding = 'Windows-1252'; + } + if ('UTF-8' !== $fromEncoding) { + $s = iconv($fromEncoding, 'UTF-8//IGNORE', $s); + } + + return preg_replace_callback('/[\x80-\xFF]+/', [__CLASS__, 'html_encoding_callback'], $s); + } + + if ('HTML-ENTITIES' === $fromEncoding) { + $s = html_entity_decode($s, \ENT_COMPAT, 'UTF-8'); + $fromEncoding = 'UTF-8'; + } + + return iconv($fromEncoding, $toEncoding.'//IGNORE', $s); + } + + public static function mb_convert_variables($toEncoding, $fromEncoding, &...$vars) + { + $ok = true; + array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) { + if (false === $v = self::mb_convert_encoding($v, $toEncoding, $fromEncoding)) { + $ok = false; + } + }); + + return $ok ? $fromEncoding : false; + } + + public static function mb_decode_mimeheader($s) + { + return iconv_mime_decode($s, 2, self::$internalEncoding); + } + + public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null) + { + trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', \E_USER_WARNING); + } + + public static function mb_decode_numericentity($s, $convmap, $encoding = null) + { + if (null !== $s && !\is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) { + trigger_error('mb_decode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return null; + } + + if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) { + return false; + } + + if (null !== $encoding && !\is_scalar($encoding)) { + trigger_error('mb_decode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return ''; // Instead of null (cf. mb_encode_numericentity). + } + + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + $cnt = floor(\count($convmap) / 4) * 4; + + for ($i = 0; $i < $cnt; $i += 4) { + // collector_decode_htmlnumericentity ignores $convmap[$i + 3] + $convmap[$i] += $convmap[$i + 2]; + $convmap[$i + 1] += $convmap[$i + 2]; + } + + $s = preg_replace_callback('/&#(?:0*([0-9]+)|x0*([0-9a-fA-F]+))(?!&);?/', function (array $m) use ($cnt, $convmap) { + $c = isset($m[2]) ? (int) hexdec($m[2]) : $m[1]; + for ($i = 0; $i < $cnt; $i += 4) { + if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) { + return self::mb_chr($c - $convmap[$i + 2]); + } + } + + return $m[0]; + }, $s); + + if (null === $encoding) { + return $s; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $s); + } + + public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = false) + { + if (null !== $s && !\is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) { + trigger_error('mb_encode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return null; + } + + if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) { + return false; + } + + if (null !== $encoding && !\is_scalar($encoding)) { + trigger_error('mb_encode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return null; // Instead of '' (cf. mb_decode_numericentity). + } + + if (null !== $is_hex && !\is_scalar($is_hex)) { + trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, '.\gettype($s).' given', \E_USER_WARNING); + + return null; + } + + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; + + $cnt = floor(\count($convmap) / 4) * 4; + $i = 0; + $len = \strlen($s); + $result = ''; + + while ($i < $len) { + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + $c = self::mb_ord($uchr); + + for ($j = 0; $j < $cnt; $j += 4) { + if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) { + $cOffset = ($c + $convmap[$j + 2]) & $convmap[$j + 3]; + $result .= $is_hex ? sprintf('&#x%X;', $cOffset) : '&#'.$cOffset.';'; + continue 2; + } + } + $result .= $uchr; + } + + if (null === $encoding) { + return $result; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $result); + } + + public static function mb_convert_case($s, $mode, $encoding = null) + { + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + if (\MB_CASE_TITLE == $mode) { + static $titleRegexp = null; + if (null === $titleRegexp) { + $titleRegexp = self::getData('titleCaseRegexp'); + } + $s = preg_replace_callback($titleRegexp, [__CLASS__, 'title_case'], $s); + } else { + if (\MB_CASE_UPPER == $mode) { + static $upper = null; + if (null === $upper) { + $upper = self::getData('upperCase'); + } + $map = $upper; + } else { + if (self::MB_CASE_FOLD === $mode) { + static $caseFolding = null; + if (null === $caseFolding) { + $caseFolding = self::getData('caseFolding'); + } + $s = strtr($s, $caseFolding); + } + + static $lower = null; + if (null === $lower) { + $lower = self::getData('lowerCase'); + } + $map = $lower; + } + + static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; + + $i = 0; + $len = \strlen($s); + + while ($i < $len) { + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + + if (isset($map[$uchr])) { + $uchr = $map[$uchr]; + $nlen = \strlen($uchr); + + if ($nlen == $ulen) { + $nlen = $i; + do { + $s[--$nlen] = $uchr[--$ulen]; + } while ($ulen); + } else { + $s = substr_replace($s, $uchr, $i - $ulen, $ulen); + $len += $nlen - $ulen; + $i += $nlen - $ulen; + } + } + } + } + + if (null === $encoding) { + return $s; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $s); + } + + public static function mb_internal_encoding($encoding = null) + { + if (null === $encoding) { + return self::$internalEncoding; + } + + $normalizedEncoding = self::getEncoding($encoding); + + if ('UTF-8' === $normalizedEncoding || false !== @iconv($normalizedEncoding, $normalizedEncoding, ' ')) { + self::$internalEncoding = $normalizedEncoding; + + return true; + } + + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError(sprintf('Argument #1 ($encoding) must be a valid encoding, "%s" given', $encoding)); + } + + public static function mb_language($lang = null) + { + if (null === $lang) { + return self::$language; + } + + switch ($normalizedLang = strtolower($lang)) { + case 'uni': + case 'neutral': + self::$language = $normalizedLang; + + return true; + } + + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError(sprintf('Argument #1 ($language) must be a valid language, "%s" given', $lang)); + } + + public static function mb_list_encodings() + { + return ['UTF-8']; + } + + public static function mb_encoding_aliases($encoding) + { + switch (strtoupper($encoding)) { + case 'UTF8': + case 'UTF-8': + return ['utf8']; + } + + return false; + } + + public static function mb_check_encoding($var = null, $encoding = null) + { + if (PHP_VERSION_ID < 70200 && \is_array($var)) { + trigger_error('mb_check_encoding() expects parameter 1 to be string, array given', \E_USER_WARNING); + + return null; + } + + if (null === $encoding) { + if (null === $var) { + return false; + } + $encoding = self::$internalEncoding; + } + + if (!\is_array($var)) { + return self::mb_detect_encoding($var, [$encoding]) || false !== @iconv($encoding, $encoding, $var); + } + + foreach ($var as $key => $value) { + if (!self::mb_check_encoding($key, $encoding)) { + return false; + } + if (!self::mb_check_encoding($value, $encoding)) { + return false; + } + } + + return true; + + } + + public static function mb_detect_encoding($str, $encodingList = null, $strict = false) + { + if (null === $encodingList) { + $encodingList = self::$encodingList; + } else { + if (!\is_array($encodingList)) { + $encodingList = array_map('trim', explode(',', $encodingList)); + } + $encodingList = array_map('strtoupper', $encodingList); + } + + foreach ($encodingList as $enc) { + switch ($enc) { + case 'ASCII': + if (!preg_match('/[\x80-\xFF]/', $str)) { + return $enc; + } + break; + + case 'UTF8': + case 'UTF-8': + if (preg_match('//u', $str)) { + return 'UTF-8'; + } + break; + + default: + if (0 === strncmp($enc, 'ISO-8859-', 9)) { + return $enc; + } + } + } + + return false; + } + + public static function mb_detect_order($encodingList = null) + { + if (null === $encodingList) { + return self::$encodingList; + } + + if (!\is_array($encodingList)) { + $encodingList = array_map('trim', explode(',', $encodingList)); + } + $encodingList = array_map('strtoupper', $encodingList); + + foreach ($encodingList as $enc) { + switch ($enc) { + default: + if (strncmp($enc, 'ISO-8859-', 9)) { + return false; + } + // no break + case 'ASCII': + case 'UTF8': + case 'UTF-8': + } + } + + self::$encodingList = $encodingList; + + return true; + } + + public static function mb_strlen($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return \strlen($s); + } + + return @iconv_strlen($s, $encoding); + } + + public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strpos($haystack, $needle, $offset); + } + + $needle = (string) $needle; + if ('' === $needle) { + if (80000 > \PHP_VERSION_ID) { + trigger_error(__METHOD__.': Empty delimiter', \E_USER_WARNING); + + return false; + } + + return 0; + } + + return iconv_strpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strrpos($haystack, $needle, $offset); + } + + if ($offset != (int) $offset) { + $offset = 0; + } elseif ($offset = (int) $offset) { + if ($offset < 0) { + if (0 > $offset += self::mb_strlen($needle)) { + $haystack = self::mb_substr($haystack, 0, $offset, $encoding); + } + $offset = 0; + } else { + $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding); + } + } + + $pos = '' !== $needle || 80000 > \PHP_VERSION_ID + ? iconv_strrpos($haystack, $needle, $encoding) + : self::mb_strlen($haystack, $encoding); + + return false !== $pos ? $offset + $pos : false; + } + + public static function mb_str_split($string, $split_length = 1, $encoding = null) + { + if (null !== $string && !\is_scalar($string) && !(\is_object($string) && method_exists($string, '__toString'))) { + trigger_error('mb_str_split() expects parameter 1 to be string, '.\gettype($string).' given', \E_USER_WARNING); + + return null; + } + + if (1 > $split_length = (int) $split_length) { + if (80000 > \PHP_VERSION_ID) { + trigger_error('The length of each segment must be greater than zero', \E_USER_WARNING); + + return false; + } + + throw new \ValueError('Argument #2 ($length) must be greater than 0'); + } + + if (null === $encoding) { + $encoding = mb_internal_encoding(); + } + + if ('UTF-8' === $encoding = self::getEncoding($encoding)) { + $rx = '/('; + while (65535 < $split_length) { + $rx .= '.{65535}'; + $split_length -= 65535; + } + $rx .= '.{'.$split_length.'})/us'; + + return preg_split($rx, $string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY); + } + + $result = []; + $length = mb_strlen($string, $encoding); + + for ($i = 0; $i < $length; $i += $split_length) { + $result[] = mb_substr($string, $i, $split_length, $encoding); + } + + return $result; + } + + public static function mb_strtolower($s, $encoding = null) + { + return self::mb_convert_case($s, \MB_CASE_LOWER, $encoding); + } + + public static function mb_strtoupper($s, $encoding = null) + { + return self::mb_convert_case($s, \MB_CASE_UPPER, $encoding); + } + + public static function mb_substitute_character($c = null) + { + if (null === $c) { + return 'none'; + } + if (0 === strcasecmp($c, 'none')) { + return true; + } + if (80000 > \PHP_VERSION_ID) { + return false; + } + if (\is_int($c) || 'long' === $c || 'entity' === $c) { + return false; + } + + throw new \ValueError('Argument #1 ($substitute_character) must be "none", "long", "entity" or a valid codepoint'); + } + + public static function mb_substr($s, $start, $length = null, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return (string) substr($s, $start, null === $length ? 2147483647 : $length); + } + + if ($start < 0) { + $start = iconv_strlen($s, $encoding) + $start; + if ($start < 0) { + $start = 0; + } + } + + if (null === $length) { + $length = 2147483647; + } elseif ($length < 0) { + $length = iconv_strlen($s, $encoding) + $length - $start; + if ($length < 0) { + return ''; + } + } + + return (string) iconv_substr($s, $start, $length, $encoding); + } + + public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) + { + [$haystack, $needle] = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], [ + self::mb_convert_case($haystack, \MB_CASE_LOWER, $encoding), + self::mb_convert_case($needle, \MB_CASE_LOWER, $encoding), + ]); + + return self::mb_strpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_stristr($haystack, $needle, $part = false, $encoding = null) + { + $pos = self::mb_stripos($haystack, $needle, 0, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + $pos = strrpos($haystack, $needle); + } else { + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = iconv_strrpos($haystack, $needle, $encoding); + } + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null) + { + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = self::mb_strripos($haystack, $needle, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) + { + $haystack = self::mb_convert_case($haystack, \MB_CASE_LOWER, $encoding); + $needle = self::mb_convert_case($needle, \MB_CASE_LOWER, $encoding); + + $haystack = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], $haystack); + $needle = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], $needle); + + return self::mb_strrpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_strstr($haystack, $needle, $part = false, $encoding = null) + { + $pos = strpos($haystack, $needle); + if (false === $pos) { + return false; + } + if ($part) { + return substr($haystack, 0, $pos); + } + + return substr($haystack, $pos); + } + + public static function mb_get_info($type = 'all') + { + $info = [ + 'internal_encoding' => self::$internalEncoding, + 'http_output' => 'pass', + 'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)', + 'func_overload' => 0, + 'func_overload_list' => 'no overload', + 'mail_charset' => 'UTF-8', + 'mail_header_encoding' => 'BASE64', + 'mail_body_encoding' => 'BASE64', + 'illegal_chars' => 0, + 'encoding_translation' => 'Off', + 'language' => self::$language, + 'detect_order' => self::$encodingList, + 'substitute_character' => 'none', + 'strict_detection' => 'Off', + ]; + + if ('all' === $type) { + return $info; + } + if (isset($info[$type])) { + return $info[$type]; + } + + return false; + } + + public static function mb_http_input($type = '') + { + return false; + } + + public static function mb_http_output($encoding = null) + { + return null !== $encoding ? 'pass' === $encoding : 'pass'; + } + + public static function mb_strwidth($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + + if ('UTF-8' !== $encoding) { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + $s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide); + + return ($wide << 1) + iconv_strlen($s, 'UTF-8'); + } + + public static function mb_substr_count($haystack, $needle, $encoding = null) + { + return substr_count($haystack, $needle); + } + + public static function mb_output_handler($contents, $status) + { + return $contents; + } + + public static function mb_chr($code, $encoding = null) + { + if (0x80 > $code %= 0x200000) { + $s = \chr($code); + } elseif (0x800 > $code) { + $s = \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F); + } elseif (0x10000 > $code) { + $s = \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } else { + $s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } + + if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { + $s = mb_convert_encoding($s, $encoding, 'UTF-8'); + } + + return $s; + } + + public static function mb_ord($s, $encoding = null) + { + if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { + $s = mb_convert_encoding($s, 'UTF-8', $encoding); + } + + if (1 === \strlen($s)) { + return \ord($s); + } + + $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0; + if (0xF0 <= $code) { + return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80; + } + if (0xE0 <= $code) { + return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80; + } + if (0xC0 <= $code) { + return (($code - 0xC0) << 6) + $s[2] - 0x80; + } + + return $code; + } + + public static function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = \STR_PAD_RIGHT, string $encoding = null): string + { + if (!\in_array($pad_type, [\STR_PAD_RIGHT, \STR_PAD_LEFT, \STR_PAD_BOTH], true)) { + throw new \ValueError('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH'); + } + + if (null === $encoding) { + $encoding = self::mb_internal_encoding(); + } + + try { + $validEncoding = @self::mb_check_encoding('', $encoding); + } catch (\ValueError $e) { + throw new \ValueError(sprintf('mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given', $encoding)); + } + + // BC for PHP 7.3 and lower + if (!$validEncoding) { + throw new \ValueError(sprintf('mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given', $encoding)); + } + + if (self::mb_strlen($pad_string, $encoding) <= 0) { + throw new \ValueError('mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string'); + } + + $paddingRequired = $length - self::mb_strlen($string, $encoding); + + if ($paddingRequired < 1) { + return $string; + } + + switch ($pad_type) { + case \STR_PAD_LEFT: + return self::mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding).$string; + case \STR_PAD_RIGHT: + return $string.self::mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding); + default: + $leftPaddingLength = floor($paddingRequired / 2); + $rightPaddingLength = $paddingRequired - $leftPaddingLength; + + return self::mb_substr(str_repeat($pad_string, $leftPaddingLength), 0, $leftPaddingLength, $encoding).$string.self::mb_substr(str_repeat($pad_string, $rightPaddingLength), 0, $rightPaddingLength, $encoding); + } + } + + private static function getSubpart($pos, $part, $haystack, $encoding) + { + if (false === $pos) { + return false; + } + if ($part) { + return self::mb_substr($haystack, 0, $pos, $encoding); + } + + return self::mb_substr($haystack, $pos, null, $encoding); + } + + private static function html_encoding_callback(array $m) + { + $i = 1; + $entities = ''; + $m = unpack('C*', htmlentities($m[0], \ENT_COMPAT, 'UTF-8')); + + while (isset($m[$i])) { + if (0x80 > $m[$i]) { + $entities .= \chr($m[$i++]); + continue; + } + if (0xF0 <= $m[$i]) { + $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } elseif (0xE0 <= $m[$i]) { + $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } else { + $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80; + } + + $entities .= '&#'.$c.';'; + } + + return $entities; + } + + private static function title_case(array $s) + { + return self::mb_convert_case($s[1], \MB_CASE_UPPER, 'UTF-8').self::mb_convert_case($s[2], \MB_CASE_LOWER, 'UTF-8'); + } + + private static function getData($file) + { + if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) { + return require $file; + } + + return false; + } + + private static function getEncoding($encoding) + { + if (null === $encoding) { + return self::$internalEncoding; + } + + if ('UTF-8' === $encoding) { + return 'UTF-8'; + } + + $encoding = strtoupper($encoding); + + if ('8BIT' === $encoding || 'BINARY' === $encoding) { + return 'CP850'; + } + + if ('UTF8' === $encoding) { + return 'UTF-8'; + } + + return $encoding; + } +} diff --git a/modules/inpostizi/vendor/symfony/polyfill-mbstring/README.md b/modules/inpostizi/vendor/symfony/polyfill-mbstring/README.md new file mode 100644 index 00000000..478b40da --- /dev/null +++ b/modules/inpostizi/vendor/symfony/polyfill-mbstring/README.md @@ -0,0 +1,13 @@ +Symfony Polyfill / Mbstring +=========================== + +This component provides a partial, native PHP implementation for the +[Mbstring](https://php.net/mbstring) extension. + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/modules/inpostizi/vendor/symfony/polyfill-mbstring/Resources/unidata/caseFolding.php b/modules/inpostizi/vendor/symfony/polyfill-mbstring/Resources/unidata/caseFolding.php new file mode 100644 index 00000000..512bba0b --- /dev/null +++ b/modules/inpostizi/vendor/symfony/polyfill-mbstring/Resources/unidata/caseFolding.php @@ -0,0 +1,119 @@ + 'i̇', + 'µ' => 'μ', + 'ſ' => 's', + 'ͅ' => 'ι', + 'ς' => 'σ', + 'ϐ' => 'β', + 'ϑ' => 'θ', + 'ϕ' => 'φ', + 'ϖ' => 'π', + 'ϰ' => 'κ', + 'ϱ' => 'ρ', + 'ϵ' => 'ε', + 'ẛ' => 'ṡ', + 'ι' => 'ι', + 'ß' => 'ss', + 'ʼn' => 'ʼn', + 'ǰ' => 'ǰ', + 'ΐ' => 'ΐ', + 'ΰ' => 'ΰ', + 'և' => 'եւ', + 'ẖ' => 'ẖ', + 'ẗ' => 'ẗ', + 'ẘ' => 'ẘ', + 'ẙ' => 'ẙ', + 'ẚ' => 'aʾ', + 'ẞ' => 'ss', + 'ὐ' => 'ὐ', + 'ὒ' => 'ὒ', + 'ὔ' => 'ὔ', + 'ὖ' => 'ὖ', + 'ᾀ' => 'ἀι', + 'ᾁ' => 'ἁι', + 'ᾂ' => 'ἂι', + 'ᾃ' => 'ἃι', + 'ᾄ' => 'ἄι', + 'ᾅ' => 'ἅι', + 'ᾆ' => 'ἆι', + 'ᾇ' => 'ἇι', + 'ᾈ' => 'ἀι', + 'ᾉ' => 'ἁι', + 'ᾊ' => 'ἂι', + 'ᾋ' => 'ἃι', + 'ᾌ' => 'ἄι', + 'ᾍ' => 'ἅι', + 'ᾎ' => 'ἆι', + 'ᾏ' => 'ἇι', + 'ᾐ' => 'ἠι', + 'ᾑ' => 'ἡι', + 'ᾒ' => 'ἢι', + 'ᾓ' => 'ἣι', + 'ᾔ' => 'ἤι', + 'ᾕ' => 'ἥι', + 'ᾖ' => 'ἦι', + 'ᾗ' => 'ἧι', + 'ᾘ' => 'ἠι', + 'ᾙ' => 'ἡι', + 'ᾚ' => 'ἢι', + 'ᾛ' => 'ἣι', + 'ᾜ' => 'ἤι', + 'ᾝ' => 'ἥι', + 'ᾞ' => 'ἦι', + 'ᾟ' => 'ἧι', + 'ᾠ' => 'ὠι', + 'ᾡ' => 'ὡι', + 'ᾢ' => 'ὢι', + 'ᾣ' => 'ὣι', + 'ᾤ' => 'ὤι', + 'ᾥ' => 'ὥι', + 'ᾦ' => 'ὦι', + 'ᾧ' => 'ὧι', + 'ᾨ' => 'ὠι', + 'ᾩ' => 'ὡι', + 'ᾪ' => 'ὢι', + 'ᾫ' => 'ὣι', + 'ᾬ' => 'ὤι', + 'ᾭ' => 'ὥι', + 'ᾮ' => 'ὦι', + 'ᾯ' => 'ὧι', + 'ᾲ' => 'ὰι', + 'ᾳ' => 'αι', + 'ᾴ' => 'άι', + 'ᾶ' => 'ᾶ', + 'ᾷ' => 'ᾶι', + 'ᾼ' => 'αι', + 'ῂ' => 'ὴι', + 'ῃ' => 'ηι', + 'ῄ' => 'ήι', + 'ῆ' => 'ῆ', + 'ῇ' => 'ῆι', + 'ῌ' => 'ηι', + 'ῒ' => 'ῒ', + 'ῖ' => 'ῖ', + 'ῗ' => 'ῗ', + 'ῢ' => 'ῢ', + 'ῤ' => 'ῤ', + 'ῦ' => 'ῦ', + 'ῧ' => 'ῧ', + 'ῲ' => 'ὼι', + 'ῳ' => 'ωι', + 'ῴ' => 'ώι', + 'ῶ' => 'ῶ', + 'ῷ' => 'ῶι', + 'ῼ' => 'ωι', + 'ff' => 'ff', + 'fi' => 'fi', + 'fl' => 'fl', + 'ffi' => 'ffi', + 'ffl' => 'ffl', + 'ſt' => 'st', + 'st' => 'st', + 'ﬓ' => 'մն', + 'ﬔ' => 'մե', + 'ﬕ' => 'մի', + 'ﬖ' => 'վն', + 'ﬗ' => 'մխ', +]; diff --git a/modules/inpostizi/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php b/modules/inpostizi/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php new file mode 100644 index 00000000..fac60b08 --- /dev/null +++ b/modules/inpostizi/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php @@ -0,0 +1,1397 @@ + 'a', + 'B' => 'b', + 'C' => 'c', + 'D' => 'd', + 'E' => 'e', + 'F' => 'f', + 'G' => 'g', + 'H' => 'h', + 'I' => 'i', + 'J' => 'j', + 'K' => 'k', + 'L' => 'l', + 'M' => 'm', + 'N' => 'n', + 'O' => 'o', + 'P' => 'p', + 'Q' => 'q', + 'R' => 'r', + 'S' => 's', + 'T' => 't', + 'U' => 'u', + 'V' => 'v', + 'W' => 'w', + 'X' => 'x', + 'Y' => 'y', + 'Z' => 'z', + 'À' => 'à', + 'Á' => 'á', + 'Â' => 'â', + 'Ã' => 'ã', + 'Ä' => 'ä', + 'Å' => 'å', + 'Æ' => 'æ', + 'Ç' => 'ç', + 'È' => 'è', + 'É' => 'é', + 'Ê' => 'ê', + 'Ë' => 'ë', + 'Ì' => 'ì', + 'Í' => 'í', + 'Î' => 'î', + 'Ï' => 'ï', + 'Ð' => 'ð', + 'Ñ' => 'ñ', + 'Ò' => 'ò', + 'Ó' => 'ó', + 'Ô' => 'ô', + 'Õ' => 'õ', + 'Ö' => 'ö', + 'Ø' => 'ø', + 'Ù' => 'ù', + 'Ú' => 'ú', + 'Û' => 'û', + 'Ü' => 'ü', + 'Ý' => 'ý', + 'Þ' => 'þ', + 'Ā' => 'ā', + 'Ă' => 'ă', + 'Ą' => 'ą', + 'Ć' => 'ć', + 'Ĉ' => 'ĉ', + 'Ċ' => 'ċ', + 'Č' => 'č', + 'Ď' => 'ď', + 'Đ' => 'đ', + 'Ē' => 'ē', + 'Ĕ' => 'ĕ', + 'Ė' => 'ė', + 'Ę' => 'ę', + 'Ě' => 'ě', + 'Ĝ' => 'ĝ', + 'Ğ' => 'ğ', + 'Ġ' => 'ġ', + 'Ģ' => 'ģ', + 'Ĥ' => 'ĥ', + 'Ħ' => 'ħ', + 'Ĩ' => 'ĩ', + 'Ī' => 'ī', + 'Ĭ' => 'ĭ', + 'Į' => 'į', + 'İ' => 'i̇', + 'IJ' => 'ij', + 'Ĵ' => 'ĵ', + 'Ķ' => 'ķ', + 'Ĺ' => 'ĺ', + 'Ļ' => 'ļ', + 'Ľ' => 'ľ', + 'Ŀ' => 'ŀ', + 'Ł' => 'ł', + 'Ń' => 'ń', + 'Ņ' => 'ņ', + 'Ň' => 'ň', + 'Ŋ' => 'ŋ', + 'Ō' => 'ō', + 'Ŏ' => 'ŏ', + 'Ő' => 'ő', + 'Œ' => 'œ', + 'Ŕ' => 'ŕ', + 'Ŗ' => 'ŗ', + 'Ř' => 'ř', + 'Ś' => 'ś', + 'Ŝ' => 'ŝ', + 'Ş' => 'ş', + 'Š' => 'š', + 'Ţ' => 'ţ', + 'Ť' => 'ť', + 'Ŧ' => 'ŧ', + 'Ũ' => 'ũ', + 'Ū' => 'ū', + 'Ŭ' => 'ŭ', + 'Ů' => 'ů', + 'Ű' => 'ű', + 'Ų' => 'ų', + 'Ŵ' => 'ŵ', + 'Ŷ' => 'ŷ', + 'Ÿ' => 'ÿ', + 'Ź' => 'ź', + 'Ż' => 'ż', + 'Ž' => 'ž', + 'Ɓ' => 'ɓ', + 'Ƃ' => 'ƃ', + 'Ƅ' => 'ƅ', + 'Ɔ' => 'ɔ', + 'Ƈ' => 'ƈ', + 'Ɖ' => 'ɖ', + 'Ɗ' => 'ɗ', + 'Ƌ' => 'ƌ', + 'Ǝ' => 'ǝ', + 'Ə' => 'ə', + 'Ɛ' => 'ɛ', + 'Ƒ' => 'ƒ', + 'Ɠ' => 'ɠ', + 'Ɣ' => 'ɣ', + 'Ɩ' => 'ɩ', + 'Ɨ' => 'ɨ', + 'Ƙ' => 'ƙ', + 'Ɯ' => 'ɯ', + 'Ɲ' => 'ɲ', + 'Ɵ' => 'ɵ', + 'Ơ' => 'ơ', + 'Ƣ' => 'ƣ', + 'Ƥ' => 'ƥ', + 'Ʀ' => 'ʀ', + 'Ƨ' => 'ƨ', + 'Ʃ' => 'ʃ', + 'Ƭ' => 'ƭ', + 'Ʈ' => 'ʈ', + 'Ư' => 'ư', + 'Ʊ' => 'ʊ', + 'Ʋ' => 'ʋ', + 'Ƴ' => 'ƴ', + 'Ƶ' => 'ƶ', + 'Ʒ' => 'ʒ', + 'Ƹ' => 'ƹ', + 'Ƽ' => 'ƽ', + 'DŽ' => 'dž', + 'Dž' => 'dž', + 'LJ' => 'lj', + 'Lj' => 'lj', + 'NJ' => 'nj', + 'Nj' => 'nj', + 'Ǎ' => 'ǎ', + 'Ǐ' => 'ǐ', + 'Ǒ' => 'ǒ', + 'Ǔ' => 'ǔ', + 'Ǖ' => 'ǖ', + 'Ǘ' => 'ǘ', + 'Ǚ' => 'ǚ', + 'Ǜ' => 'ǜ', + 'Ǟ' => 'ǟ', + 'Ǡ' => 'ǡ', + 'Ǣ' => 'ǣ', + 'Ǥ' => 'ǥ', + 'Ǧ' => 'ǧ', + 'Ǩ' => 'ǩ', + 'Ǫ' => 'ǫ', + 'Ǭ' => 'ǭ', + 'Ǯ' => 'ǯ', + 'DZ' => 'dz', + 'Dz' => 'dz', + 'Ǵ' => 'ǵ', + 'Ƕ' => 'ƕ', + 'Ƿ' => 'ƿ', + 'Ǹ' => 'ǹ', + 'Ǻ' => 'ǻ', + 'Ǽ' => 'ǽ', + 'Ǿ' => 'ǿ', + 'Ȁ' => 'ȁ', + 'Ȃ' => 'ȃ', + 'Ȅ' => 'ȅ', + 'Ȇ' => 'ȇ', + 'Ȉ' => 'ȉ', + 'Ȋ' => 'ȋ', + 'Ȍ' => 'ȍ', + 'Ȏ' => 'ȏ', + 'Ȑ' => 'ȑ', + 'Ȓ' => 'ȓ', + 'Ȕ' => 'ȕ', + 'Ȗ' => 'ȗ', + 'Ș' => 'ș', + 'Ț' => 'ț', + 'Ȝ' => 'ȝ', + 'Ȟ' => 'ȟ', + 'Ƞ' => 'ƞ', + 'Ȣ' => 'ȣ', + 'Ȥ' => 'ȥ', + 'Ȧ' => 'ȧ', + 'Ȩ' => 'ȩ', + 'Ȫ' => 'ȫ', + 'Ȭ' => 'ȭ', + 'Ȯ' => 'ȯ', + 'Ȱ' => 'ȱ', + 'Ȳ' => 'ȳ', + 'Ⱥ' => 'ⱥ', + 'Ȼ' => 'ȼ', + 'Ƚ' => 'ƚ', + 'Ⱦ' => 'ⱦ', + 'Ɂ' => 'ɂ', + 'Ƀ' => 'ƀ', + 'Ʉ' => 'ʉ', + 'Ʌ' => 'ʌ', + 'Ɇ' => 'ɇ', + 'Ɉ' => 'ɉ', + 'Ɋ' => 'ɋ', + 'Ɍ' => 'ɍ', + 'Ɏ' => 'ɏ', + 'Ͱ' => 'ͱ', + 'Ͳ' => 'ͳ', + 'Ͷ' => 'ͷ', + 'Ϳ' => 'ϳ', + 'Ά' => 'ά', + 'Έ' => 'έ', + 'Ή' => 'ή', + 'Ί' => 'ί', + 'Ό' => 'ό', + 'Ύ' => 'ύ', + 'Ώ' => 'ώ', + 'Α' => 'α', + 'Β' => 'β', + 'Γ' => 'γ', + 'Δ' => 'δ', + 'Ε' => 'ε', + 'Ζ' => 'ζ', + 'Η' => 'η', + 'Θ' => 'θ', + 'Ι' => 'ι', + 'Κ' => 'κ', + 'Λ' => 'λ', + 'Μ' => 'μ', + 'Ν' => 'ν', + 'Ξ' => 'ξ', + 'Ο' => 'ο', + 'Π' => 'π', + 'Ρ' => 'ρ', + 'Σ' => 'σ', + 'Τ' => 'τ', + 'Υ' => 'υ', + 'Φ' => 'φ', + 'Χ' => 'χ', + 'Ψ' => 'ψ', + 'Ω' => 'ω', + 'Ϊ' => 'ϊ', + 'Ϋ' => 'ϋ', + 'Ϗ' => 'ϗ', + 'Ϙ' => 'ϙ', + 'Ϛ' => 'ϛ', + 'Ϝ' => 'ϝ', + 'Ϟ' => 'ϟ', + 'Ϡ' => 'ϡ', + 'Ϣ' => 'ϣ', + 'Ϥ' => 'ϥ', + 'Ϧ' => 'ϧ', + 'Ϩ' => 'ϩ', + 'Ϫ' => 'ϫ', + 'Ϭ' => 'ϭ', + 'Ϯ' => 'ϯ', + 'ϴ' => 'θ', + 'Ϸ' => 'ϸ', + 'Ϲ' => 'ϲ', + 'Ϻ' => 'ϻ', + 'Ͻ' => 'ͻ', + 'Ͼ' => 'ͼ', + 'Ͽ' => 'ͽ', + 'Ѐ' => 'ѐ', + 'Ё' => 'ё', + 'Ђ' => 'ђ', + 'Ѓ' => 'ѓ', + 'Є' => 'є', + 'Ѕ' => 'ѕ', + 'І' => 'і', + 'Ї' => 'ї', + 'Ј' => 'ј', + 'Љ' => 'љ', + 'Њ' => 'њ', + 'Ћ' => 'ћ', + 'Ќ' => 'ќ', + 'Ѝ' => 'ѝ', + 'Ў' => 'ў', + 'Џ' => 'џ', + 'А' => 'а', + 'Б' => 'б', + 'В' => 'в', + 'Г' => 'г', + 'Д' => 'д', + 'Е' => 'е', + 'Ж' => 'ж', + 'З' => 'з', + 'И' => 'и', + 'Й' => 'й', + 'К' => 'к', + 'Л' => 'л', + 'М' => 'м', + 'Н' => 'н', + 'О' => 'о', + 'П' => 'п', + 'Р' => 'р', + 'С' => 'с', + 'Т' => 'т', + 'У' => 'у', + 'Ф' => 'ф', + 'Х' => 'х', + 'Ц' => 'ц', + 'Ч' => 'ч', + 'Ш' => 'ш', + 'Щ' => 'щ', + 'Ъ' => 'ъ', + 'Ы' => 'ы', + 'Ь' => 'ь', + 'Э' => 'э', + 'Ю' => 'ю', + 'Я' => 'я', + 'Ѡ' => 'ѡ', + 'Ѣ' => 'ѣ', + 'Ѥ' => 'ѥ', + 'Ѧ' => 'ѧ', + 'Ѩ' => 'ѩ', + 'Ѫ' => 'ѫ', + 'Ѭ' => 'ѭ', + 'Ѯ' => 'ѯ', + 'Ѱ' => 'ѱ', + 'Ѳ' => 'ѳ', + 'Ѵ' => 'ѵ', + 'Ѷ' => 'ѷ', + 'Ѹ' => 'ѹ', + 'Ѻ' => 'ѻ', + 'Ѽ' => 'ѽ', + 'Ѿ' => 'ѿ', + 'Ҁ' => 'ҁ', + 'Ҋ' => 'ҋ', + 'Ҍ' => 'ҍ', + 'Ҏ' => 'ҏ', + 'Ґ' => 'ґ', + 'Ғ' => 'ғ', + 'Ҕ' => 'ҕ', + 'Җ' => 'җ', + 'Ҙ' => 'ҙ', + 'Қ' => 'қ', + 'Ҝ' => 'ҝ', + 'Ҟ' => 'ҟ', + 'Ҡ' => 'ҡ', + 'Ң' => 'ң', + 'Ҥ' => 'ҥ', + 'Ҧ' => 'ҧ', + 'Ҩ' => 'ҩ', + 'Ҫ' => 'ҫ', + 'Ҭ' => 'ҭ', + 'Ү' => 'ү', + 'Ұ' => 'ұ', + 'Ҳ' => 'ҳ', + 'Ҵ' => 'ҵ', + 'Ҷ' => 'ҷ', + 'Ҹ' => 'ҹ', + 'Һ' => 'һ', + 'Ҽ' => 'ҽ', + 'Ҿ' => 'ҿ', + 'Ӏ' => 'ӏ', + 'Ӂ' => 'ӂ', + 'Ӄ' => 'ӄ', + 'Ӆ' => 'ӆ', + 'Ӈ' => 'ӈ', + 'Ӊ' => 'ӊ', + 'Ӌ' => 'ӌ', + 'Ӎ' => 'ӎ', + 'Ӑ' => 'ӑ', + 'Ӓ' => 'ӓ', + 'Ӕ' => 'ӕ', + 'Ӗ' => 'ӗ', + 'Ә' => 'ә', + 'Ӛ' => 'ӛ', + 'Ӝ' => 'ӝ', + 'Ӟ' => 'ӟ', + 'Ӡ' => 'ӡ', + 'Ӣ' => 'ӣ', + 'Ӥ' => 'ӥ', + 'Ӧ' => 'ӧ', + 'Ө' => 'ө', + 'Ӫ' => 'ӫ', + 'Ӭ' => 'ӭ', + 'Ӯ' => 'ӯ', + 'Ӱ' => 'ӱ', + 'Ӳ' => 'ӳ', + 'Ӵ' => 'ӵ', + 'Ӷ' => 'ӷ', + 'Ӹ' => 'ӹ', + 'Ӻ' => 'ӻ', + 'Ӽ' => 'ӽ', + 'Ӿ' => 'ӿ', + 'Ԁ' => 'ԁ', + 'Ԃ' => 'ԃ', + 'Ԅ' => 'ԅ', + 'Ԇ' => 'ԇ', + 'Ԉ' => 'ԉ', + 'Ԋ' => 'ԋ', + 'Ԍ' => 'ԍ', + 'Ԏ' => 'ԏ', + 'Ԑ' => 'ԑ', + 'Ԓ' => 'ԓ', + 'Ԕ' => 'ԕ', + 'Ԗ' => 'ԗ', + 'Ԙ' => 'ԙ', + 'Ԛ' => 'ԛ', + 'Ԝ' => 'ԝ', + 'Ԟ' => 'ԟ', + 'Ԡ' => 'ԡ', + 'Ԣ' => 'ԣ', + 'Ԥ' => 'ԥ', + 'Ԧ' => 'ԧ', + 'Ԩ' => 'ԩ', + 'Ԫ' => 'ԫ', + 'Ԭ' => 'ԭ', + 'Ԯ' => 'ԯ', + 'Ա' => 'ա', + 'Բ' => 'բ', + 'Գ' => 'գ', + 'Դ' => 'դ', + 'Ե' => 'ե', + 'Զ' => 'զ', + 'Է' => 'է', + 'Ը' => 'ը', + 'Թ' => 'թ', + 'Ժ' => 'ժ', + 'Ի' => 'ի', + 'Լ' => 'լ', + 'Խ' => 'խ', + 'Ծ' => 'ծ', + 'Կ' => 'կ', + 'Հ' => 'հ', + 'Ձ' => 'ձ', + 'Ղ' => 'ղ', + 'Ճ' => 'ճ', + 'Մ' => 'մ', + 'Յ' => 'յ', + 'Ն' => 'ն', + 'Շ' => 'շ', + 'Ո' => 'ո', + 'Չ' => 'չ', + 'Պ' => 'պ', + 'Ջ' => 'ջ', + 'Ռ' => 'ռ', + 'Ս' => 'ս', + 'Վ' => 'վ', + 'Տ' => 'տ', + 'Ր' => 'ր', + 'Ց' => 'ց', + 'Ւ' => 'ւ', + 'Փ' => 'փ', + 'Ք' => 'ք', + 'Օ' => 'օ', + 'Ֆ' => 'ֆ', + 'Ⴀ' => 'ⴀ', + 'Ⴁ' => 'ⴁ', + 'Ⴂ' => 'ⴂ', + 'Ⴃ' => 'ⴃ', + 'Ⴄ' => 'ⴄ', + 'Ⴅ' => 'ⴅ', + 'Ⴆ' => 'ⴆ', + 'Ⴇ' => 'ⴇ', + 'Ⴈ' => 'ⴈ', + 'Ⴉ' => 'ⴉ', + 'Ⴊ' => 'ⴊ', + 'Ⴋ' => 'ⴋ', + 'Ⴌ' => 'ⴌ', + 'Ⴍ' => 'ⴍ', + 'Ⴎ' => 'ⴎ', + 'Ⴏ' => 'ⴏ', + 'Ⴐ' => 'ⴐ', + 'Ⴑ' => 'ⴑ', + 'Ⴒ' => 'ⴒ', + 'Ⴓ' => 'ⴓ', + 'Ⴔ' => 'ⴔ', + 'Ⴕ' => 'ⴕ', + 'Ⴖ' => 'ⴖ', + 'Ⴗ' => 'ⴗ', + 'Ⴘ' => 'ⴘ', + 'Ⴙ' => 'ⴙ', + 'Ⴚ' => 'ⴚ', + 'Ⴛ' => 'ⴛ', + 'Ⴜ' => 'ⴜ', + 'Ⴝ' => 'ⴝ', + 'Ⴞ' => 'ⴞ', + 'Ⴟ' => 'ⴟ', + 'Ⴠ' => 'ⴠ', + 'Ⴡ' => 'ⴡ', + 'Ⴢ' => 'ⴢ', + 'Ⴣ' => 'ⴣ', + 'Ⴤ' => 'ⴤ', + 'Ⴥ' => 'ⴥ', + 'Ⴧ' => 'ⴧ', + 'Ⴭ' => 'ⴭ', + 'Ꭰ' => 'ꭰ', + 'Ꭱ' => 'ꭱ', + 'Ꭲ' => 'ꭲ', + 'Ꭳ' => 'ꭳ', + 'Ꭴ' => 'ꭴ', + 'Ꭵ' => 'ꭵ', + 'Ꭶ' => 'ꭶ', + 'Ꭷ' => 'ꭷ', + 'Ꭸ' => 'ꭸ', + 'Ꭹ' => 'ꭹ', + 'Ꭺ' => 'ꭺ', + 'Ꭻ' => 'ꭻ', + 'Ꭼ' => 'ꭼ', + 'Ꭽ' => 'ꭽ', + 'Ꭾ' => 'ꭾ', + 'Ꭿ' => 'ꭿ', + 'Ꮀ' => 'ꮀ', + 'Ꮁ' => 'ꮁ', + 'Ꮂ' => 'ꮂ', + 'Ꮃ' => 'ꮃ', + 'Ꮄ' => 'ꮄ', + 'Ꮅ' => 'ꮅ', + 'Ꮆ' => 'ꮆ', + 'Ꮇ' => 'ꮇ', + 'Ꮈ' => 'ꮈ', + 'Ꮉ' => 'ꮉ', + 'Ꮊ' => 'ꮊ', + 'Ꮋ' => 'ꮋ', + 'Ꮌ' => 'ꮌ', + 'Ꮍ' => 'ꮍ', + 'Ꮎ' => 'ꮎ', + 'Ꮏ' => 'ꮏ', + 'Ꮐ' => 'ꮐ', + 'Ꮑ' => 'ꮑ', + 'Ꮒ' => 'ꮒ', + 'Ꮓ' => 'ꮓ', + 'Ꮔ' => 'ꮔ', + 'Ꮕ' => 'ꮕ', + 'Ꮖ' => 'ꮖ', + 'Ꮗ' => 'ꮗ', + 'Ꮘ' => 'ꮘ', + 'Ꮙ' => 'ꮙ', + 'Ꮚ' => 'ꮚ', + 'Ꮛ' => 'ꮛ', + 'Ꮜ' => 'ꮜ', + 'Ꮝ' => 'ꮝ', + 'Ꮞ' => 'ꮞ', + 'Ꮟ' => 'ꮟ', + 'Ꮠ' => 'ꮠ', + 'Ꮡ' => 'ꮡ', + 'Ꮢ' => 'ꮢ', + 'Ꮣ' => 'ꮣ', + 'Ꮤ' => 'ꮤ', + 'Ꮥ' => 'ꮥ', + 'Ꮦ' => 'ꮦ', + 'Ꮧ' => 'ꮧ', + 'Ꮨ' => 'ꮨ', + 'Ꮩ' => 'ꮩ', + 'Ꮪ' => 'ꮪ', + 'Ꮫ' => 'ꮫ', + 'Ꮬ' => 'ꮬ', + 'Ꮭ' => 'ꮭ', + 'Ꮮ' => 'ꮮ', + 'Ꮯ' => 'ꮯ', + 'Ꮰ' => 'ꮰ', + 'Ꮱ' => 'ꮱ', + 'Ꮲ' => 'ꮲ', + 'Ꮳ' => 'ꮳ', + 'Ꮴ' => 'ꮴ', + 'Ꮵ' => 'ꮵ', + 'Ꮶ' => 'ꮶ', + 'Ꮷ' => 'ꮷ', + 'Ꮸ' => 'ꮸ', + 'Ꮹ' => 'ꮹ', + 'Ꮺ' => 'ꮺ', + 'Ꮻ' => 'ꮻ', + 'Ꮼ' => 'ꮼ', + 'Ꮽ' => 'ꮽ', + 'Ꮾ' => 'ꮾ', + 'Ꮿ' => 'ꮿ', + 'Ᏸ' => 'ᏸ', + 'Ᏹ' => 'ᏹ', + 'Ᏺ' => 'ᏺ', + 'Ᏻ' => 'ᏻ', + 'Ᏼ' => 'ᏼ', + 'Ᏽ' => 'ᏽ', + 'Ა' => 'ა', + 'Ბ' => 'ბ', + 'Გ' => 'გ', + 'Დ' => 'დ', + 'Ე' => 'ე', + 'Ვ' => 'ვ', + 'Ზ' => 'ზ', + 'Თ' => 'თ', + 'Ი' => 'ი', + 'Კ' => 'კ', + 'Ლ' => 'ლ', + 'Მ' => 'მ', + 'Ნ' => 'ნ', + 'Ო' => 'ო', + 'Პ' => 'პ', + 'Ჟ' => 'ჟ', + 'Რ' => 'რ', + 'Ს' => 'ს', + 'Ტ' => 'ტ', + 'Უ' => 'უ', + 'Ფ' => 'ფ', + 'Ქ' => 'ქ', + 'Ღ' => 'ღ', + 'Ყ' => 'ყ', + 'Შ' => 'შ', + 'Ჩ' => 'ჩ', + 'Ც' => 'ც', + 'Ძ' => 'ძ', + 'Წ' => 'წ', + 'Ჭ' => 'ჭ', + 'Ხ' => 'ხ', + 'Ჯ' => 'ჯ', + 'Ჰ' => 'ჰ', + 'Ჱ' => 'ჱ', + 'Ჲ' => 'ჲ', + 'Ჳ' => 'ჳ', + 'Ჴ' => 'ჴ', + 'Ჵ' => 'ჵ', + 'Ჶ' => 'ჶ', + 'Ჷ' => 'ჷ', + 'Ჸ' => 'ჸ', + 'Ჹ' => 'ჹ', + 'Ჺ' => 'ჺ', + 'Ჽ' => 'ჽ', + 'Ჾ' => 'ჾ', + 'Ჿ' => 'ჿ', + 'Ḁ' => 'ḁ', + 'Ḃ' => 'ḃ', + 'Ḅ' => 'ḅ', + 'Ḇ' => 'ḇ', + 'Ḉ' => 'ḉ', + 'Ḋ' => 'ḋ', + 'Ḍ' => 'ḍ', + 'Ḏ' => 'ḏ', + 'Ḑ' => 'ḑ', + 'Ḓ' => 'ḓ', + 'Ḕ' => 'ḕ', + 'Ḗ' => 'ḗ', + 'Ḙ' => 'ḙ', + 'Ḛ' => 'ḛ', + 'Ḝ' => 'ḝ', + 'Ḟ' => 'ḟ', + 'Ḡ' => 'ḡ', + 'Ḣ' => 'ḣ', + 'Ḥ' => 'ḥ', + 'Ḧ' => 'ḧ', + 'Ḩ' => 'ḩ', + 'Ḫ' => 'ḫ', + 'Ḭ' => 'ḭ', + 'Ḯ' => 'ḯ', + 'Ḱ' => 'ḱ', + 'Ḳ' => 'ḳ', + 'Ḵ' => 'ḵ', + 'Ḷ' => 'ḷ', + 'Ḹ' => 'ḹ', + 'Ḻ' => 'ḻ', + 'Ḽ' => 'ḽ', + 'Ḿ' => 'ḿ', + 'Ṁ' => 'ṁ', + 'Ṃ' => 'ṃ', + 'Ṅ' => 'ṅ', + 'Ṇ' => 'ṇ', + 'Ṉ' => 'ṉ', + 'Ṋ' => 'ṋ', + 'Ṍ' => 'ṍ', + 'Ṏ' => 'ṏ', + 'Ṑ' => 'ṑ', + 'Ṓ' => 'ṓ', + 'Ṕ' => 'ṕ', + 'Ṗ' => 'ṗ', + 'Ṙ' => 'ṙ', + 'Ṛ' => 'ṛ', + 'Ṝ' => 'ṝ', + 'Ṟ' => 'ṟ', + 'Ṡ' => 'ṡ', + 'Ṣ' => 'ṣ', + 'Ṥ' => 'ṥ', + 'Ṧ' => 'ṧ', + 'Ṩ' => 'ṩ', + 'Ṫ' => 'ṫ', + 'Ṭ' => 'ṭ', + 'Ṯ' => 'ṯ', + 'Ṱ' => 'ṱ', + 'Ṳ' => 'ṳ', + 'Ṵ' => 'ṵ', + 'Ṷ' => 'ṷ', + 'Ṹ' => 'ṹ', + 'Ṻ' => 'ṻ', + 'Ṽ' => 'ṽ', + 'Ṿ' => 'ṿ', + 'Ẁ' => 'ẁ', + 'Ẃ' => 'ẃ', + 'Ẅ' => 'ẅ', + 'Ẇ' => 'ẇ', + 'Ẉ' => 'ẉ', + 'Ẋ' => 'ẋ', + 'Ẍ' => 'ẍ', + 'Ẏ' => 'ẏ', + 'Ẑ' => 'ẑ', + 'Ẓ' => 'ẓ', + 'Ẕ' => 'ẕ', + 'ẞ' => 'ß', + 'Ạ' => 'ạ', + 'Ả' => 'ả', + 'Ấ' => 'ấ', + 'Ầ' => 'ầ', + 'Ẩ' => 'ẩ', + 'Ẫ' => 'ẫ', + 'Ậ' => 'ậ', + 'Ắ' => 'ắ', + 'Ằ' => 'ằ', + 'Ẳ' => 'ẳ', + 'Ẵ' => 'ẵ', + 'Ặ' => 'ặ', + 'Ẹ' => 'ẹ', + 'Ẻ' => 'ẻ', + 'Ẽ' => 'ẽ', + 'Ế' => 'ế', + 'Ề' => 'ề', + 'Ể' => 'ể', + 'Ễ' => 'ễ', + 'Ệ' => 'ệ', + 'Ỉ' => 'ỉ', + 'Ị' => 'ị', + 'Ọ' => 'ọ', + 'Ỏ' => 'ỏ', + 'Ố' => 'ố', + 'Ồ' => 'ồ', + 'Ổ' => 'ổ', + 'Ỗ' => 'ỗ', + 'Ộ' => 'ộ', + 'Ớ' => 'ớ', + 'Ờ' => 'ờ', + 'Ở' => 'ở', + 'Ỡ' => 'ỡ', + 'Ợ' => 'ợ', + 'Ụ' => 'ụ', + 'Ủ' => 'ủ', + 'Ứ' => 'ứ', + 'Ừ' => 'ừ', + 'Ử' => 'ử', + 'Ữ' => 'ữ', + 'Ự' => 'ự', + 'Ỳ' => 'ỳ', + 'Ỵ' => 'ỵ', + 'Ỷ' => 'ỷ', + 'Ỹ' => 'ỹ', + 'Ỻ' => 'ỻ', + 'Ỽ' => 'ỽ', + 'Ỿ' => 'ỿ', + 'Ἀ' => 'ἀ', + 'Ἁ' => 'ἁ', + 'Ἂ' => 'ἂ', + 'Ἃ' => 'ἃ', + 'Ἄ' => 'ἄ', + 'Ἅ' => 'ἅ', + 'Ἆ' => 'ἆ', + 'Ἇ' => 'ἇ', + 'Ἐ' => 'ἐ', + 'Ἑ' => 'ἑ', + 'Ἒ' => 'ἒ', + 'Ἓ' => 'ἓ', + 'Ἔ' => 'ἔ', + 'Ἕ' => 'ἕ', + 'Ἠ' => 'ἠ', + 'Ἡ' => 'ἡ', + 'Ἢ' => 'ἢ', + 'Ἣ' => 'ἣ', + 'Ἤ' => 'ἤ', + 'Ἥ' => 'ἥ', + 'Ἦ' => 'ἦ', + 'Ἧ' => 'ἧ', + 'Ἰ' => 'ἰ', + 'Ἱ' => 'ἱ', + 'Ἲ' => 'ἲ', + 'Ἳ' => 'ἳ', + 'Ἴ' => 'ἴ', + 'Ἵ' => 'ἵ', + 'Ἶ' => 'ἶ', + 'Ἷ' => 'ἷ', + 'Ὀ' => 'ὀ', + 'Ὁ' => 'ὁ', + 'Ὂ' => 'ὂ', + 'Ὃ' => 'ὃ', + 'Ὄ' => 'ὄ', + 'Ὅ' => 'ὅ', + 'Ὑ' => 'ὑ', + 'Ὓ' => 'ὓ', + 'Ὕ' => 'ὕ', + 'Ὗ' => 'ὗ', + 'Ὠ' => 'ὠ', + 'Ὡ' => 'ὡ', + 'Ὢ' => 'ὢ', + 'Ὣ' => 'ὣ', + 'Ὤ' => 'ὤ', + 'Ὥ' => 'ὥ', + 'Ὦ' => 'ὦ', + 'Ὧ' => 'ὧ', + 'ᾈ' => 'ᾀ', + 'ᾉ' => 'ᾁ', + 'ᾊ' => 'ᾂ', + 'ᾋ' => 'ᾃ', + 'ᾌ' => 'ᾄ', + 'ᾍ' => 'ᾅ', + 'ᾎ' => 'ᾆ', + 'ᾏ' => 'ᾇ', + 'ᾘ' => 'ᾐ', + 'ᾙ' => 'ᾑ', + 'ᾚ' => 'ᾒ', + 'ᾛ' => 'ᾓ', + 'ᾜ' => 'ᾔ', + 'ᾝ' => 'ᾕ', + 'ᾞ' => 'ᾖ', + 'ᾟ' => 'ᾗ', + 'ᾨ' => 'ᾠ', + 'ᾩ' => 'ᾡ', + 'ᾪ' => 'ᾢ', + 'ᾫ' => 'ᾣ', + 'ᾬ' => 'ᾤ', + 'ᾭ' => 'ᾥ', + 'ᾮ' => 'ᾦ', + 'ᾯ' => 'ᾧ', + 'Ᾰ' => 'ᾰ', + 'Ᾱ' => 'ᾱ', + 'Ὰ' => 'ὰ', + 'Ά' => 'ά', + 'ᾼ' => 'ᾳ', + 'Ὲ' => 'ὲ', + 'Έ' => 'έ', + 'Ὴ' => 'ὴ', + 'Ή' => 'ή', + 'ῌ' => 'ῃ', + 'Ῐ' => 'ῐ', + 'Ῑ' => 'ῑ', + 'Ὶ' => 'ὶ', + 'Ί' => 'ί', + 'Ῠ' => 'ῠ', + 'Ῡ' => 'ῡ', + 'Ὺ' => 'ὺ', + 'Ύ' => 'ύ', + 'Ῥ' => 'ῥ', + 'Ὸ' => 'ὸ', + 'Ό' => 'ό', + 'Ὼ' => 'ὼ', + 'Ώ' => 'ώ', + 'ῼ' => 'ῳ', + 'Ω' => 'ω', + 'K' => 'k', + 'Å' => 'å', + 'Ⅎ' => 'ⅎ', + 'Ⅰ' => 'ⅰ', + 'Ⅱ' => 'ⅱ', + 'Ⅲ' => 'ⅲ', + 'Ⅳ' => 'ⅳ', + 'Ⅴ' => 'ⅴ', + 'Ⅵ' => 'ⅵ', + 'Ⅶ' => 'ⅶ', + 'Ⅷ' => 'ⅷ', + 'Ⅸ' => 'ⅸ', + 'Ⅹ' => 'ⅹ', + 'Ⅺ' => 'ⅺ', + 'Ⅻ' => 'ⅻ', + 'Ⅼ' => 'ⅼ', + 'Ⅽ' => 'ⅽ', + 'Ⅾ' => 'ⅾ', + 'Ⅿ' => 'ⅿ', + 'Ↄ' => 'ↄ', + 'Ⓐ' => 'ⓐ', + 'Ⓑ' => 'ⓑ', + 'Ⓒ' => 'ⓒ', + 'Ⓓ' => 'ⓓ', + 'Ⓔ' => 'ⓔ', + 'Ⓕ' => 'ⓕ', + 'Ⓖ' => 'ⓖ', + 'Ⓗ' => 'ⓗ', + 'Ⓘ' => 'ⓘ', + 'Ⓙ' => 'ⓙ', + 'Ⓚ' => 'ⓚ', + 'Ⓛ' => 'ⓛ', + 'Ⓜ' => 'ⓜ', + 'Ⓝ' => 'ⓝ', + 'Ⓞ' => 'ⓞ', + 'Ⓟ' => 'ⓟ', + 'Ⓠ' => 'ⓠ', + 'Ⓡ' => 'ⓡ', + 'Ⓢ' => 'ⓢ', + 'Ⓣ' => 'ⓣ', + 'Ⓤ' => 'ⓤ', + 'Ⓥ' => 'ⓥ', + 'Ⓦ' => 'ⓦ', + 'Ⓧ' => 'ⓧ', + 'Ⓨ' => 'ⓨ', + 'Ⓩ' => 'ⓩ', + 'Ⰰ' => 'ⰰ', + 'Ⰱ' => 'ⰱ', + 'Ⰲ' => 'ⰲ', + 'Ⰳ' => 'ⰳ', + 'Ⰴ' => 'ⰴ', + 'Ⰵ' => 'ⰵ', + 'Ⰶ' => 'ⰶ', + 'Ⰷ' => 'ⰷ', + 'Ⰸ' => 'ⰸ', + 'Ⰹ' => 'ⰹ', + 'Ⰺ' => 'ⰺ', + 'Ⰻ' => 'ⰻ', + 'Ⰼ' => 'ⰼ', + 'Ⰽ' => 'ⰽ', + 'Ⰾ' => 'ⰾ', + 'Ⰿ' => 'ⰿ', + 'Ⱀ' => 'ⱀ', + 'Ⱁ' => 'ⱁ', + 'Ⱂ' => 'ⱂ', + 'Ⱃ' => 'ⱃ', + 'Ⱄ' => 'ⱄ', + 'Ⱅ' => 'ⱅ', + 'Ⱆ' => 'ⱆ', + 'Ⱇ' => 'ⱇ', + 'Ⱈ' => 'ⱈ', + 'Ⱉ' => 'ⱉ', + 'Ⱊ' => 'ⱊ', + 'Ⱋ' => 'ⱋ', + 'Ⱌ' => 'ⱌ', + 'Ⱍ' => 'ⱍ', + 'Ⱎ' => 'ⱎ', + 'Ⱏ' => 'ⱏ', + 'Ⱐ' => 'ⱐ', + 'Ⱑ' => 'ⱑ', + 'Ⱒ' => 'ⱒ', + 'Ⱓ' => 'ⱓ', + 'Ⱔ' => 'ⱔ', + 'Ⱕ' => 'ⱕ', + 'Ⱖ' => 'ⱖ', + 'Ⱗ' => 'ⱗ', + 'Ⱘ' => 'ⱘ', + 'Ⱙ' => 'ⱙ', + 'Ⱚ' => 'ⱚ', + 'Ⱛ' => 'ⱛ', + 'Ⱜ' => 'ⱜ', + 'Ⱝ' => 'ⱝ', + 'Ⱞ' => 'ⱞ', + 'Ⱡ' => 'ⱡ', + 'Ɫ' => 'ɫ', + 'Ᵽ' => 'ᵽ', + 'Ɽ' => 'ɽ', + 'Ⱨ' => 'ⱨ', + 'Ⱪ' => 'ⱪ', + 'Ⱬ' => 'ⱬ', + 'Ɑ' => 'ɑ', + 'Ɱ' => 'ɱ', + 'Ɐ' => 'ɐ', + 'Ɒ' => 'ɒ', + 'Ⱳ' => 'ⱳ', + 'Ⱶ' => 'ⱶ', + 'Ȿ' => 'ȿ', + 'Ɀ' => 'ɀ', + 'Ⲁ' => 'ⲁ', + 'Ⲃ' => 'ⲃ', + 'Ⲅ' => 'ⲅ', + 'Ⲇ' => 'ⲇ', + 'Ⲉ' => 'ⲉ', + 'Ⲋ' => 'ⲋ', + 'Ⲍ' => 'ⲍ', + 'Ⲏ' => 'ⲏ', + 'Ⲑ' => 'ⲑ', + 'Ⲓ' => 'ⲓ', + 'Ⲕ' => 'ⲕ', + 'Ⲗ' => 'ⲗ', + 'Ⲙ' => 'ⲙ', + 'Ⲛ' => 'ⲛ', + 'Ⲝ' => 'ⲝ', + 'Ⲟ' => 'ⲟ', + 'Ⲡ' => 'ⲡ', + 'Ⲣ' => 'ⲣ', + 'Ⲥ' => 'ⲥ', + 'Ⲧ' => 'ⲧ', + 'Ⲩ' => 'ⲩ', + 'Ⲫ' => 'ⲫ', + 'Ⲭ' => 'ⲭ', + 'Ⲯ' => 'ⲯ', + 'Ⲱ' => 'ⲱ', + 'Ⲳ' => 'ⲳ', + 'Ⲵ' => 'ⲵ', + 'Ⲷ' => 'ⲷ', + 'Ⲹ' => 'ⲹ', + 'Ⲻ' => 'ⲻ', + 'Ⲽ' => 'ⲽ', + 'Ⲿ' => 'ⲿ', + 'Ⳁ' => 'ⳁ', + 'Ⳃ' => 'ⳃ', + 'Ⳅ' => 'ⳅ', + 'Ⳇ' => 'ⳇ', + 'Ⳉ' => 'ⳉ', + 'Ⳋ' => 'ⳋ', + 'Ⳍ' => 'ⳍ', + 'Ⳏ' => 'ⳏ', + 'Ⳑ' => 'ⳑ', + 'Ⳓ' => 'ⳓ', + 'Ⳕ' => 'ⳕ', + 'Ⳗ' => 'ⳗ', + 'Ⳙ' => 'ⳙ', + 'Ⳛ' => 'ⳛ', + 'Ⳝ' => 'ⳝ', + 'Ⳟ' => 'ⳟ', + 'Ⳡ' => 'ⳡ', + 'Ⳣ' => 'ⳣ', + 'Ⳬ' => 'ⳬ', + 'Ⳮ' => 'ⳮ', + 'Ⳳ' => 'ⳳ', + 'Ꙁ' => 'ꙁ', + 'Ꙃ' => 'ꙃ', + 'Ꙅ' => 'ꙅ', + 'Ꙇ' => 'ꙇ', + 'Ꙉ' => 'ꙉ', + 'Ꙋ' => 'ꙋ', + 'Ꙍ' => 'ꙍ', + 'Ꙏ' => 'ꙏ', + 'Ꙑ' => 'ꙑ', + 'Ꙓ' => 'ꙓ', + 'Ꙕ' => 'ꙕ', + 'Ꙗ' => 'ꙗ', + 'Ꙙ' => 'ꙙ', + 'Ꙛ' => 'ꙛ', + 'Ꙝ' => 'ꙝ', + 'Ꙟ' => 'ꙟ', + 'Ꙡ' => 'ꙡ', + 'Ꙣ' => 'ꙣ', + 'Ꙥ' => 'ꙥ', + 'Ꙧ' => 'ꙧ', + 'Ꙩ' => 'ꙩ', + 'Ꙫ' => 'ꙫ', + 'Ꙭ' => 'ꙭ', + 'Ꚁ' => 'ꚁ', + 'Ꚃ' => 'ꚃ', + 'Ꚅ' => 'ꚅ', + 'Ꚇ' => 'ꚇ', + 'Ꚉ' => 'ꚉ', + 'Ꚋ' => 'ꚋ', + 'Ꚍ' => 'ꚍ', + 'Ꚏ' => 'ꚏ', + 'Ꚑ' => 'ꚑ', + 'Ꚓ' => 'ꚓ', + 'Ꚕ' => 'ꚕ', + 'Ꚗ' => 'ꚗ', + 'Ꚙ' => 'ꚙ', + 'Ꚛ' => 'ꚛ', + 'Ꜣ' => 'ꜣ', + 'Ꜥ' => 'ꜥ', + 'Ꜧ' => 'ꜧ', + 'Ꜩ' => 'ꜩ', + 'Ꜫ' => 'ꜫ', + 'Ꜭ' => 'ꜭ', + 'Ꜯ' => 'ꜯ', + 'Ꜳ' => 'ꜳ', + 'Ꜵ' => 'ꜵ', + 'Ꜷ' => 'ꜷ', + 'Ꜹ' => 'ꜹ', + 'Ꜻ' => 'ꜻ', + 'Ꜽ' => 'ꜽ', + 'Ꜿ' => 'ꜿ', + 'Ꝁ' => 'ꝁ', + 'Ꝃ' => 'ꝃ', + 'Ꝅ' => 'ꝅ', + 'Ꝇ' => 'ꝇ', + 'Ꝉ' => 'ꝉ', + 'Ꝋ' => 'ꝋ', + 'Ꝍ' => 'ꝍ', + 'Ꝏ' => 'ꝏ', + 'Ꝑ' => 'ꝑ', + 'Ꝓ' => 'ꝓ', + 'Ꝕ' => 'ꝕ', + 'Ꝗ' => 'ꝗ', + 'Ꝙ' => 'ꝙ', + 'Ꝛ' => 'ꝛ', + 'Ꝝ' => 'ꝝ', + 'Ꝟ' => 'ꝟ', + 'Ꝡ' => 'ꝡ', + 'Ꝣ' => 'ꝣ', + 'Ꝥ' => 'ꝥ', + 'Ꝧ' => 'ꝧ', + 'Ꝩ' => 'ꝩ', + 'Ꝫ' => 'ꝫ', + 'Ꝭ' => 'ꝭ', + 'Ꝯ' => 'ꝯ', + 'Ꝺ' => 'ꝺ', + 'Ꝼ' => 'ꝼ', + 'Ᵹ' => 'ᵹ', + 'Ꝿ' => 'ꝿ', + 'Ꞁ' => 'ꞁ', + 'Ꞃ' => 'ꞃ', + 'Ꞅ' => 'ꞅ', + 'Ꞇ' => 'ꞇ', + 'Ꞌ' => 'ꞌ', + 'Ɥ' => 'ɥ', + 'Ꞑ' => 'ꞑ', + 'Ꞓ' => 'ꞓ', + 'Ꞗ' => 'ꞗ', + 'Ꞙ' => 'ꞙ', + 'Ꞛ' => 'ꞛ', + 'Ꞝ' => 'ꞝ', + 'Ꞟ' => 'ꞟ', + 'Ꞡ' => 'ꞡ', + 'Ꞣ' => 'ꞣ', + 'Ꞥ' => 'ꞥ', + 'Ꞧ' => 'ꞧ', + 'Ꞩ' => 'ꞩ', + 'Ɦ' => 'ɦ', + 'Ɜ' => 'ɜ', + 'Ɡ' => 'ɡ', + 'Ɬ' => 'ɬ', + 'Ɪ' => 'ɪ', + 'Ʞ' => 'ʞ', + 'Ʇ' => 'ʇ', + 'Ʝ' => 'ʝ', + 'Ꭓ' => 'ꭓ', + 'Ꞵ' => 'ꞵ', + 'Ꞷ' => 'ꞷ', + 'Ꞹ' => 'ꞹ', + 'Ꞻ' => 'ꞻ', + 'Ꞽ' => 'ꞽ', + 'Ꞿ' => 'ꞿ', + 'Ꟃ' => 'ꟃ', + 'Ꞔ' => 'ꞔ', + 'Ʂ' => 'ʂ', + 'Ᶎ' => 'ᶎ', + 'Ꟈ' => 'ꟈ', + 'Ꟊ' => 'ꟊ', + 'Ꟶ' => 'ꟶ', + 'A' => 'a', + 'B' => 'b', + 'C' => 'c', + 'D' => 'd', + 'E' => 'e', + 'F' => 'f', + 'G' => 'g', + 'H' => 'h', + 'I' => 'i', + 'J' => 'j', + 'K' => 'k', + 'L' => 'l', + 'M' => 'm', + 'N' => 'n', + 'O' => 'o', + 'P' => 'p', + 'Q' => 'q', + 'R' => 'r', + 'S' => 's', + 'T' => 't', + 'U' => 'u', + 'V' => 'v', + 'W' => 'w', + 'X' => 'x', + 'Y' => 'y', + 'Z' => 'z', + '𐐀' => '𐐨', + '𐐁' => '𐐩', + '𐐂' => '𐐪', + '𐐃' => '𐐫', + '𐐄' => '𐐬', + '𐐅' => '𐐭', + '𐐆' => '𐐮', + '𐐇' => '𐐯', + '𐐈' => '𐐰', + '𐐉' => '𐐱', + '𐐊' => '𐐲', + '𐐋' => '𐐳', + '𐐌' => '𐐴', + '𐐍' => '𐐵', + '𐐎' => '𐐶', + '𐐏' => '𐐷', + '𐐐' => '𐐸', + '𐐑' => '𐐹', + '𐐒' => '𐐺', + '𐐓' => '𐐻', + '𐐔' => '𐐼', + '𐐕' => '𐐽', + '𐐖' => '𐐾', + '𐐗' => '𐐿', + '𐐘' => '𐑀', + '𐐙' => '𐑁', + '𐐚' => '𐑂', + '𐐛' => '𐑃', + '𐐜' => '𐑄', + '𐐝' => '𐑅', + '𐐞' => '𐑆', + '𐐟' => '𐑇', + '𐐠' => '𐑈', + '𐐡' => '𐑉', + '𐐢' => '𐑊', + '𐐣' => '𐑋', + '𐐤' => '𐑌', + '𐐥' => '𐑍', + '𐐦' => '𐑎', + '𐐧' => '𐑏', + '𐒰' => '𐓘', + '𐒱' => '𐓙', + '𐒲' => '𐓚', + '𐒳' => '𐓛', + '𐒴' => '𐓜', + '𐒵' => '𐓝', + '𐒶' => '𐓞', + '𐒷' => '𐓟', + '𐒸' => '𐓠', + '𐒹' => '𐓡', + '𐒺' => '𐓢', + '𐒻' => '𐓣', + '𐒼' => '𐓤', + '𐒽' => '𐓥', + '𐒾' => '𐓦', + '𐒿' => '𐓧', + '𐓀' => '𐓨', + '𐓁' => '𐓩', + '𐓂' => '𐓪', + '𐓃' => '𐓫', + '𐓄' => '𐓬', + '𐓅' => '𐓭', + '𐓆' => '𐓮', + '𐓇' => '𐓯', + '𐓈' => '𐓰', + '𐓉' => '𐓱', + '𐓊' => '𐓲', + '𐓋' => '𐓳', + '𐓌' => '𐓴', + '𐓍' => '𐓵', + '𐓎' => '𐓶', + '𐓏' => '𐓷', + '𐓐' => '𐓸', + '𐓑' => '𐓹', + '𐓒' => '𐓺', + '𐓓' => '𐓻', + '𐲀' => '𐳀', + '𐲁' => '𐳁', + '𐲂' => '𐳂', + '𐲃' => '𐳃', + '𐲄' => '𐳄', + '𐲅' => '𐳅', + '𐲆' => '𐳆', + '𐲇' => '𐳇', + '𐲈' => '𐳈', + '𐲉' => '𐳉', + '𐲊' => '𐳊', + '𐲋' => '𐳋', + '𐲌' => '𐳌', + '𐲍' => '𐳍', + '𐲎' => '𐳎', + '𐲏' => '𐳏', + '𐲐' => '𐳐', + '𐲑' => '𐳑', + '𐲒' => '𐳒', + '𐲓' => '𐳓', + '𐲔' => '𐳔', + '𐲕' => '𐳕', + '𐲖' => '𐳖', + '𐲗' => '𐳗', + '𐲘' => '𐳘', + '𐲙' => '𐳙', + '𐲚' => '𐳚', + '𐲛' => '𐳛', + '𐲜' => '𐳜', + '𐲝' => '𐳝', + '𐲞' => '𐳞', + '𐲟' => '𐳟', + '𐲠' => '𐳠', + '𐲡' => '𐳡', + '𐲢' => '𐳢', + '𐲣' => '𐳣', + '𐲤' => '𐳤', + '𐲥' => '𐳥', + '𐲦' => '𐳦', + '𐲧' => '𐳧', + '𐲨' => '𐳨', + '𐲩' => '𐳩', + '𐲪' => '𐳪', + '𐲫' => '𐳫', + '𐲬' => '𐳬', + '𐲭' => '𐳭', + '𐲮' => '𐳮', + '𐲯' => '𐳯', + '𐲰' => '𐳰', + '𐲱' => '𐳱', + '𐲲' => '𐳲', + '𑢠' => '𑣀', + '𑢡' => '𑣁', + '𑢢' => '𑣂', + '𑢣' => '𑣃', + '𑢤' => '𑣄', + '𑢥' => '𑣅', + '𑢦' => '𑣆', + '𑢧' => '𑣇', + '𑢨' => '𑣈', + '𑢩' => '𑣉', + '𑢪' => '𑣊', + '𑢫' => '𑣋', + '𑢬' => '𑣌', + '𑢭' => '𑣍', + '𑢮' => '𑣎', + '𑢯' => '𑣏', + '𑢰' => '𑣐', + '𑢱' => '𑣑', + '𑢲' => '𑣒', + '𑢳' => '𑣓', + '𑢴' => '𑣔', + '𑢵' => '𑣕', + '𑢶' => '𑣖', + '𑢷' => '𑣗', + '𑢸' => '𑣘', + '𑢹' => '𑣙', + '𑢺' => '𑣚', + '𑢻' => '𑣛', + '𑢼' => '𑣜', + '𑢽' => '𑣝', + '𑢾' => '𑣞', + '𑢿' => '𑣟', + '𖹀' => '𖹠', + '𖹁' => '𖹡', + '𖹂' => '𖹢', + '𖹃' => '𖹣', + '𖹄' => '𖹤', + '𖹅' => '𖹥', + '𖹆' => '𖹦', + '𖹇' => '𖹧', + '𖹈' => '𖹨', + '𖹉' => '𖹩', + '𖹊' => '𖹪', + '𖹋' => '𖹫', + '𖹌' => '𖹬', + '𖹍' => '𖹭', + '𖹎' => '𖹮', + '𖹏' => '𖹯', + '𖹐' => '𖹰', + '𖹑' => '𖹱', + '𖹒' => '𖹲', + '𖹓' => '𖹳', + '𖹔' => '𖹴', + '𖹕' => '𖹵', + '𖹖' => '𖹶', + '𖹗' => '𖹷', + '𖹘' => '𖹸', + '𖹙' => '𖹹', + '𖹚' => '𖹺', + '𖹛' => '𖹻', + '𖹜' => '𖹼', + '𖹝' => '𖹽', + '𖹞' => '𖹾', + '𖹟' => '𖹿', + '𞤀' => '𞤢', + '𞤁' => '𞤣', + '𞤂' => '𞤤', + '𞤃' => '𞤥', + '𞤄' => '𞤦', + '𞤅' => '𞤧', + '𞤆' => '𞤨', + '𞤇' => '𞤩', + '𞤈' => '𞤪', + '𞤉' => '𞤫', + '𞤊' => '𞤬', + '𞤋' => '𞤭', + '𞤌' => '𞤮', + '𞤍' => '𞤯', + '𞤎' => '𞤰', + '𞤏' => '𞤱', + '𞤐' => '𞤲', + '𞤑' => '𞤳', + '𞤒' => '𞤴', + '𞤓' => '𞤵', + '𞤔' => '𞤶', + '𞤕' => '𞤷', + '𞤖' => '𞤸', + '𞤗' => '𞤹', + '𞤘' => '𞤺', + '𞤙' => '𞤻', + '𞤚' => '𞤼', + '𞤛' => '𞤽', + '𞤜' => '𞤾', + '𞤝' => '𞤿', + '𞤞' => '𞥀', + '𞤟' => '𞥁', + '𞤠' => '𞥂', + '𞤡' => '𞥃', +); diff --git a/modules/inpostizi/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php b/modules/inpostizi/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php new file mode 100644 index 00000000..2a8f6e73 --- /dev/null +++ b/modules/inpostizi/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php @@ -0,0 +1,5 @@ + 'A', + 'b' => 'B', + 'c' => 'C', + 'd' => 'D', + 'e' => 'E', + 'f' => 'F', + 'g' => 'G', + 'h' => 'H', + 'i' => 'I', + 'j' => 'J', + 'k' => 'K', + 'l' => 'L', + 'm' => 'M', + 'n' => 'N', + 'o' => 'O', + 'p' => 'P', + 'q' => 'Q', + 'r' => 'R', + 's' => 'S', + 't' => 'T', + 'u' => 'U', + 'v' => 'V', + 'w' => 'W', + 'x' => 'X', + 'y' => 'Y', + 'z' => 'Z', + 'µ' => 'Μ', + 'à' => 'À', + 'á' => 'Á', + 'â' => 'Â', + 'ã' => 'Ã', + 'ä' => 'Ä', + 'å' => 'Å', + 'æ' => 'Æ', + 'ç' => 'Ç', + 'è' => 'È', + 'é' => 'É', + 'ê' => 'Ê', + 'ë' => 'Ë', + 'ì' => 'Ì', + 'í' => 'Í', + 'î' => 'Î', + 'ï' => 'Ï', + 'ð' => 'Ð', + 'ñ' => 'Ñ', + 'ò' => 'Ò', + 'ó' => 'Ó', + 'ô' => 'Ô', + 'õ' => 'Õ', + 'ö' => 'Ö', + 'ø' => 'Ø', + 'ù' => 'Ù', + 'ú' => 'Ú', + 'û' => 'Û', + 'ü' => 'Ü', + 'ý' => 'Ý', + 'þ' => 'Þ', + 'ÿ' => 'Ÿ', + 'ā' => 'Ā', + 'ă' => 'Ă', + 'ą' => 'Ą', + 'ć' => 'Ć', + 'ĉ' => 'Ĉ', + 'ċ' => 'Ċ', + 'č' => 'Č', + 'ď' => 'Ď', + 'đ' => 'Đ', + 'ē' => 'Ē', + 'ĕ' => 'Ĕ', + 'ė' => 'Ė', + 'ę' => 'Ę', + 'ě' => 'Ě', + 'ĝ' => 'Ĝ', + 'ğ' => 'Ğ', + 'ġ' => 'Ġ', + 'ģ' => 'Ģ', + 'ĥ' => 'Ĥ', + 'ħ' => 'Ħ', + 'ĩ' => 'Ĩ', + 'ī' => 'Ī', + 'ĭ' => 'Ĭ', + 'į' => 'Į', + 'ı' => 'I', + 'ij' => 'IJ', + 'ĵ' => 'Ĵ', + 'ķ' => 'Ķ', + 'ĺ' => 'Ĺ', + 'ļ' => 'Ļ', + 'ľ' => 'Ľ', + 'ŀ' => 'Ŀ', + 'ł' => 'Ł', + 'ń' => 'Ń', + 'ņ' => 'Ņ', + 'ň' => 'Ň', + 'ŋ' => 'Ŋ', + 'ō' => 'Ō', + 'ŏ' => 'Ŏ', + 'ő' => 'Ő', + 'œ' => 'Œ', + 'ŕ' => 'Ŕ', + 'ŗ' => 'Ŗ', + 'ř' => 'Ř', + 'ś' => 'Ś', + 'ŝ' => 'Ŝ', + 'ş' => 'Ş', + 'š' => 'Š', + 'ţ' => 'Ţ', + 'ť' => 'Ť', + 'ŧ' => 'Ŧ', + 'ũ' => 'Ũ', + 'ū' => 'Ū', + 'ŭ' => 'Ŭ', + 'ů' => 'Ů', + 'ű' => 'Ű', + 'ų' => 'Ų', + 'ŵ' => 'Ŵ', + 'ŷ' => 'Ŷ', + 'ź' => 'Ź', + 'ż' => 'Ż', + 'ž' => 'Ž', + 'ſ' => 'S', + 'ƀ' => 'Ƀ', + 'ƃ' => 'Ƃ', + 'ƅ' => 'Ƅ', + 'ƈ' => 'Ƈ', + 'ƌ' => 'Ƌ', + 'ƒ' => 'Ƒ', + 'ƕ' => 'Ƕ', + 'ƙ' => 'Ƙ', + 'ƚ' => 'Ƚ', + 'ƞ' => 'Ƞ', + 'ơ' => 'Ơ', + 'ƣ' => 'Ƣ', + 'ƥ' => 'Ƥ', + 'ƨ' => 'Ƨ', + 'ƭ' => 'Ƭ', + 'ư' => 'Ư', + 'ƴ' => 'Ƴ', + 'ƶ' => 'Ƶ', + 'ƹ' => 'Ƹ', + 'ƽ' => 'Ƽ', + 'ƿ' => 'Ƿ', + 'Dž' => 'DŽ', + 'dž' => 'DŽ', + 'Lj' => 'LJ', + 'lj' => 'LJ', + 'Nj' => 'NJ', + 'nj' => 'NJ', + 'ǎ' => 'Ǎ', + 'ǐ' => 'Ǐ', + 'ǒ' => 'Ǒ', + 'ǔ' => 'Ǔ', + 'ǖ' => 'Ǖ', + 'ǘ' => 'Ǘ', + 'ǚ' => 'Ǚ', + 'ǜ' => 'Ǜ', + 'ǝ' => 'Ǝ', + 'ǟ' => 'Ǟ', + 'ǡ' => 'Ǡ', + 'ǣ' => 'Ǣ', + 'ǥ' => 'Ǥ', + 'ǧ' => 'Ǧ', + 'ǩ' => 'Ǩ', + 'ǫ' => 'Ǫ', + 'ǭ' => 'Ǭ', + 'ǯ' => 'Ǯ', + 'Dz' => 'DZ', + 'dz' => 'DZ', + 'ǵ' => 'Ǵ', + 'ǹ' => 'Ǹ', + 'ǻ' => 'Ǻ', + 'ǽ' => 'Ǽ', + 'ǿ' => 'Ǿ', + 'ȁ' => 'Ȁ', + 'ȃ' => 'Ȃ', + 'ȅ' => 'Ȅ', + 'ȇ' => 'Ȇ', + 'ȉ' => 'Ȉ', + 'ȋ' => 'Ȋ', + 'ȍ' => 'Ȍ', + 'ȏ' => 'Ȏ', + 'ȑ' => 'Ȑ', + 'ȓ' => 'Ȓ', + 'ȕ' => 'Ȕ', + 'ȗ' => 'Ȗ', + 'ș' => 'Ș', + 'ț' => 'Ț', + 'ȝ' => 'Ȝ', + 'ȟ' => 'Ȟ', + 'ȣ' => 'Ȣ', + 'ȥ' => 'Ȥ', + 'ȧ' => 'Ȧ', + 'ȩ' => 'Ȩ', + 'ȫ' => 'Ȫ', + 'ȭ' => 'Ȭ', + 'ȯ' => 'Ȯ', + 'ȱ' => 'Ȱ', + 'ȳ' => 'Ȳ', + 'ȼ' => 'Ȼ', + 'ȿ' => 'Ȿ', + 'ɀ' => 'Ɀ', + 'ɂ' => 'Ɂ', + 'ɇ' => 'Ɇ', + 'ɉ' => 'Ɉ', + 'ɋ' => 'Ɋ', + 'ɍ' => 'Ɍ', + 'ɏ' => 'Ɏ', + 'ɐ' => 'Ɐ', + 'ɑ' => 'Ɑ', + 'ɒ' => 'Ɒ', + 'ɓ' => 'Ɓ', + 'ɔ' => 'Ɔ', + 'ɖ' => 'Ɖ', + 'ɗ' => 'Ɗ', + 'ə' => 'Ə', + 'ɛ' => 'Ɛ', + 'ɜ' => 'Ɜ', + 'ɠ' => 'Ɠ', + 'ɡ' => 'Ɡ', + 'ɣ' => 'Ɣ', + 'ɥ' => 'Ɥ', + 'ɦ' => 'Ɦ', + 'ɨ' => 'Ɨ', + 'ɩ' => 'Ɩ', + 'ɪ' => 'Ɪ', + 'ɫ' => 'Ɫ', + 'ɬ' => 'Ɬ', + 'ɯ' => 'Ɯ', + 'ɱ' => 'Ɱ', + 'ɲ' => 'Ɲ', + 'ɵ' => 'Ɵ', + 'ɽ' => 'Ɽ', + 'ʀ' => 'Ʀ', + 'ʂ' => 'Ʂ', + 'ʃ' => 'Ʃ', + 'ʇ' => 'Ʇ', + 'ʈ' => 'Ʈ', + 'ʉ' => 'Ʉ', + 'ʊ' => 'Ʊ', + 'ʋ' => 'Ʋ', + 'ʌ' => 'Ʌ', + 'ʒ' => 'Ʒ', + 'ʝ' => 'Ʝ', + 'ʞ' => 'Ʞ', + 'ͅ' => 'Ι', + 'ͱ' => 'Ͱ', + 'ͳ' => 'Ͳ', + 'ͷ' => 'Ͷ', + 'ͻ' => 'Ͻ', + 'ͼ' => 'Ͼ', + 'ͽ' => 'Ͽ', + 'ά' => 'Ά', + 'έ' => 'Έ', + 'ή' => 'Ή', + 'ί' => 'Ί', + 'α' => 'Α', + 'β' => 'Β', + 'γ' => 'Γ', + 'δ' => 'Δ', + 'ε' => 'Ε', + 'ζ' => 'Ζ', + 'η' => 'Η', + 'θ' => 'Θ', + 'ι' => 'Ι', + 'κ' => 'Κ', + 'λ' => 'Λ', + 'μ' => 'Μ', + 'ν' => 'Ν', + 'ξ' => 'Ξ', + 'ο' => 'Ο', + 'π' => 'Π', + 'ρ' => 'Ρ', + 'ς' => 'Σ', + 'σ' => 'Σ', + 'τ' => 'Τ', + 'υ' => 'Υ', + 'φ' => 'Φ', + 'χ' => 'Χ', + 'ψ' => 'Ψ', + 'ω' => 'Ω', + 'ϊ' => 'Ϊ', + 'ϋ' => 'Ϋ', + 'ό' => 'Ό', + 'ύ' => 'Ύ', + 'ώ' => 'Ώ', + 'ϐ' => 'Β', + 'ϑ' => 'Θ', + 'ϕ' => 'Φ', + 'ϖ' => 'Π', + 'ϗ' => 'Ϗ', + 'ϙ' => 'Ϙ', + 'ϛ' => 'Ϛ', + 'ϝ' => 'Ϝ', + 'ϟ' => 'Ϟ', + 'ϡ' => 'Ϡ', + 'ϣ' => 'Ϣ', + 'ϥ' => 'Ϥ', + 'ϧ' => 'Ϧ', + 'ϩ' => 'Ϩ', + 'ϫ' => 'Ϫ', + 'ϭ' => 'Ϭ', + 'ϯ' => 'Ϯ', + 'ϰ' => 'Κ', + 'ϱ' => 'Ρ', + 'ϲ' => 'Ϲ', + 'ϳ' => 'Ϳ', + 'ϵ' => 'Ε', + 'ϸ' => 'Ϸ', + 'ϻ' => 'Ϻ', + 'а' => 'А', + 'б' => 'Б', + 'в' => 'В', + 'г' => 'Г', + 'д' => 'Д', + 'е' => 'Е', + 'ж' => 'Ж', + 'з' => 'З', + 'и' => 'И', + 'й' => 'Й', + 'к' => 'К', + 'л' => 'Л', + 'м' => 'М', + 'н' => 'Н', + 'о' => 'О', + 'п' => 'П', + 'р' => 'Р', + 'с' => 'С', + 'т' => 'Т', + 'у' => 'У', + 'ф' => 'Ф', + 'х' => 'Х', + 'ц' => 'Ц', + 'ч' => 'Ч', + 'ш' => 'Ш', + 'щ' => 'Щ', + 'ъ' => 'Ъ', + 'ы' => 'Ы', + 'ь' => 'Ь', + 'э' => 'Э', + 'ю' => 'Ю', + 'я' => 'Я', + 'ѐ' => 'Ѐ', + 'ё' => 'Ё', + 'ђ' => 'Ђ', + 'ѓ' => 'Ѓ', + 'є' => 'Є', + 'ѕ' => 'Ѕ', + 'і' => 'І', + 'ї' => 'Ї', + 'ј' => 'Ј', + 'љ' => 'Љ', + 'њ' => 'Њ', + 'ћ' => 'Ћ', + 'ќ' => 'Ќ', + 'ѝ' => 'Ѝ', + 'ў' => 'Ў', + 'џ' => 'Џ', + 'ѡ' => 'Ѡ', + 'ѣ' => 'Ѣ', + 'ѥ' => 'Ѥ', + 'ѧ' => 'Ѧ', + 'ѩ' => 'Ѩ', + 'ѫ' => 'Ѫ', + 'ѭ' => 'Ѭ', + 'ѯ' => 'Ѯ', + 'ѱ' => 'Ѱ', + 'ѳ' => 'Ѳ', + 'ѵ' => 'Ѵ', + 'ѷ' => 'Ѷ', + 'ѹ' => 'Ѹ', + 'ѻ' => 'Ѻ', + 'ѽ' => 'Ѽ', + 'ѿ' => 'Ѿ', + 'ҁ' => 'Ҁ', + 'ҋ' => 'Ҋ', + 'ҍ' => 'Ҍ', + 'ҏ' => 'Ҏ', + 'ґ' => 'Ґ', + 'ғ' => 'Ғ', + 'ҕ' => 'Ҕ', + 'җ' => 'Җ', + 'ҙ' => 'Ҙ', + 'қ' => 'Қ', + 'ҝ' => 'Ҝ', + 'ҟ' => 'Ҟ', + 'ҡ' => 'Ҡ', + 'ң' => 'Ң', + 'ҥ' => 'Ҥ', + 'ҧ' => 'Ҧ', + 'ҩ' => 'Ҩ', + 'ҫ' => 'Ҫ', + 'ҭ' => 'Ҭ', + 'ү' => 'Ү', + 'ұ' => 'Ұ', + 'ҳ' => 'Ҳ', + 'ҵ' => 'Ҵ', + 'ҷ' => 'Ҷ', + 'ҹ' => 'Ҹ', + 'һ' => 'Һ', + 'ҽ' => 'Ҽ', + 'ҿ' => 'Ҿ', + 'ӂ' => 'Ӂ', + 'ӄ' => 'Ӄ', + 'ӆ' => 'Ӆ', + 'ӈ' => 'Ӈ', + 'ӊ' => 'Ӊ', + 'ӌ' => 'Ӌ', + 'ӎ' => 'Ӎ', + 'ӏ' => 'Ӏ', + 'ӑ' => 'Ӑ', + 'ӓ' => 'Ӓ', + 'ӕ' => 'Ӕ', + 'ӗ' => 'Ӗ', + 'ә' => 'Ә', + 'ӛ' => 'Ӛ', + 'ӝ' => 'Ӝ', + 'ӟ' => 'Ӟ', + 'ӡ' => 'Ӡ', + 'ӣ' => 'Ӣ', + 'ӥ' => 'Ӥ', + 'ӧ' => 'Ӧ', + 'ө' => 'Ө', + 'ӫ' => 'Ӫ', + 'ӭ' => 'Ӭ', + 'ӯ' => 'Ӯ', + 'ӱ' => 'Ӱ', + 'ӳ' => 'Ӳ', + 'ӵ' => 'Ӵ', + 'ӷ' => 'Ӷ', + 'ӹ' => 'Ӹ', + 'ӻ' => 'Ӻ', + 'ӽ' => 'Ӽ', + 'ӿ' => 'Ӿ', + 'ԁ' => 'Ԁ', + 'ԃ' => 'Ԃ', + 'ԅ' => 'Ԅ', + 'ԇ' => 'Ԇ', + 'ԉ' => 'Ԉ', + 'ԋ' => 'Ԋ', + 'ԍ' => 'Ԍ', + 'ԏ' => 'Ԏ', + 'ԑ' => 'Ԑ', + 'ԓ' => 'Ԓ', + 'ԕ' => 'Ԕ', + 'ԗ' => 'Ԗ', + 'ԙ' => 'Ԙ', + 'ԛ' => 'Ԛ', + 'ԝ' => 'Ԝ', + 'ԟ' => 'Ԟ', + 'ԡ' => 'Ԡ', + 'ԣ' => 'Ԣ', + 'ԥ' => 'Ԥ', + 'ԧ' => 'Ԧ', + 'ԩ' => 'Ԩ', + 'ԫ' => 'Ԫ', + 'ԭ' => 'Ԭ', + 'ԯ' => 'Ԯ', + 'ա' => 'Ա', + 'բ' => 'Բ', + 'գ' => 'Գ', + 'դ' => 'Դ', + 'ե' => 'Ե', + 'զ' => 'Զ', + 'է' => 'Է', + 'ը' => 'Ը', + 'թ' => 'Թ', + 'ժ' => 'Ժ', + 'ի' => 'Ի', + 'լ' => 'Լ', + 'խ' => 'Խ', + 'ծ' => 'Ծ', + 'կ' => 'Կ', + 'հ' => 'Հ', + 'ձ' => 'Ձ', + 'ղ' => 'Ղ', + 'ճ' => 'Ճ', + 'մ' => 'Մ', + 'յ' => 'Յ', + 'ն' => 'Ն', + 'շ' => 'Շ', + 'ո' => 'Ո', + 'չ' => 'Չ', + 'պ' => 'Պ', + 'ջ' => 'Ջ', + 'ռ' => 'Ռ', + 'ս' => 'Ս', + 'վ' => 'Վ', + 'տ' => 'Տ', + 'ր' => 'Ր', + 'ց' => 'Ց', + 'ւ' => 'Ւ', + 'փ' => 'Փ', + 'ք' => 'Ք', + 'օ' => 'Օ', + 'ֆ' => 'Ֆ', + 'ა' => 'Ა', + 'ბ' => 'Ბ', + 'გ' => 'Გ', + 'დ' => 'Დ', + 'ე' => 'Ე', + 'ვ' => 'Ვ', + 'ზ' => 'Ზ', + 'თ' => 'Თ', + 'ი' => 'Ი', + 'კ' => 'Კ', + 'ლ' => 'Ლ', + 'მ' => 'Მ', + 'ნ' => 'Ნ', + 'ო' => 'Ო', + 'პ' => 'Პ', + 'ჟ' => 'Ჟ', + 'რ' => 'Რ', + 'ს' => 'Ს', + 'ტ' => 'Ტ', + 'უ' => 'Უ', + 'ფ' => 'Ფ', + 'ქ' => 'Ქ', + 'ღ' => 'Ღ', + 'ყ' => 'Ყ', + 'შ' => 'Შ', + 'ჩ' => 'Ჩ', + 'ც' => 'Ც', + 'ძ' => 'Ძ', + 'წ' => 'Წ', + 'ჭ' => 'Ჭ', + 'ხ' => 'Ხ', + 'ჯ' => 'Ჯ', + 'ჰ' => 'Ჰ', + 'ჱ' => 'Ჱ', + 'ჲ' => 'Ჲ', + 'ჳ' => 'Ჳ', + 'ჴ' => 'Ჴ', + 'ჵ' => 'Ჵ', + 'ჶ' => 'Ჶ', + 'ჷ' => 'Ჷ', + 'ჸ' => 'Ჸ', + 'ჹ' => 'Ჹ', + 'ჺ' => 'Ჺ', + 'ჽ' => 'Ჽ', + 'ჾ' => 'Ჾ', + 'ჿ' => 'Ჿ', + 'ᏸ' => 'Ᏸ', + 'ᏹ' => 'Ᏹ', + 'ᏺ' => 'Ᏺ', + 'ᏻ' => 'Ᏻ', + 'ᏼ' => 'Ᏼ', + 'ᏽ' => 'Ᏽ', + 'ᲀ' => 'В', + 'ᲁ' => 'Д', + 'ᲂ' => 'О', + 'ᲃ' => 'С', + 'ᲄ' => 'Т', + 'ᲅ' => 'Т', + 'ᲆ' => 'Ъ', + 'ᲇ' => 'Ѣ', + 'ᲈ' => 'Ꙋ', + 'ᵹ' => 'Ᵹ', + 'ᵽ' => 'Ᵽ', + 'ᶎ' => 'Ᶎ', + 'ḁ' => 'Ḁ', + 'ḃ' => 'Ḃ', + 'ḅ' => 'Ḅ', + 'ḇ' => 'Ḇ', + 'ḉ' => 'Ḉ', + 'ḋ' => 'Ḋ', + 'ḍ' => 'Ḍ', + 'ḏ' => 'Ḏ', + 'ḑ' => 'Ḑ', + 'ḓ' => 'Ḓ', + 'ḕ' => 'Ḕ', + 'ḗ' => 'Ḗ', + 'ḙ' => 'Ḙ', + 'ḛ' => 'Ḛ', + 'ḝ' => 'Ḝ', + 'ḟ' => 'Ḟ', + 'ḡ' => 'Ḡ', + 'ḣ' => 'Ḣ', + 'ḥ' => 'Ḥ', + 'ḧ' => 'Ḧ', + 'ḩ' => 'Ḩ', + 'ḫ' => 'Ḫ', + 'ḭ' => 'Ḭ', + 'ḯ' => 'Ḯ', + 'ḱ' => 'Ḱ', + 'ḳ' => 'Ḳ', + 'ḵ' => 'Ḵ', + 'ḷ' => 'Ḷ', + 'ḹ' => 'Ḹ', + 'ḻ' => 'Ḻ', + 'ḽ' => 'Ḽ', + 'ḿ' => 'Ḿ', + 'ṁ' => 'Ṁ', + 'ṃ' => 'Ṃ', + 'ṅ' => 'Ṅ', + 'ṇ' => 'Ṇ', + 'ṉ' => 'Ṉ', + 'ṋ' => 'Ṋ', + 'ṍ' => 'Ṍ', + 'ṏ' => 'Ṏ', + 'ṑ' => 'Ṑ', + 'ṓ' => 'Ṓ', + 'ṕ' => 'Ṕ', + 'ṗ' => 'Ṗ', + 'ṙ' => 'Ṙ', + 'ṛ' => 'Ṛ', + 'ṝ' => 'Ṝ', + 'ṟ' => 'Ṟ', + 'ṡ' => 'Ṡ', + 'ṣ' => 'Ṣ', + 'ṥ' => 'Ṥ', + 'ṧ' => 'Ṧ', + 'ṩ' => 'Ṩ', + 'ṫ' => 'Ṫ', + 'ṭ' => 'Ṭ', + 'ṯ' => 'Ṯ', + 'ṱ' => 'Ṱ', + 'ṳ' => 'Ṳ', + 'ṵ' => 'Ṵ', + 'ṷ' => 'Ṷ', + 'ṹ' => 'Ṹ', + 'ṻ' => 'Ṻ', + 'ṽ' => 'Ṽ', + 'ṿ' => 'Ṿ', + 'ẁ' => 'Ẁ', + 'ẃ' => 'Ẃ', + 'ẅ' => 'Ẅ', + 'ẇ' => 'Ẇ', + 'ẉ' => 'Ẉ', + 'ẋ' => 'Ẋ', + 'ẍ' => 'Ẍ', + 'ẏ' => 'Ẏ', + 'ẑ' => 'Ẑ', + 'ẓ' => 'Ẓ', + 'ẕ' => 'Ẕ', + 'ẛ' => 'Ṡ', + 'ạ' => 'Ạ', + 'ả' => 'Ả', + 'ấ' => 'Ấ', + 'ầ' => 'Ầ', + 'ẩ' => 'Ẩ', + 'ẫ' => 'Ẫ', + 'ậ' => 'Ậ', + 'ắ' => 'Ắ', + 'ằ' => 'Ằ', + 'ẳ' => 'Ẳ', + 'ẵ' => 'Ẵ', + 'ặ' => 'Ặ', + 'ẹ' => 'Ẹ', + 'ẻ' => 'Ẻ', + 'ẽ' => 'Ẽ', + 'ế' => 'Ế', + 'ề' => 'Ề', + 'ể' => 'Ể', + 'ễ' => 'Ễ', + 'ệ' => 'Ệ', + 'ỉ' => 'Ỉ', + 'ị' => 'Ị', + 'ọ' => 'Ọ', + 'ỏ' => 'Ỏ', + 'ố' => 'Ố', + 'ồ' => 'Ồ', + 'ổ' => 'Ổ', + 'ỗ' => 'Ỗ', + 'ộ' => 'Ộ', + 'ớ' => 'Ớ', + 'ờ' => 'Ờ', + 'ở' => 'Ở', + 'ỡ' => 'Ỡ', + 'ợ' => 'Ợ', + 'ụ' => 'Ụ', + 'ủ' => 'Ủ', + 'ứ' => 'Ứ', + 'ừ' => 'Ừ', + 'ử' => 'Ử', + 'ữ' => 'Ữ', + 'ự' => 'Ự', + 'ỳ' => 'Ỳ', + 'ỵ' => 'Ỵ', + 'ỷ' => 'Ỷ', + 'ỹ' => 'Ỹ', + 'ỻ' => 'Ỻ', + 'ỽ' => 'Ỽ', + 'ỿ' => 'Ỿ', + 'ἀ' => 'Ἀ', + 'ἁ' => 'Ἁ', + 'ἂ' => 'Ἂ', + 'ἃ' => 'Ἃ', + 'ἄ' => 'Ἄ', + 'ἅ' => 'Ἅ', + 'ἆ' => 'Ἆ', + 'ἇ' => 'Ἇ', + 'ἐ' => 'Ἐ', + 'ἑ' => 'Ἑ', + 'ἒ' => 'Ἒ', + 'ἓ' => 'Ἓ', + 'ἔ' => 'Ἔ', + 'ἕ' => 'Ἕ', + 'ἠ' => 'Ἠ', + 'ἡ' => 'Ἡ', + 'ἢ' => 'Ἢ', + 'ἣ' => 'Ἣ', + 'ἤ' => 'Ἤ', + 'ἥ' => 'Ἥ', + 'ἦ' => 'Ἦ', + 'ἧ' => 'Ἧ', + 'ἰ' => 'Ἰ', + 'ἱ' => 'Ἱ', + 'ἲ' => 'Ἲ', + 'ἳ' => 'Ἳ', + 'ἴ' => 'Ἴ', + 'ἵ' => 'Ἵ', + 'ἶ' => 'Ἶ', + 'ἷ' => 'Ἷ', + 'ὀ' => 'Ὀ', + 'ὁ' => 'Ὁ', + 'ὂ' => 'Ὂ', + 'ὃ' => 'Ὃ', + 'ὄ' => 'Ὄ', + 'ὅ' => 'Ὅ', + 'ὑ' => 'Ὑ', + 'ὓ' => 'Ὓ', + 'ὕ' => 'Ὕ', + 'ὗ' => 'Ὗ', + 'ὠ' => 'Ὠ', + 'ὡ' => 'Ὡ', + 'ὢ' => 'Ὢ', + 'ὣ' => 'Ὣ', + 'ὤ' => 'Ὤ', + 'ὥ' => 'Ὥ', + 'ὦ' => 'Ὦ', + 'ὧ' => 'Ὧ', + 'ὰ' => 'Ὰ', + 'ά' => 'Ά', + 'ὲ' => 'Ὲ', + 'έ' => 'Έ', + 'ὴ' => 'Ὴ', + 'ή' => 'Ή', + 'ὶ' => 'Ὶ', + 'ί' => 'Ί', + 'ὸ' => 'Ὸ', + 'ό' => 'Ό', + 'ὺ' => 'Ὺ', + 'ύ' => 'Ύ', + 'ὼ' => 'Ὼ', + 'ώ' => 'Ώ', + 'ᾀ' => 'ἈΙ', + 'ᾁ' => 'ἉΙ', + 'ᾂ' => 'ἊΙ', + 'ᾃ' => 'ἋΙ', + 'ᾄ' => 'ἌΙ', + 'ᾅ' => 'ἍΙ', + 'ᾆ' => 'ἎΙ', + 'ᾇ' => 'ἏΙ', + 'ᾐ' => 'ἨΙ', + 'ᾑ' => 'ἩΙ', + 'ᾒ' => 'ἪΙ', + 'ᾓ' => 'ἫΙ', + 'ᾔ' => 'ἬΙ', + 'ᾕ' => 'ἭΙ', + 'ᾖ' => 'ἮΙ', + 'ᾗ' => 'ἯΙ', + 'ᾠ' => 'ὨΙ', + 'ᾡ' => 'ὩΙ', + 'ᾢ' => 'ὪΙ', + 'ᾣ' => 'ὫΙ', + 'ᾤ' => 'ὬΙ', + 'ᾥ' => 'ὭΙ', + 'ᾦ' => 'ὮΙ', + 'ᾧ' => 'ὯΙ', + 'ᾰ' => 'Ᾰ', + 'ᾱ' => 'Ᾱ', + 'ᾳ' => 'ΑΙ', + 'ι' => 'Ι', + 'ῃ' => 'ΗΙ', + 'ῐ' => 'Ῐ', + 'ῑ' => 'Ῑ', + 'ῠ' => 'Ῠ', + 'ῡ' => 'Ῡ', + 'ῥ' => 'Ῥ', + 'ῳ' => 'ΩΙ', + 'ⅎ' => 'Ⅎ', + 'ⅰ' => 'Ⅰ', + 'ⅱ' => 'Ⅱ', + 'ⅲ' => 'Ⅲ', + 'ⅳ' => 'Ⅳ', + 'ⅴ' => 'Ⅴ', + 'ⅵ' => 'Ⅵ', + 'ⅶ' => 'Ⅶ', + 'ⅷ' => 'Ⅷ', + 'ⅸ' => 'Ⅸ', + 'ⅹ' => 'Ⅹ', + 'ⅺ' => 'Ⅺ', + 'ⅻ' => 'Ⅻ', + 'ⅼ' => 'Ⅼ', + 'ⅽ' => 'Ⅽ', + 'ⅾ' => 'Ⅾ', + 'ⅿ' => 'Ⅿ', + 'ↄ' => 'Ↄ', + 'ⓐ' => 'Ⓐ', + 'ⓑ' => 'Ⓑ', + 'ⓒ' => 'Ⓒ', + 'ⓓ' => 'Ⓓ', + 'ⓔ' => 'Ⓔ', + 'ⓕ' => 'Ⓕ', + 'ⓖ' => 'Ⓖ', + 'ⓗ' => 'Ⓗ', + 'ⓘ' => 'Ⓘ', + 'ⓙ' => 'Ⓙ', + 'ⓚ' => 'Ⓚ', + 'ⓛ' => 'Ⓛ', + 'ⓜ' => 'Ⓜ', + 'ⓝ' => 'Ⓝ', + 'ⓞ' => 'Ⓞ', + 'ⓟ' => 'Ⓟ', + 'ⓠ' => 'Ⓠ', + 'ⓡ' => 'Ⓡ', + 'ⓢ' => 'Ⓢ', + 'ⓣ' => 'Ⓣ', + 'ⓤ' => 'Ⓤ', + 'ⓥ' => 'Ⓥ', + 'ⓦ' => 'Ⓦ', + 'ⓧ' => 'Ⓧ', + 'ⓨ' => 'Ⓨ', + 'ⓩ' => 'Ⓩ', + 'ⰰ' => 'Ⰰ', + 'ⰱ' => 'Ⰱ', + 'ⰲ' => 'Ⰲ', + 'ⰳ' => 'Ⰳ', + 'ⰴ' => 'Ⰴ', + 'ⰵ' => 'Ⰵ', + 'ⰶ' => 'Ⰶ', + 'ⰷ' => 'Ⰷ', + 'ⰸ' => 'Ⰸ', + 'ⰹ' => 'Ⰹ', + 'ⰺ' => 'Ⰺ', + 'ⰻ' => 'Ⰻ', + 'ⰼ' => 'Ⰼ', + 'ⰽ' => 'Ⰽ', + 'ⰾ' => 'Ⰾ', + 'ⰿ' => 'Ⰿ', + 'ⱀ' => 'Ⱀ', + 'ⱁ' => 'Ⱁ', + 'ⱂ' => 'Ⱂ', + 'ⱃ' => 'Ⱃ', + 'ⱄ' => 'Ⱄ', + 'ⱅ' => 'Ⱅ', + 'ⱆ' => 'Ⱆ', + 'ⱇ' => 'Ⱇ', + 'ⱈ' => 'Ⱈ', + 'ⱉ' => 'Ⱉ', + 'ⱊ' => 'Ⱊ', + 'ⱋ' => 'Ⱋ', + 'ⱌ' => 'Ⱌ', + 'ⱍ' => 'Ⱍ', + 'ⱎ' => 'Ⱎ', + 'ⱏ' => 'Ⱏ', + 'ⱐ' => 'Ⱐ', + 'ⱑ' => 'Ⱑ', + 'ⱒ' => 'Ⱒ', + 'ⱓ' => 'Ⱓ', + 'ⱔ' => 'Ⱔ', + 'ⱕ' => 'Ⱕ', + 'ⱖ' => 'Ⱖ', + 'ⱗ' => 'Ⱗ', + 'ⱘ' => 'Ⱘ', + 'ⱙ' => 'Ⱙ', + 'ⱚ' => 'Ⱚ', + 'ⱛ' => 'Ⱛ', + 'ⱜ' => 'Ⱜ', + 'ⱝ' => 'Ⱝ', + 'ⱞ' => 'Ⱞ', + 'ⱡ' => 'Ⱡ', + 'ⱥ' => 'Ⱥ', + 'ⱦ' => 'Ⱦ', + 'ⱨ' => 'Ⱨ', + 'ⱪ' => 'Ⱪ', + 'ⱬ' => 'Ⱬ', + 'ⱳ' => 'Ⱳ', + 'ⱶ' => 'Ⱶ', + 'ⲁ' => 'Ⲁ', + 'ⲃ' => 'Ⲃ', + 'ⲅ' => 'Ⲅ', + 'ⲇ' => 'Ⲇ', + 'ⲉ' => 'Ⲉ', + 'ⲋ' => 'Ⲋ', + 'ⲍ' => 'Ⲍ', + 'ⲏ' => 'Ⲏ', + 'ⲑ' => 'Ⲑ', + 'ⲓ' => 'Ⲓ', + 'ⲕ' => 'Ⲕ', + 'ⲗ' => 'Ⲗ', + 'ⲙ' => 'Ⲙ', + 'ⲛ' => 'Ⲛ', + 'ⲝ' => 'Ⲝ', + 'ⲟ' => 'Ⲟ', + 'ⲡ' => 'Ⲡ', + 'ⲣ' => 'Ⲣ', + 'ⲥ' => 'Ⲥ', + 'ⲧ' => 'Ⲧ', + 'ⲩ' => 'Ⲩ', + 'ⲫ' => 'Ⲫ', + 'ⲭ' => 'Ⲭ', + 'ⲯ' => 'Ⲯ', + 'ⲱ' => 'Ⲱ', + 'ⲳ' => 'Ⲳ', + 'ⲵ' => 'Ⲵ', + 'ⲷ' => 'Ⲷ', + 'ⲹ' => 'Ⲹ', + 'ⲻ' => 'Ⲻ', + 'ⲽ' => 'Ⲽ', + 'ⲿ' => 'Ⲿ', + 'ⳁ' => 'Ⳁ', + 'ⳃ' => 'Ⳃ', + 'ⳅ' => 'Ⳅ', + 'ⳇ' => 'Ⳇ', + 'ⳉ' => 'Ⳉ', + 'ⳋ' => 'Ⳋ', + 'ⳍ' => 'Ⳍ', + 'ⳏ' => 'Ⳏ', + 'ⳑ' => 'Ⳑ', + 'ⳓ' => 'Ⳓ', + 'ⳕ' => 'Ⳕ', + 'ⳗ' => 'Ⳗ', + 'ⳙ' => 'Ⳙ', + 'ⳛ' => 'Ⳛ', + 'ⳝ' => 'Ⳝ', + 'ⳟ' => 'Ⳟ', + 'ⳡ' => 'Ⳡ', + 'ⳣ' => 'Ⳣ', + 'ⳬ' => 'Ⳬ', + 'ⳮ' => 'Ⳮ', + 'ⳳ' => 'Ⳳ', + 'ⴀ' => 'Ⴀ', + 'ⴁ' => 'Ⴁ', + 'ⴂ' => 'Ⴂ', + 'ⴃ' => 'Ⴃ', + 'ⴄ' => 'Ⴄ', + 'ⴅ' => 'Ⴅ', + 'ⴆ' => 'Ⴆ', + 'ⴇ' => 'Ⴇ', + 'ⴈ' => 'Ⴈ', + 'ⴉ' => 'Ⴉ', + 'ⴊ' => 'Ⴊ', + 'ⴋ' => 'Ⴋ', + 'ⴌ' => 'Ⴌ', + 'ⴍ' => 'Ⴍ', + 'ⴎ' => 'Ⴎ', + 'ⴏ' => 'Ⴏ', + 'ⴐ' => 'Ⴐ', + 'ⴑ' => 'Ⴑ', + 'ⴒ' => 'Ⴒ', + 'ⴓ' => 'Ⴓ', + 'ⴔ' => 'Ⴔ', + 'ⴕ' => 'Ⴕ', + 'ⴖ' => 'Ⴖ', + 'ⴗ' => 'Ⴗ', + 'ⴘ' => 'Ⴘ', + 'ⴙ' => 'Ⴙ', + 'ⴚ' => 'Ⴚ', + 'ⴛ' => 'Ⴛ', + 'ⴜ' => 'Ⴜ', + 'ⴝ' => 'Ⴝ', + 'ⴞ' => 'Ⴞ', + 'ⴟ' => 'Ⴟ', + 'ⴠ' => 'Ⴠ', + 'ⴡ' => 'Ⴡ', + 'ⴢ' => 'Ⴢ', + 'ⴣ' => 'Ⴣ', + 'ⴤ' => 'Ⴤ', + 'ⴥ' => 'Ⴥ', + 'ⴧ' => 'Ⴧ', + 'ⴭ' => 'Ⴭ', + 'ꙁ' => 'Ꙁ', + 'ꙃ' => 'Ꙃ', + 'ꙅ' => 'Ꙅ', + 'ꙇ' => 'Ꙇ', + 'ꙉ' => 'Ꙉ', + 'ꙋ' => 'Ꙋ', + 'ꙍ' => 'Ꙍ', + 'ꙏ' => 'Ꙏ', + 'ꙑ' => 'Ꙑ', + 'ꙓ' => 'Ꙓ', + 'ꙕ' => 'Ꙕ', + 'ꙗ' => 'Ꙗ', + 'ꙙ' => 'Ꙙ', + 'ꙛ' => 'Ꙛ', + 'ꙝ' => 'Ꙝ', + 'ꙟ' => 'Ꙟ', + 'ꙡ' => 'Ꙡ', + 'ꙣ' => 'Ꙣ', + 'ꙥ' => 'Ꙥ', + 'ꙧ' => 'Ꙧ', + 'ꙩ' => 'Ꙩ', + 'ꙫ' => 'Ꙫ', + 'ꙭ' => 'Ꙭ', + 'ꚁ' => 'Ꚁ', + 'ꚃ' => 'Ꚃ', + 'ꚅ' => 'Ꚅ', + 'ꚇ' => 'Ꚇ', + 'ꚉ' => 'Ꚉ', + 'ꚋ' => 'Ꚋ', + 'ꚍ' => 'Ꚍ', + 'ꚏ' => 'Ꚏ', + 'ꚑ' => 'Ꚑ', + 'ꚓ' => 'Ꚓ', + 'ꚕ' => 'Ꚕ', + 'ꚗ' => 'Ꚗ', + 'ꚙ' => 'Ꚙ', + 'ꚛ' => 'Ꚛ', + 'ꜣ' => 'Ꜣ', + 'ꜥ' => 'Ꜥ', + 'ꜧ' => 'Ꜧ', + 'ꜩ' => 'Ꜩ', + 'ꜫ' => 'Ꜫ', + 'ꜭ' => 'Ꜭ', + 'ꜯ' => 'Ꜯ', + 'ꜳ' => 'Ꜳ', + 'ꜵ' => 'Ꜵ', + 'ꜷ' => 'Ꜷ', + 'ꜹ' => 'Ꜹ', + 'ꜻ' => 'Ꜻ', + 'ꜽ' => 'Ꜽ', + 'ꜿ' => 'Ꜿ', + 'ꝁ' => 'Ꝁ', + 'ꝃ' => 'Ꝃ', + 'ꝅ' => 'Ꝅ', + 'ꝇ' => 'Ꝇ', + 'ꝉ' => 'Ꝉ', + 'ꝋ' => 'Ꝋ', + 'ꝍ' => 'Ꝍ', + 'ꝏ' => 'Ꝏ', + 'ꝑ' => 'Ꝑ', + 'ꝓ' => 'Ꝓ', + 'ꝕ' => 'Ꝕ', + 'ꝗ' => 'Ꝗ', + 'ꝙ' => 'Ꝙ', + 'ꝛ' => 'Ꝛ', + 'ꝝ' => 'Ꝝ', + 'ꝟ' => 'Ꝟ', + 'ꝡ' => 'Ꝡ', + 'ꝣ' => 'Ꝣ', + 'ꝥ' => 'Ꝥ', + 'ꝧ' => 'Ꝧ', + 'ꝩ' => 'Ꝩ', + 'ꝫ' => 'Ꝫ', + 'ꝭ' => 'Ꝭ', + 'ꝯ' => 'Ꝯ', + 'ꝺ' => 'Ꝺ', + 'ꝼ' => 'Ꝼ', + 'ꝿ' => 'Ꝿ', + 'ꞁ' => 'Ꞁ', + 'ꞃ' => 'Ꞃ', + 'ꞅ' => 'Ꞅ', + 'ꞇ' => 'Ꞇ', + 'ꞌ' => 'Ꞌ', + 'ꞑ' => 'Ꞑ', + 'ꞓ' => 'Ꞓ', + 'ꞔ' => 'Ꞔ', + 'ꞗ' => 'Ꞗ', + 'ꞙ' => 'Ꞙ', + 'ꞛ' => 'Ꞛ', + 'ꞝ' => 'Ꞝ', + 'ꞟ' => 'Ꞟ', + 'ꞡ' => 'Ꞡ', + 'ꞣ' => 'Ꞣ', + 'ꞥ' => 'Ꞥ', + 'ꞧ' => 'Ꞧ', + 'ꞩ' => 'Ꞩ', + 'ꞵ' => 'Ꞵ', + 'ꞷ' => 'Ꞷ', + 'ꞹ' => 'Ꞹ', + 'ꞻ' => 'Ꞻ', + 'ꞽ' => 'Ꞽ', + 'ꞿ' => 'Ꞿ', + 'ꟃ' => 'Ꟃ', + 'ꟈ' => 'Ꟈ', + 'ꟊ' => 'Ꟊ', + 'ꟶ' => 'Ꟶ', + 'ꭓ' => 'Ꭓ', + 'ꭰ' => 'Ꭰ', + 'ꭱ' => 'Ꭱ', + 'ꭲ' => 'Ꭲ', + 'ꭳ' => 'Ꭳ', + 'ꭴ' => 'Ꭴ', + 'ꭵ' => 'Ꭵ', + 'ꭶ' => 'Ꭶ', + 'ꭷ' => 'Ꭷ', + 'ꭸ' => 'Ꭸ', + 'ꭹ' => 'Ꭹ', + 'ꭺ' => 'Ꭺ', + 'ꭻ' => 'Ꭻ', + 'ꭼ' => 'Ꭼ', + 'ꭽ' => 'Ꭽ', + 'ꭾ' => 'Ꭾ', + 'ꭿ' => 'Ꭿ', + 'ꮀ' => 'Ꮀ', + 'ꮁ' => 'Ꮁ', + 'ꮂ' => 'Ꮂ', + 'ꮃ' => 'Ꮃ', + 'ꮄ' => 'Ꮄ', + 'ꮅ' => 'Ꮅ', + 'ꮆ' => 'Ꮆ', + 'ꮇ' => 'Ꮇ', + 'ꮈ' => 'Ꮈ', + 'ꮉ' => 'Ꮉ', + 'ꮊ' => 'Ꮊ', + 'ꮋ' => 'Ꮋ', + 'ꮌ' => 'Ꮌ', + 'ꮍ' => 'Ꮍ', + 'ꮎ' => 'Ꮎ', + 'ꮏ' => 'Ꮏ', + 'ꮐ' => 'Ꮐ', + 'ꮑ' => 'Ꮑ', + 'ꮒ' => 'Ꮒ', + 'ꮓ' => 'Ꮓ', + 'ꮔ' => 'Ꮔ', + 'ꮕ' => 'Ꮕ', + 'ꮖ' => 'Ꮖ', + 'ꮗ' => 'Ꮗ', + 'ꮘ' => 'Ꮘ', + 'ꮙ' => 'Ꮙ', + 'ꮚ' => 'Ꮚ', + 'ꮛ' => 'Ꮛ', + 'ꮜ' => 'Ꮜ', + 'ꮝ' => 'Ꮝ', + 'ꮞ' => 'Ꮞ', + 'ꮟ' => 'Ꮟ', + 'ꮠ' => 'Ꮠ', + 'ꮡ' => 'Ꮡ', + 'ꮢ' => 'Ꮢ', + 'ꮣ' => 'Ꮣ', + 'ꮤ' => 'Ꮤ', + 'ꮥ' => 'Ꮥ', + 'ꮦ' => 'Ꮦ', + 'ꮧ' => 'Ꮧ', + 'ꮨ' => 'Ꮨ', + 'ꮩ' => 'Ꮩ', + 'ꮪ' => 'Ꮪ', + 'ꮫ' => 'Ꮫ', + 'ꮬ' => 'Ꮬ', + 'ꮭ' => 'Ꮭ', + 'ꮮ' => 'Ꮮ', + 'ꮯ' => 'Ꮯ', + 'ꮰ' => 'Ꮰ', + 'ꮱ' => 'Ꮱ', + 'ꮲ' => 'Ꮲ', + 'ꮳ' => 'Ꮳ', + 'ꮴ' => 'Ꮴ', + 'ꮵ' => 'Ꮵ', + 'ꮶ' => 'Ꮶ', + 'ꮷ' => 'Ꮷ', + 'ꮸ' => 'Ꮸ', + 'ꮹ' => 'Ꮹ', + 'ꮺ' => 'Ꮺ', + 'ꮻ' => 'Ꮻ', + 'ꮼ' => 'Ꮼ', + 'ꮽ' => 'Ꮽ', + 'ꮾ' => 'Ꮾ', + 'ꮿ' => 'Ꮿ', + 'a' => 'A', + 'b' => 'B', + 'c' => 'C', + 'd' => 'D', + 'e' => 'E', + 'f' => 'F', + 'g' => 'G', + 'h' => 'H', + 'i' => 'I', + 'j' => 'J', + 'k' => 'K', + 'l' => 'L', + 'm' => 'M', + 'n' => 'N', + 'o' => 'O', + 'p' => 'P', + 'q' => 'Q', + 'r' => 'R', + 's' => 'S', + 't' => 'T', + 'u' => 'U', + 'v' => 'V', + 'w' => 'W', + 'x' => 'X', + 'y' => 'Y', + 'z' => 'Z', + '𐐨' => '𐐀', + '𐐩' => '𐐁', + '𐐪' => '𐐂', + '𐐫' => '𐐃', + '𐐬' => '𐐄', + '𐐭' => '𐐅', + '𐐮' => '𐐆', + '𐐯' => '𐐇', + '𐐰' => '𐐈', + '𐐱' => '𐐉', + '𐐲' => '𐐊', + '𐐳' => '𐐋', + '𐐴' => '𐐌', + '𐐵' => '𐐍', + '𐐶' => '𐐎', + '𐐷' => '𐐏', + '𐐸' => '𐐐', + '𐐹' => '𐐑', + '𐐺' => '𐐒', + '𐐻' => '𐐓', + '𐐼' => '𐐔', + '𐐽' => '𐐕', + '𐐾' => '𐐖', + '𐐿' => '𐐗', + '𐑀' => '𐐘', + '𐑁' => '𐐙', + '𐑂' => '𐐚', + '𐑃' => '𐐛', + '𐑄' => '𐐜', + '𐑅' => '𐐝', + '𐑆' => '𐐞', + '𐑇' => '𐐟', + '𐑈' => '𐐠', + '𐑉' => '𐐡', + '𐑊' => '𐐢', + '𐑋' => '𐐣', + '𐑌' => '𐐤', + '𐑍' => '𐐥', + '𐑎' => '𐐦', + '𐑏' => '𐐧', + '𐓘' => '𐒰', + '𐓙' => '𐒱', + '𐓚' => '𐒲', + '𐓛' => '𐒳', + '𐓜' => '𐒴', + '𐓝' => '𐒵', + '𐓞' => '𐒶', + '𐓟' => '𐒷', + '𐓠' => '𐒸', + '𐓡' => '𐒹', + '𐓢' => '𐒺', + '𐓣' => '𐒻', + '𐓤' => '𐒼', + '𐓥' => '𐒽', + '𐓦' => '𐒾', + '𐓧' => '𐒿', + '𐓨' => '𐓀', + '𐓩' => '𐓁', + '𐓪' => '𐓂', + '𐓫' => '𐓃', + '𐓬' => '𐓄', + '𐓭' => '𐓅', + '𐓮' => '𐓆', + '𐓯' => '𐓇', + '𐓰' => '𐓈', + '𐓱' => '𐓉', + '𐓲' => '𐓊', + '𐓳' => '𐓋', + '𐓴' => '𐓌', + '𐓵' => '𐓍', + '𐓶' => '𐓎', + '𐓷' => '𐓏', + '𐓸' => '𐓐', + '𐓹' => '𐓑', + '𐓺' => '𐓒', + '𐓻' => '𐓓', + '𐳀' => '𐲀', + '𐳁' => '𐲁', + '𐳂' => '𐲂', + '𐳃' => '𐲃', + '𐳄' => '𐲄', + '𐳅' => '𐲅', + '𐳆' => '𐲆', + '𐳇' => '𐲇', + '𐳈' => '𐲈', + '𐳉' => '𐲉', + '𐳊' => '𐲊', + '𐳋' => '𐲋', + '𐳌' => '𐲌', + '𐳍' => '𐲍', + '𐳎' => '𐲎', + '𐳏' => '𐲏', + '𐳐' => '𐲐', + '𐳑' => '𐲑', + '𐳒' => '𐲒', + '𐳓' => '𐲓', + '𐳔' => '𐲔', + '𐳕' => '𐲕', + '𐳖' => '𐲖', + '𐳗' => '𐲗', + '𐳘' => '𐲘', + '𐳙' => '𐲙', + '𐳚' => '𐲚', + '𐳛' => '𐲛', + '𐳜' => '𐲜', + '𐳝' => '𐲝', + '𐳞' => '𐲞', + '𐳟' => '𐲟', + '𐳠' => '𐲠', + '𐳡' => '𐲡', + '𐳢' => '𐲢', + '𐳣' => '𐲣', + '𐳤' => '𐲤', + '𐳥' => '𐲥', + '𐳦' => '𐲦', + '𐳧' => '𐲧', + '𐳨' => '𐲨', + '𐳩' => '𐲩', + '𐳪' => '𐲪', + '𐳫' => '𐲫', + '𐳬' => '𐲬', + '𐳭' => '𐲭', + '𐳮' => '𐲮', + '𐳯' => '𐲯', + '𐳰' => '𐲰', + '𐳱' => '𐲱', + '𐳲' => '𐲲', + '𑣀' => '𑢠', + '𑣁' => '𑢡', + '𑣂' => '𑢢', + '𑣃' => '𑢣', + '𑣄' => '𑢤', + '𑣅' => '𑢥', + '𑣆' => '𑢦', + '𑣇' => '𑢧', + '𑣈' => '𑢨', + '𑣉' => '𑢩', + '𑣊' => '𑢪', + '𑣋' => '𑢫', + '𑣌' => '𑢬', + '𑣍' => '𑢭', + '𑣎' => '𑢮', + '𑣏' => '𑢯', + '𑣐' => '𑢰', + '𑣑' => '𑢱', + '𑣒' => '𑢲', + '𑣓' => '𑢳', + '𑣔' => '𑢴', + '𑣕' => '𑢵', + '𑣖' => '𑢶', + '𑣗' => '𑢷', + '𑣘' => '𑢸', + '𑣙' => '𑢹', + '𑣚' => '𑢺', + '𑣛' => '𑢻', + '𑣜' => '𑢼', + '𑣝' => '𑢽', + '𑣞' => '𑢾', + '𑣟' => '𑢿', + '𖹠' => '𖹀', + '𖹡' => '𖹁', + '𖹢' => '𖹂', + '𖹣' => '𖹃', + '𖹤' => '𖹄', + '𖹥' => '𖹅', + '𖹦' => '𖹆', + '𖹧' => '𖹇', + '𖹨' => '𖹈', + '𖹩' => '𖹉', + '𖹪' => '𖹊', + '𖹫' => '𖹋', + '𖹬' => '𖹌', + '𖹭' => '𖹍', + '𖹮' => '𖹎', + '𖹯' => '𖹏', + '𖹰' => '𖹐', + '𖹱' => '𖹑', + '𖹲' => '𖹒', + '𖹳' => '𖹓', + '𖹴' => '𖹔', + '𖹵' => '𖹕', + '𖹶' => '𖹖', + '𖹷' => '𖹗', + '𖹸' => '𖹘', + '𖹹' => '𖹙', + '𖹺' => '𖹚', + '𖹻' => '𖹛', + '𖹼' => '𖹜', + '𖹽' => '𖹝', + '𖹾' => '𖹞', + '𖹿' => '𖹟', + '𞤢' => '𞤀', + '𞤣' => '𞤁', + '𞤤' => '𞤂', + '𞤥' => '𞤃', + '𞤦' => '𞤄', + '𞤧' => '𞤅', + '𞤨' => '𞤆', + '𞤩' => '𞤇', + '𞤪' => '𞤈', + '𞤫' => '𞤉', + '𞤬' => '𞤊', + '𞤭' => '𞤋', + '𞤮' => '𞤌', + '𞤯' => '𞤍', + '𞤰' => '𞤎', + '𞤱' => '𞤏', + '𞤲' => '𞤐', + '𞤳' => '𞤑', + '𞤴' => '𞤒', + '𞤵' => '𞤓', + '𞤶' => '𞤔', + '𞤷' => '𞤕', + '𞤸' => '𞤖', + '𞤹' => '𞤗', + '𞤺' => '𞤘', + '𞤻' => '𞤙', + '𞤼' => '𞤚', + '𞤽' => '𞤛', + '𞤾' => '𞤜', + '𞤿' => '𞤝', + '𞥀' => '𞤞', + '𞥁' => '𞤟', + '𞥂' => '𞤠', + '𞥃' => '𞤡', + 'ß' => 'SS', + 'ff' => 'FF', + 'fi' => 'FI', + 'fl' => 'FL', + 'ffi' => 'FFI', + 'ffl' => 'FFL', + 'ſt' => 'ST', + 'st' => 'ST', + 'և' => 'ԵՒ', + 'ﬓ' => 'ՄՆ', + 'ﬔ' => 'ՄԵ', + 'ﬕ' => 'ՄԻ', + 'ﬖ' => 'ՎՆ', + 'ﬗ' => 'ՄԽ', + 'ʼn' => 'ʼN', + 'ΐ' => 'Ϊ́', + 'ΰ' => 'Ϋ́', + 'ǰ' => 'J̌', + 'ẖ' => 'H̱', + 'ẗ' => 'T̈', + 'ẘ' => 'W̊', + 'ẙ' => 'Y̊', + 'ẚ' => 'Aʾ', + 'ὐ' => 'Υ̓', + 'ὒ' => 'Υ̓̀', + 'ὔ' => 'Υ̓́', + 'ὖ' => 'Υ̓͂', + 'ᾶ' => 'Α͂', + 'ῆ' => 'Η͂', + 'ῒ' => 'Ϊ̀', + 'ΐ' => 'Ϊ́', + 'ῖ' => 'Ι͂', + 'ῗ' => 'Ϊ͂', + 'ῢ' => 'Ϋ̀', + 'ΰ' => 'Ϋ́', + 'ῤ' => 'Ρ̓', + 'ῦ' => 'Υ͂', + 'ῧ' => 'Ϋ͂', + 'ῶ' => 'Ω͂', + 'ᾈ' => 'ἈΙ', + 'ᾉ' => 'ἉΙ', + 'ᾊ' => 'ἊΙ', + 'ᾋ' => 'ἋΙ', + 'ᾌ' => 'ἌΙ', + 'ᾍ' => 'ἍΙ', + 'ᾎ' => 'ἎΙ', + 'ᾏ' => 'ἏΙ', + 'ᾘ' => 'ἨΙ', + 'ᾙ' => 'ἩΙ', + 'ᾚ' => 'ἪΙ', + 'ᾛ' => 'ἫΙ', + 'ᾜ' => 'ἬΙ', + 'ᾝ' => 'ἭΙ', + 'ᾞ' => 'ἮΙ', + 'ᾟ' => 'ἯΙ', + 'ᾨ' => 'ὨΙ', + 'ᾩ' => 'ὩΙ', + 'ᾪ' => 'ὪΙ', + 'ᾫ' => 'ὫΙ', + 'ᾬ' => 'ὬΙ', + 'ᾭ' => 'ὭΙ', + 'ᾮ' => 'ὮΙ', + 'ᾯ' => 'ὯΙ', + 'ᾼ' => 'ΑΙ', + 'ῌ' => 'ΗΙ', + 'ῼ' => 'ΩΙ', + 'ᾲ' => 'ᾺΙ', + 'ᾴ' => 'ΆΙ', + 'ῂ' => 'ῊΙ', + 'ῄ' => 'ΉΙ', + 'ῲ' => 'ῺΙ', + 'ῴ' => 'ΏΙ', + 'ᾷ' => 'Α͂Ι', + 'ῇ' => 'Η͂Ι', + 'ῷ' => 'Ω͂Ι', +); diff --git a/modules/inpostizi/vendor/symfony/polyfill-mbstring/bootstrap.php b/modules/inpostizi/vendor/symfony/polyfill-mbstring/bootstrap.php new file mode 100644 index 00000000..ecf1a035 --- /dev/null +++ b/modules/inpostizi/vendor/symfony/polyfill-mbstring/bootstrap.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Mbstring as p; + +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + +if (!function_exists('mb_convert_encoding')) { + function mb_convert_encoding($string, $to_encoding, $from_encoding = null) { return p\Mbstring::mb_convert_encoding($string, $to_encoding, $from_encoding); } +} +if (!function_exists('mb_decode_mimeheader')) { + function mb_decode_mimeheader($string) { return p\Mbstring::mb_decode_mimeheader($string); } +} +if (!function_exists('mb_encode_mimeheader')) { + function mb_encode_mimeheader($string, $charset = null, $transfer_encoding = null, $newline = "\r\n", $indent = 0) { return p\Mbstring::mb_encode_mimeheader($string, $charset, $transfer_encoding, $newline, $indent); } +} +if (!function_exists('mb_decode_numericentity')) { + function mb_decode_numericentity($string, $map, $encoding = null) { return p\Mbstring::mb_decode_numericentity($string, $map, $encoding); } +} +if (!function_exists('mb_encode_numericentity')) { + function mb_encode_numericentity($string, $map, $encoding = null, $hex = false) { return p\Mbstring::mb_encode_numericentity($string, $map, $encoding, $hex); } +} +if (!function_exists('mb_convert_case')) { + function mb_convert_case($string, $mode, $encoding = null) { return p\Mbstring::mb_convert_case($string, $mode, $encoding); } +} +if (!function_exists('mb_internal_encoding')) { + function mb_internal_encoding($encoding = null) { return p\Mbstring::mb_internal_encoding($encoding); } +} +if (!function_exists('mb_language')) { + function mb_language($language = null) { return p\Mbstring::mb_language($language); } +} +if (!function_exists('mb_list_encodings')) { + function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); } +} +if (!function_exists('mb_encoding_aliases')) { + function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); } +} +if (!function_exists('mb_check_encoding')) { + function mb_check_encoding($value = null, $encoding = null) { return p\Mbstring::mb_check_encoding($value, $encoding); } +} +if (!function_exists('mb_detect_encoding')) { + function mb_detect_encoding($string, $encodings = null, $strict = false) { return p\Mbstring::mb_detect_encoding($string, $encodings, $strict); } +} +if (!function_exists('mb_detect_order')) { + function mb_detect_order($encoding = null) { return p\Mbstring::mb_detect_order($encoding); } +} +if (!function_exists('mb_parse_str')) { + function mb_parse_str($string, &$result = []) { parse_str($string, $result); return (bool) $result; } +} +if (!function_exists('mb_strlen')) { + function mb_strlen($string, $encoding = null) { return p\Mbstring::mb_strlen($string, $encoding); } +} +if (!function_exists('mb_strpos')) { + function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strpos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strtolower')) { + function mb_strtolower($string, $encoding = null) { return p\Mbstring::mb_strtolower($string, $encoding); } +} +if (!function_exists('mb_strtoupper')) { + function mb_strtoupper($string, $encoding = null) { return p\Mbstring::mb_strtoupper($string, $encoding); } +} +if (!function_exists('mb_substitute_character')) { + function mb_substitute_character($substitute_character = null) { return p\Mbstring::mb_substitute_character($substitute_character); } +} +if (!function_exists('mb_substr')) { + function mb_substr($string, $start, $length = 2147483647, $encoding = null) { return p\Mbstring::mb_substr($string, $start, $length, $encoding); } +} +if (!function_exists('mb_stripos')) { + function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_stripos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_stristr')) { + function mb_stristr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_stristr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strrchr')) { + function mb_strrchr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrchr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strrichr')) { + function mb_strrichr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrichr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strripos')) { + function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strripos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strrpos')) { + function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strrpos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strstr')) { + function mb_strstr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strstr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_get_info')) { + function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); } +} +if (!function_exists('mb_http_output')) { + function mb_http_output($encoding = null) { return p\Mbstring::mb_http_output($encoding); } +} +if (!function_exists('mb_strwidth')) { + function mb_strwidth($string, $encoding = null) { return p\Mbstring::mb_strwidth($string, $encoding); } +} +if (!function_exists('mb_substr_count')) { + function mb_substr_count($haystack, $needle, $encoding = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $encoding); } +} +if (!function_exists('mb_output_handler')) { + function mb_output_handler($string, $status) { return p\Mbstring::mb_output_handler($string, $status); } +} +if (!function_exists('mb_http_input')) { + function mb_http_input($type = null) { return p\Mbstring::mb_http_input($type); } +} + +if (!function_exists('mb_convert_variables')) { + function mb_convert_variables($to_encoding, $from_encoding, &...$vars) { return p\Mbstring::mb_convert_variables($to_encoding, $from_encoding, ...$vars); } +} + +if (!function_exists('mb_ord')) { + function mb_ord($string, $encoding = null) { return p\Mbstring::mb_ord($string, $encoding); } +} +if (!function_exists('mb_chr')) { + function mb_chr($codepoint, $encoding = null) { return p\Mbstring::mb_chr($codepoint, $encoding); } +} +if (!function_exists('mb_scrub')) { + function mb_scrub($string, $encoding = null) { $encoding = null === $encoding ? mb_internal_encoding() : $encoding; return mb_convert_encoding($string, $encoding, $encoding); } +} +if (!function_exists('mb_str_split')) { + function mb_str_split($string, $length = 1, $encoding = null) { return p\Mbstring::mb_str_split($string, $length, $encoding); } +} + +if (!function_exists('mb_str_pad')) { + function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Mbstring::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); } +} + +if (extension_loaded('mbstring')) { + return; +} + +if (!defined('MB_CASE_UPPER')) { + define('MB_CASE_UPPER', 0); +} +if (!defined('MB_CASE_LOWER')) { + define('MB_CASE_LOWER', 1); +} +if (!defined('MB_CASE_TITLE')) { + define('MB_CASE_TITLE', 2); +} diff --git a/modules/inpostizi/vendor/symfony/polyfill-mbstring/bootstrap80.php b/modules/inpostizi/vendor/symfony/polyfill-mbstring/bootstrap80.php new file mode 100644 index 00000000..2f9fb5b4 --- /dev/null +++ b/modules/inpostizi/vendor/symfony/polyfill-mbstring/bootstrap80.php @@ -0,0 +1,147 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Mbstring as p; + +if (!function_exists('mb_convert_encoding')) { + function mb_convert_encoding(array|string|null $string, ?string $to_encoding, array|string|null $from_encoding = null): array|string|false { return p\Mbstring::mb_convert_encoding($string ?? '', (string) $to_encoding, $from_encoding); } +} +if (!function_exists('mb_decode_mimeheader')) { + function mb_decode_mimeheader(?string $string): string { return p\Mbstring::mb_decode_mimeheader((string) $string); } +} +if (!function_exists('mb_encode_mimeheader')) { + function mb_encode_mimeheader(?string $string, ?string $charset = null, ?string $transfer_encoding = null, ?string $newline = "\r\n", ?int $indent = 0): string { return p\Mbstring::mb_encode_mimeheader((string) $string, $charset, $transfer_encoding, (string) $newline, (int) $indent); } +} +if (!function_exists('mb_decode_numericentity')) { + function mb_decode_numericentity(?string $string, array $map, ?string $encoding = null): string { return p\Mbstring::mb_decode_numericentity((string) $string, $map, $encoding); } +} +if (!function_exists('mb_encode_numericentity')) { + function mb_encode_numericentity(?string $string, array $map, ?string $encoding = null, ?bool $hex = false): string { return p\Mbstring::mb_encode_numericentity((string) $string, $map, $encoding, (bool) $hex); } +} +if (!function_exists('mb_convert_case')) { + function mb_convert_case(?string $string, ?int $mode, ?string $encoding = null): string { return p\Mbstring::mb_convert_case((string) $string, (int) $mode, $encoding); } +} +if (!function_exists('mb_internal_encoding')) { + function mb_internal_encoding(?string $encoding = null): string|bool { return p\Mbstring::mb_internal_encoding($encoding); } +} +if (!function_exists('mb_language')) { + function mb_language(?string $language = null): string|bool { return p\Mbstring::mb_language($language); } +} +if (!function_exists('mb_list_encodings')) { + function mb_list_encodings(): array { return p\Mbstring::mb_list_encodings(); } +} +if (!function_exists('mb_encoding_aliases')) { + function mb_encoding_aliases(?string $encoding): array { return p\Mbstring::mb_encoding_aliases((string) $encoding); } +} +if (!function_exists('mb_check_encoding')) { + function mb_check_encoding(array|string|null $value = null, ?string $encoding = null): bool { return p\Mbstring::mb_check_encoding($value, $encoding); } +} +if (!function_exists('mb_detect_encoding')) { + function mb_detect_encoding(?string $string, array|string|null $encodings = null, ?bool $strict = false): string|false { return p\Mbstring::mb_detect_encoding((string) $string, $encodings, (bool) $strict); } +} +if (!function_exists('mb_detect_order')) { + function mb_detect_order(array|string|null $encoding = null): array|bool { return p\Mbstring::mb_detect_order($encoding); } +} +if (!function_exists('mb_parse_str')) { + function mb_parse_str(?string $string, &$result = []): bool { parse_str((string) $string, $result); return (bool) $result; } +} +if (!function_exists('mb_strlen')) { + function mb_strlen(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strlen((string) $string, $encoding); } +} +if (!function_exists('mb_strpos')) { + function mb_strpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strpos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strtolower')) { + function mb_strtolower(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtolower((string) $string, $encoding); } +} +if (!function_exists('mb_strtoupper')) { + function mb_strtoupper(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtoupper((string) $string, $encoding); } +} +if (!function_exists('mb_substitute_character')) { + function mb_substitute_character(string|int|null $substitute_character = null): string|int|bool { return p\Mbstring::mb_substitute_character($substitute_character); } +} +if (!function_exists('mb_substr')) { + function mb_substr(?string $string, ?int $start, ?int $length = null, ?string $encoding = null): string { return p\Mbstring::mb_substr((string) $string, (int) $start, $length, $encoding); } +} +if (!function_exists('mb_stripos')) { + function mb_stripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_stripos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_stristr')) { + function mb_stristr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_stristr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strrchr')) { + function mb_strrchr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrchr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strrichr')) { + function mb_strrichr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrichr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strripos')) { + function mb_strripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strripos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strrpos')) { + function mb_strrpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strrpos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strstr')) { + function mb_strstr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strstr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_get_info')) { + function mb_get_info(?string $type = 'all'): array|string|int|false { return p\Mbstring::mb_get_info((string) $type); } +} +if (!function_exists('mb_http_output')) { + function mb_http_output(?string $encoding = null): string|bool { return p\Mbstring::mb_http_output($encoding); } +} +if (!function_exists('mb_strwidth')) { + function mb_strwidth(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strwidth((string) $string, $encoding); } +} +if (!function_exists('mb_substr_count')) { + function mb_substr_count(?string $haystack, ?string $needle, ?string $encoding = null): int { return p\Mbstring::mb_substr_count((string) $haystack, (string) $needle, $encoding); } +} +if (!function_exists('mb_output_handler')) { + function mb_output_handler(?string $string, ?int $status): string { return p\Mbstring::mb_output_handler((string) $string, (int) $status); } +} +if (!function_exists('mb_http_input')) { + function mb_http_input(?string $type = null): array|string|false { return p\Mbstring::mb_http_input($type); } +} + +if (!function_exists('mb_convert_variables')) { + function mb_convert_variables(?string $to_encoding, array|string|null $from_encoding, mixed &$var, mixed &...$vars): string|false { return p\Mbstring::mb_convert_variables((string) $to_encoding, $from_encoding ?? '', $var, ...$vars); } +} + +if (!function_exists('mb_ord')) { + function mb_ord(?string $string, ?string $encoding = null): int|false { return p\Mbstring::mb_ord((string) $string, $encoding); } +} +if (!function_exists('mb_chr')) { + function mb_chr(?int $codepoint, ?string $encoding = null): string|false { return p\Mbstring::mb_chr((int) $codepoint, $encoding); } +} +if (!function_exists('mb_scrub')) { + function mb_scrub(?string $string, ?string $encoding = null): string { $encoding ??= mb_internal_encoding(); return mb_convert_encoding((string) $string, $encoding, $encoding); } +} +if (!function_exists('mb_str_split')) { + function mb_str_split(?string $string, ?int $length = 1, ?string $encoding = null): array { return p\Mbstring::mb_str_split((string) $string, (int) $length, $encoding); } +} + +if (!function_exists('mb_str_pad')) { + function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Mbstring::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); } +} + +if (extension_loaded('mbstring')) { + return; +} + +if (!defined('MB_CASE_UPPER')) { + define('MB_CASE_UPPER', 0); +} +if (!defined('MB_CASE_LOWER')) { + define('MB_CASE_LOWER', 1); +} +if (!defined('MB_CASE_TITLE')) { + define('MB_CASE_TITLE', 2); +} diff --git a/modules/inpostizi/vendor/symfony/polyfill-mbstring/composer.json b/modules/inpostizi/vendor/symfony/polyfill-mbstring/composer.json new file mode 100644 index 00000000..bd99d4b9 --- /dev/null +++ b/modules/inpostizi/vendor/symfony/polyfill-mbstring/composer.json @@ -0,0 +1,38 @@ +{ + "name": "symfony/polyfill-mbstring", + "type": "library", + "description": "Symfony polyfill for the Mbstring extension", + "keywords": ["polyfill", "shim", "compatibility", "portable", "mbstring"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Mbstring\\": "" }, + "files": [ "bootstrap.php" ] + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/modules/inpostizi/vendor/symfony/polyfill-php80/LICENSE b/modules/inpostizi/vendor/symfony/polyfill-php80/LICENSE new file mode 100644 index 00000000..0ed3a246 --- /dev/null +++ b/modules/inpostizi/vendor/symfony/polyfill-php80/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/modules/inpostizi/vendor/symfony/polyfill-php80/Php80.php b/modules/inpostizi/vendor/symfony/polyfill-php80/Php80.php new file mode 100644 index 00000000..362dd1a9 --- /dev/null +++ b/modules/inpostizi/vendor/symfony/polyfill-php80/Php80.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php80; + +/** + * @author Ion Bazan + * @author Nico Oelgart + * @author Nicolas Grekas + * + * @internal + */ +final class Php80 +{ + public static function fdiv(float $dividend, float $divisor): float + { + return @($dividend / $divisor); + } + + public static function get_debug_type($value): string + { + switch (true) { + case null === $value: return 'null'; + case \is_bool($value): return 'bool'; + case \is_string($value): return 'string'; + case \is_array($value): return 'array'; + case \is_int($value): return 'int'; + case \is_float($value): return 'float'; + case \is_object($value): break; + case $value instanceof \__PHP_Incomplete_Class: return '__PHP_Incomplete_Class'; + default: + if (null === $type = @get_resource_type($value)) { + return 'unknown'; + } + + if ('Unknown' === $type) { + $type = 'closed'; + } + + return "resource ($type)"; + } + + $class = \get_class($value); + + if (false === strpos($class, '@')) { + return $class; + } + + return (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous'; + } + + public static function get_resource_id($res): int + { + if (!\is_resource($res) && null === @get_resource_type($res)) { + throw new \TypeError(sprintf('Argument 1 passed to get_resource_id() must be of the type resource, %s given', get_debug_type($res))); + } + + return (int) $res; + } + + public static function preg_last_error_msg(): string + { + switch (preg_last_error()) { + case \PREG_INTERNAL_ERROR: + return 'Internal error'; + case \PREG_BAD_UTF8_ERROR: + return 'Malformed UTF-8 characters, possibly incorrectly encoded'; + case \PREG_BAD_UTF8_OFFSET_ERROR: + return 'The offset did not correspond to the beginning of a valid UTF-8 code point'; + case \PREG_BACKTRACK_LIMIT_ERROR: + return 'Backtrack limit exhausted'; + case \PREG_RECURSION_LIMIT_ERROR: + return 'Recursion limit exhausted'; + case \PREG_JIT_STACKLIMIT_ERROR: + return 'JIT stack limit exhausted'; + case \PREG_NO_ERROR: + return 'No error'; + default: + return 'Unknown error'; + } + } + + public static function str_contains(string $haystack, string $needle): bool + { + return '' === $needle || false !== strpos($haystack, $needle); + } + + public static function str_starts_with(string $haystack, string $needle): bool + { + return 0 === strncmp($haystack, $needle, \strlen($needle)); + } + + public static function str_ends_with(string $haystack, string $needle): bool + { + if ('' === $needle || $needle === $haystack) { + return true; + } + + if ('' === $haystack) { + return false; + } + + $needleLength = \strlen($needle); + + return $needleLength <= \strlen($haystack) && 0 === substr_compare($haystack, $needle, -$needleLength); + } +} diff --git a/modules/inpostizi/vendor/symfony/polyfill-php80/PhpToken.php b/modules/inpostizi/vendor/symfony/polyfill-php80/PhpToken.php new file mode 100644 index 00000000..fe6e6910 --- /dev/null +++ b/modules/inpostizi/vendor/symfony/polyfill-php80/PhpToken.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php80; + +/** + * @author Fedonyuk Anton + * + * @internal + */ +class PhpToken implements \Stringable +{ + /** + * @var int + */ + public $id; + + /** + * @var string + */ + public $text; + + /** + * @var int + */ + public $line; + + /** + * @var int + */ + public $pos; + + public function __construct(int $id, string $text, int $line = -1, int $position = -1) + { + $this->id = $id; + $this->text = $text; + $this->line = $line; + $this->pos = $position; + } + + public function getTokenName(): ?string + { + if ('UNKNOWN' === $name = token_name($this->id)) { + $name = \strlen($this->text) > 1 || \ord($this->text) < 32 ? null : $this->text; + } + + return $name; + } + + /** + * @param int|string|array $kind + */ + public function is($kind): bool + { + foreach ((array) $kind as $value) { + if (\in_array($value, [$this->id, $this->text], true)) { + return true; + } + } + + return false; + } + + public function isIgnorable(): bool + { + return \in_array($this->id, [\T_WHITESPACE, \T_COMMENT, \T_DOC_COMMENT, \T_OPEN_TAG], true); + } + + public function __toString(): string + { + return (string) $this->text; + } + + /** + * @return static[] + */ + public static function tokenize(string $code, int $flags = 0): array + { + $line = 1; + $position = 0; + $tokens = token_get_all($code, $flags); + foreach ($tokens as $index => $token) { + if (\is_string($token)) { + $id = \ord($token); + $text = $token; + } else { + [$id, $text, $line] = $token; + } + $tokens[$index] = new static($id, $text, $line, $position); + $position += \strlen($text); + } + + return $tokens; + } +} diff --git a/modules/inpostizi/vendor/symfony/polyfill-php80/README.md b/modules/inpostizi/vendor/symfony/polyfill-php80/README.md new file mode 100644 index 00000000..3816c559 --- /dev/null +++ b/modules/inpostizi/vendor/symfony/polyfill-php80/README.md @@ -0,0 +1,25 @@ +Symfony Polyfill / Php80 +======================== + +This component provides features added to PHP 8.0 core: + +- [`Stringable`](https://php.net/stringable) interface +- [`fdiv`](https://php.net/fdiv) +- [`ValueError`](https://php.net/valueerror) class +- [`UnhandledMatchError`](https://php.net/unhandledmatcherror) class +- `FILTER_VALIDATE_BOOL` constant +- [`get_debug_type`](https://php.net/get_debug_type) +- [`PhpToken`](https://php.net/phptoken) class +- [`preg_last_error_msg`](https://php.net/preg_last_error_msg) +- [`str_contains`](https://php.net/str_contains) +- [`str_starts_with`](https://php.net/str_starts_with) +- [`str_ends_with`](https://php.net/str_ends_with) +- [`get_resource_id`](https://php.net/get_resource_id) + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/modules/inpostizi/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php b/modules/inpostizi/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php new file mode 100644 index 00000000..2b955423 --- /dev/null +++ b/modules/inpostizi/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#[Attribute(Attribute::TARGET_CLASS)] +final class Attribute +{ + public const TARGET_CLASS = 1; + public const TARGET_FUNCTION = 2; + public const TARGET_METHOD = 4; + public const TARGET_PROPERTY = 8; + public const TARGET_CLASS_CONSTANT = 16; + public const TARGET_PARAMETER = 32; + public const TARGET_ALL = 63; + public const IS_REPEATABLE = 64; + + /** @var int */ + public $flags; + + public function __construct(int $flags = self::TARGET_ALL) + { + $this->flags = $flags; + } +} diff --git a/modules/inpostizi/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php b/modules/inpostizi/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php new file mode 100644 index 00000000..bd1212f6 --- /dev/null +++ b/modules/inpostizi/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80000 && extension_loaded('tokenizer')) { + class PhpToken extends Symfony\Polyfill\Php80\PhpToken + { + } +} diff --git a/modules/inpostizi/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php b/modules/inpostizi/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php new file mode 100644 index 00000000..7c62d750 --- /dev/null +++ b/modules/inpostizi/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80000) { + interface Stringable + { + /** + * @return string + */ + public function __toString(); + } +} diff --git a/modules/inpostizi/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php b/modules/inpostizi/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php new file mode 100644 index 00000000..01c6c6c8 --- /dev/null +++ b/modules/inpostizi/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80000) { + class UnhandledMatchError extends Error + { + } +} diff --git a/modules/inpostizi/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php b/modules/inpostizi/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php new file mode 100644 index 00000000..783dbc28 --- /dev/null +++ b/modules/inpostizi/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80000) { + class ValueError extends Error + { + } +} diff --git a/modules/inpostizi/vendor/symfony/polyfill-php80/bootstrap.php b/modules/inpostizi/vendor/symfony/polyfill-php80/bootstrap.php new file mode 100644 index 00000000..e5f7dbc1 --- /dev/null +++ b/modules/inpostizi/vendor/symfony/polyfill-php80/bootstrap.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php80 as p; + +if (\PHP_VERSION_ID >= 80000) { + return; +} + +if (!defined('FILTER_VALIDATE_BOOL') && defined('FILTER_VALIDATE_BOOLEAN')) { + define('FILTER_VALIDATE_BOOL', \FILTER_VALIDATE_BOOLEAN); +} + +if (!function_exists('fdiv')) { + function fdiv(float $num1, float $num2): float { return p\Php80::fdiv($num1, $num2); } +} +if (!function_exists('preg_last_error_msg')) { + function preg_last_error_msg(): string { return p\Php80::preg_last_error_msg(); } +} +if (!function_exists('str_contains')) { + function str_contains(?string $haystack, ?string $needle): bool { return p\Php80::str_contains($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('str_starts_with')) { + function str_starts_with(?string $haystack, ?string $needle): bool { return p\Php80::str_starts_with($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('str_ends_with')) { + function str_ends_with(?string $haystack, ?string $needle): bool { return p\Php80::str_ends_with($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('get_debug_type')) { + function get_debug_type($value): string { return p\Php80::get_debug_type($value); } +} +if (!function_exists('get_resource_id')) { + function get_resource_id($resource): int { return p\Php80::get_resource_id($resource); } +} diff --git a/modules/inpostizi/vendor/symfony/polyfill-php80/composer.json b/modules/inpostizi/vendor/symfony/polyfill-php80/composer.json new file mode 100644 index 00000000..46ccde20 --- /dev/null +++ b/modules/inpostizi/vendor/symfony/polyfill-php80/composer.json @@ -0,0 +1,37 @@ +{ + "name": "symfony/polyfill-php80", + "type": "library", + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "keywords": ["polyfill", "shim", "compatibility", "portable"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php80\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "minimum-stability": "dev", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/modules/inpostizi/vendor/symfony/service-contracts/.gitignore b/modules/inpostizi/vendor/symfony/service-contracts/.gitignore new file mode 100644 index 00000000..c49a5d8d --- /dev/null +++ b/modules/inpostizi/vendor/symfony/service-contracts/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/modules/inpostizi/vendor/symfony/service-contracts/LICENSE b/modules/inpostizi/vendor/symfony/service-contracts/LICENSE new file mode 100644 index 00000000..74cdc2db --- /dev/null +++ b/modules/inpostizi/vendor/symfony/service-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2022 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/modules/inpostizi/vendor/symfony/service-contracts/README.md b/modules/inpostizi/vendor/symfony/service-contracts/README.md new file mode 100644 index 00000000..41e054a1 --- /dev/null +++ b/modules/inpostizi/vendor/symfony/service-contracts/README.md @@ -0,0 +1,9 @@ +Symfony Service Contracts +========================= + +A set of abstractions extracted out of the Symfony components. + +Can be used to build on semantics that the Symfony components proved useful - and +that already have battle tested implementations. + +See https://github.com/symfony/contracts/blob/main/README.md for more information. diff --git a/modules/inpostizi/vendor/symfony/service-contracts/ResetInterface.php b/modules/inpostizi/vendor/symfony/service-contracts/ResetInterface.php new file mode 100644 index 00000000..1af1075e --- /dev/null +++ b/modules/inpostizi/vendor/symfony/service-contracts/ResetInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +/** + * Provides a way to reset an object to its initial state. + * + * When calling the "reset()" method on an object, it should be put back to its + * initial state. This usually means clearing any internal buffers and forwarding + * the call to internal dependencies. All properties of the object should be put + * back to the same state it had when it was first ready to use. + * + * This method could be called, for example, to recycle objects that are used as + * services, so that they can be used to handle several requests in the same + * process loop (note that we advise making your services stateless instead of + * implementing this interface when possible.) + */ +interface ResetInterface +{ + public function reset(); +} diff --git a/modules/inpostizi/vendor/symfony/service-contracts/ServiceLocatorTrait.php b/modules/inpostizi/vendor/symfony/service-contracts/ServiceLocatorTrait.php new file mode 100644 index 00000000..da797edc --- /dev/null +++ b/modules/inpostizi/vendor/symfony/service-contracts/ServiceLocatorTrait.php @@ -0,0 +1,128 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\NotFoundExceptionInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(ContainerExceptionInterface::class); +class_exists(NotFoundExceptionInterface::class); + +/** + * A trait to help implement ServiceProviderInterface. + * + * @author Robin Chalas + * @author Nicolas Grekas + */ +trait ServiceLocatorTrait +{ + private $factories; + private $loading = []; + private $providedTypes; + + /** + * @param callable[] $factories + */ + public function __construct(array $factories) + { + $this->factories = $factories; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function has($id) + { + return isset($this->factories[$id]); + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function get($id) + { + if (!isset($this->factories[$id])) { + throw $this->createNotFoundException($id); + } + + if (isset($this->loading[$id])) { + $ids = array_values($this->loading); + $ids = \array_slice($this->loading, array_search($id, $ids)); + $ids[] = $id; + + throw $this->createCircularReferenceException($id, $ids); + } + + $this->loading[$id] = $id; + try { + return $this->factories[$id]($this); + } finally { + unset($this->loading[$id]); + } + } + + /** + * {@inheritdoc} + */ + public function getProvidedServices(): array + { + if (null === $this->providedTypes) { + $this->providedTypes = []; + + foreach ($this->factories as $name => $factory) { + if (!\is_callable($factory)) { + $this->providedTypes[$name] = '?'; + } else { + $type = (new \ReflectionFunction($factory))->getReturnType(); + + $this->providedTypes[$name] = $type ? ($type->allowsNull() ? '?' : '').($type instanceof \ReflectionNamedType ? $type->getName() : $type) : '?'; + } + } + } + + return $this->providedTypes; + } + + private function createNotFoundException(string $id): NotFoundExceptionInterface + { + if (!$alternatives = array_keys($this->factories)) { + $message = 'is empty...'; + } else { + $last = array_pop($alternatives); + if ($alternatives) { + $message = sprintf('only knows about the "%s" and "%s" services.', implode('", "', $alternatives), $last); + } else { + $message = sprintf('only knows about the "%s" service.', $last); + } + } + + if ($this->loading) { + $message = sprintf('The service "%s" has a dependency on a non-existent service "%s". This locator %s', end($this->loading), $id, $message); + } else { + $message = sprintf('Service "%s" not found: the current service locator %s', $id, $message); + } + + return new class($message) extends \InvalidArgumentException implements NotFoundExceptionInterface { + }; + } + + private function createCircularReferenceException(string $id, array $path): ContainerExceptionInterface + { + return new class(sprintf('Circular reference detected for service "%s", path: "%s".', $id, implode(' -> ', $path))) extends \RuntimeException implements ContainerExceptionInterface { + }; + } +} diff --git a/modules/inpostizi/vendor/symfony/service-contracts/ServiceProviderInterface.php b/modules/inpostizi/vendor/symfony/service-contracts/ServiceProviderInterface.php new file mode 100644 index 00000000..c60ad0bd --- /dev/null +++ b/modules/inpostizi/vendor/symfony/service-contracts/ServiceProviderInterface.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +use Psr\Container\ContainerInterface; + +/** + * A ServiceProviderInterface exposes the identifiers and the types of services provided by a container. + * + * @author Nicolas Grekas + * @author Mateusz Sip + */ +interface ServiceProviderInterface extends ContainerInterface +{ + /** + * Returns an associative array of service types keyed by the identifiers provided by the current container. + * + * Examples: + * + * * ['logger' => 'Psr\Log\LoggerInterface'] means the object provides a service named "logger" that implements Psr\Log\LoggerInterface + * * ['foo' => '?'] means the container provides service name "foo" of unspecified type + * * ['bar' => '?Bar\Baz'] means the container provides a service "bar" of type Bar\Baz|null + * + * @return string[] The provided service types, keyed by service names + */ + public function getProvidedServices(): array; +} diff --git a/modules/inpostizi/vendor/symfony/service-contracts/ServiceSubscriberInterface.php b/modules/inpostizi/vendor/symfony/service-contracts/ServiceSubscriberInterface.php new file mode 100644 index 00000000..098ab908 --- /dev/null +++ b/modules/inpostizi/vendor/symfony/service-contracts/ServiceSubscriberInterface.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +/** + * A ServiceSubscriber exposes its dependencies via the static {@link getSubscribedServices} method. + * + * The getSubscribedServices method returns an array of service types required by such instances, + * optionally keyed by the service names used internally. Service types that start with an interrogation + * mark "?" are optional, while the other ones are mandatory service dependencies. + * + * The injected service locators SHOULD NOT allow access to any other services not specified by the method. + * + * It is expected that ServiceSubscriber instances consume PSR-11-based service locators internally. + * This interface does not dictate any injection method for these service locators, although constructor + * injection is recommended. + * + * @author Nicolas Grekas + */ +interface ServiceSubscriberInterface +{ + /** + * Returns an array of service types required by such instances, optionally keyed by the service names used internally. + * + * For mandatory dependencies: + * + * * ['logger' => 'Psr\Log\LoggerInterface'] means the objects use the "logger" name + * internally to fetch a service which must implement Psr\Log\LoggerInterface. + * * ['loggers' => 'Psr\Log\LoggerInterface[]'] means the objects use the "loggers" name + * internally to fetch an iterable of Psr\Log\LoggerInterface instances. + * * ['Psr\Log\LoggerInterface'] is a shortcut for + * * ['Psr\Log\LoggerInterface' => 'Psr\Log\LoggerInterface'] + * + * otherwise: + * + * * ['logger' => '?Psr\Log\LoggerInterface'] denotes an optional dependency + * * ['loggers' => '?Psr\Log\LoggerInterface[]'] denotes an optional iterable dependency + * * ['?Psr\Log\LoggerInterface'] is a shortcut for + * * ['Psr\Log\LoggerInterface' => '?Psr\Log\LoggerInterface'] + * + * @return string[] The required service types, optionally keyed by service names + */ + public static function getSubscribedServices(); +} diff --git a/modules/inpostizi/vendor/symfony/service-contracts/ServiceSubscriberTrait.php b/modules/inpostizi/vendor/symfony/service-contracts/ServiceSubscriberTrait.php new file mode 100644 index 00000000..48c610ab --- /dev/null +++ b/modules/inpostizi/vendor/symfony/service-contracts/ServiceSubscriberTrait.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +use Psr\Container\ContainerInterface; + +/** + * Implementation of ServiceSubscriberInterface that determines subscribed services from + * private method return types. Service ids are available as "ClassName::methodName". + * + * @author Kevin Bond + */ +trait ServiceSubscriberTrait +{ + /** @var ContainerInterface */ + protected $container; + + /** + * {@inheritdoc} + */ + public static function getSubscribedServices(): array + { + $services = method_exists(get_parent_class(self::class) ?: '', __FUNCTION__) ? parent::getSubscribedServices() : []; + + foreach ((new \ReflectionClass(self::class))->getMethods() as $method) { + if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) { + continue; + } + + if (self::class !== $method->getDeclaringClass()->name) { + continue; + } + + if (!($returnType = $method->getReturnType()) instanceof \ReflectionNamedType) { + continue; + } + + if ($returnType->isBuiltin()) { + continue; + } + + $services[self::class.'::'.$method->name] = '?'.$returnType->getName(); + } + + return $services; + } + + /** + * @required + * + * @return ContainerInterface|null + */ + public function setContainer(ContainerInterface $container) + { + $this->container = $container; + + if (method_exists(get_parent_class(self::class) ?: '', __FUNCTION__)) { + return parent::setContainer($container); + } + + return null; + } +} diff --git a/modules/inpostizi/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php b/modules/inpostizi/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php new file mode 100644 index 00000000..2a1b565f --- /dev/null +++ b/modules/inpostizi/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service\Test; + +use PHPUnit\Framework\TestCase; +use Psr\Container\ContainerInterface; +use Symfony\Contracts\Service\ServiceLocatorTrait; + +abstract class ServiceLocatorTest extends TestCase +{ + /** + * @return ContainerInterface + */ + protected function getServiceLocator(array $factories) + { + return new class($factories) implements ContainerInterface { + use ServiceLocatorTrait; + }; + } + + public function testHas() + { + $locator = $this->getServiceLocator([ + 'foo' => function () { return 'bar'; }, + 'bar' => function () { return 'baz'; }, + function () { return 'dummy'; }, + ]); + + $this->assertTrue($locator->has('foo')); + $this->assertTrue($locator->has('bar')); + $this->assertFalse($locator->has('dummy')); + } + + public function testGet() + { + $locator = $this->getServiceLocator([ + 'foo' => function () { return 'bar'; }, + 'bar' => function () { return 'baz'; }, + ]); + + $this->assertSame('bar', $locator->get('foo')); + $this->assertSame('baz', $locator->get('bar')); + } + + public function testGetDoesNotMemoize() + { + $i = 0; + $locator = $this->getServiceLocator([ + 'foo' => function () use (&$i) { + ++$i; + + return 'bar'; + }, + ]); + + $this->assertSame('bar', $locator->get('foo')); + $this->assertSame('bar', $locator->get('foo')); + $this->assertSame(2, $i); + } + + public function testThrowsOnUndefinedInternalService() + { + if (!$this->getExpectedException()) { + $this->expectException(\Psr\Container\NotFoundExceptionInterface::class); + $this->expectExceptionMessage('The service "foo" has a dependency on a non-existent service "bar". This locator only knows about the "foo" service.'); + } + $locator = $this->getServiceLocator([ + 'foo' => function () use (&$locator) { return $locator->get('bar'); }, + ]); + + $locator->get('foo'); + } + + public function testThrowsOnCircularReference() + { + $this->expectException(\Psr\Container\ContainerExceptionInterface::class); + $this->expectExceptionMessage('Circular reference detected for service "bar", path: "bar -> baz -> bar".'); + $locator = $this->getServiceLocator([ + 'foo' => function () use (&$locator) { return $locator->get('bar'); }, + 'bar' => function () use (&$locator) { return $locator->get('baz'); }, + 'baz' => function () use (&$locator) { return $locator->get('bar'); }, + ]); + + $locator->get('foo'); + } +} diff --git a/modules/inpostizi/vendor/symfony/service-contracts/composer.json b/modules/inpostizi/vendor/symfony/service-contracts/composer.json new file mode 100644 index 00000000..e3bf920c --- /dev/null +++ b/modules/inpostizi/vendor/symfony/service-contracts/composer.json @@ -0,0 +1,38 @@ +{ + "name": "symfony/service-contracts", + "type": "library", + "description": "Generic abstractions related to writing services", + "keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1.3", + "psr/container": "^1.0" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "autoload": { + "psr-4": { "Symfony\\Contracts\\Service\\": "" } + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.1-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} diff --git a/modules/inpostizi/views/css/admin/admin-legacy.css b/modules/inpostizi/views/css/admin/admin-legacy.css new file mode 100644 index 00000000..438b0d87 --- /dev/null +++ b/modules/inpostizi/views/css/admin/admin-legacy.css @@ -0,0 +1,3 @@ +.ps-switch{position:relative;display:inline-block;min-width:8.125rem;margin-top:3px;font-size:.813rem;background-color:#eff1f2;border-radius:1px;text-align:center}.ps-switch input{position:absolute;opacity:0}.ps-switch input:disabled+label{cursor:not-allowed}.ps-switch input:disabled:checked+label{background-color:#bbcdd2}.ps-switch label{position:relative;z-index:2;float:left;width:50%;height:100%;padding:.231em .769em;margin:0;vertical-align:middle;text-align:center;text-transform:uppercase;color:#bbcdd2;font-weight:400;cursor:pointer;transition:color .2s ease-out;font-size:12px}.content-div .ps-switch input:checked+label{color:#fff}.content-div .ps-switch>.slide-button{position:absolute;top:0;left:0;padding:0;z-index:1;width:50%;height:100%;color:#fff;background-color:#c05c67;transition:all .3s ease-out}.content-div .ps-switch input:last-of-type:checked~.slide-button{background-color:#2eacce;left:50%}.content-div .ps-switch.ps-switch-sm{min-width:6.25rem;font-size:.75rem}.content-div .ps-switch.ps-switch-lg{font-size:1rem}.content-div .d-none{display:none}.content-div .d-flex{display:flex}.row-flex,.content-div .row{display:flex;flex-wrap:wrap}.row-flex:after,.content-div .row:after,.row-flex:before,.content-div .row:before{display:none}.col{flex:1 1 0;width:auto;max-width:100%}.col-auto{flex:0 0 auto;width:auto;max-width:none}.no-gutters{margin-left:0;margin-right:0}.no-gutters>*{padding-left:0;padding-right:0}.m-0{margin:0 !important}.mt-0,.my-0{margin-top:0 !important}.mr-0,.mx-0{margin-right:0 !important}.mb-0,.my-0{margin-bottom:0 !important}.ml-0,.mx-0{margin-left:0 !important}.m-1{margin:.25rem !important}.mt-1,.my-1{margin-top:.25rem !important}.mr-1,.mx-1{margin-right:.25rem !important}.mb-1,.my-1{margin-bottom:.25rem !important}.ml-1,.mx-1{margin-left:.25rem !important}.m-2{margin:.5rem !important}.mt-2,.my-2{margin-top:.5rem !important}.mr-2,.mx-2{margin-right:.5rem !important}.mb-2,.my-2{margin-bottom:.5rem !important}.ml-2,.mx-2{margin-left:.5rem !important}.m-3{margin:1rem !important}.mt-3,.my-3{margin-top:1rem !important}.mr-3,.mx-3{margin-right:1rem !important}.mb-3,.my-3{margin-bottom:1rem !important}.ml-3,.mx-3{margin-left:1rem !important}.m-4{margin:1.5rem !important}.mt-4,.my-4{margin-top:1.5rem !important}.mr-4,.mx-4{margin-right:1.5rem !important}.mb-4,.my-4{margin-bottom:1.5rem !important}.ml-4,.mx-4{margin-left:1.5rem !important}.m-5{margin:3rem !important}.mt-5,.my-5{margin-top:3rem !important}.mr-5,.mx-5{margin-right:3rem !important}.mb-5,.my-5{margin-bottom:3rem !important}.ml-5,.mx-5{margin-left:3rem !important}.p-0{padding:0 !important}.pt-0,.py-0{padding-top:0 !important}.pr-0,.px-0{padding-right:0 !important}.pb-0,.py-0{padding-bottom:0 !important}.pl-0,.px-0{padding-left:0 !important}.p-1{padding:.25rem !important}.pt-1,.py-1{padding-top:.25rem !important}.pr-1,.px-1{padding-right:.25rem !important}.pb-1,.py-1{padding-bottom:.25rem !important}.pl-1,.px-1{padding-left:.25rem !important}.p-2{padding:.5rem !important}.pt-2,.py-2{padding-top:.5rem !important}.pr-2,.px-2{padding-right:.5rem !important}.pb-2,.py-2{padding-bottom:.5rem !important}.pl-2,.px-2{padding-left:.5rem !important}.p-3{padding:1rem !important}.pt-3,.py-3{padding-top:1rem !important}.pr-3,.px-3{padding-right:1rem !important}.pb-3,.py-3{padding-bottom:1rem !important}.pl-3,.px-3{padding-left:1rem !important}.p-4{padding:1.5rem !important}.pt-4,.py-4{padding-top:1.5rem !important}.pr-4,.px-4{padding-right:1.5rem !important}.pb-4,.py-4{padding-bottom:1.5rem !important}.pl-4,.px-4{padding-left:1.5rem !important}.p-5{padding:3rem !important}.pt-5,.py-5{padding-top:3rem !important}.pr-5,.px-5{padding-right:3rem !important}.pb-5,.py-5{padding-bottom:3rem !important}.pl-5,.px-5{padding-left:3rem !important}.m-n1{margin:-0.25rem !important}.mt-n1,.my-n1{margin-top:-0.25rem !important}.mr-n1,.mx-n1{margin-right:-0.25rem !important}.mb-n1,.my-n1{margin-bottom:-0.25rem !important}.ml-n1,.mx-n1{margin-left:-0.25rem !important}.m-n2{margin:-0.5rem !important}.mt-n2,.my-n2{margin-top:-0.5rem !important}.mr-n2,.mx-n2{margin-right:-0.5rem !important}.mb-n2,.my-n2{margin-bottom:-0.5rem !important}.ml-n2,.mx-n2{margin-left:-0.5rem !important}.m-n3{margin:-1rem !important}.mt-n3,.my-n3{margin-top:-1rem !important}.mr-n3,.mx-n3{margin-right:-1rem !important}.mb-n3,.my-n3{margin-bottom:-1rem !important}.ml-n3,.mx-n3{margin-left:-1rem !important}.m-n4{margin:-1.5rem !important}.mt-n4,.my-n4{margin-top:-1.5rem !important}.mr-n4,.mx-n4{margin-right:-1.5rem !important}.mb-n4,.my-n4{margin-bottom:-1.5rem !important}.ml-n4,.mx-n4{margin-left:-1.5rem !important}.m-n5{margin:-3rem !important}.mt-n5,.my-n5{margin-top:-3rem !important}.mr-n5,.mx-n5{margin-right:-3rem !important}.mb-n5,.my-n5{margin-bottom:-3rem !important}.ml-n5,.mx-n5{margin-left:-3rem !important}.m-auto{margin:auto !important}.mt-auto,.my-auto{margin-top:auto !important}.mr-auto,.mx-auto{margin-right:auto !important}.mb-auto,.my-auto{margin-bottom:auto !important}.ml-auto,.mx-auto{margin-left:auto !important}.border-0{border:0 !important}.border-top-0{border-top:0 !important}.border-bottom-0{border-bottom:0 !important}.border-left-0{border-left:0 !important}.border-right-0{border-right:0 !important}.border{border:1px solid #dbe6e9 !important}.border-top{border-top:1px solid #dbe6e9 !important}.border-bottom{border-bottom:1px solid #dbe6e9 !important}.border-left{border-left:1px solid #dbe6e9 !important}.border-right{border-right:1px solid #dbe6e9 !important}.float-right{float:right !important}.float-left{float:left !important}.float-none{float:none !important}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.align-items-center{align-items:center}.align-items-end{align-items:end}.align-items-baseline{align-items:baseline}.justify-content-center{justify-content:center}.justify-content-between{justify-content:space-between}.justify-content-around{justify-content:space-around}.justify-content-end{justify-content:flex-end}.justify-content-start{justify-content:flex-start}.content-div .control-label,.content-div .form-control-label,.content-div .form-group .control-label,.content-div .form-group .form-control-label{padding-top:0;padding-right:0;padding-left:0;color:#363a41}.content-div .h4,.content-div h4{color:#363a41}.content-div .form-group{margin-bottom:1rem}.content-div .form-group::after{content:"";clear:both;display:table}.content-div .input-group .input-group-addon{color:#6c868e;border-left:1px solid #bbcdd2}.form-group.has-error .form-control{border-color:#c05c67}.form-group.has-error .help-block{color:#c05c67;font-size:12px;margin-top:5px;display:block}.help-box{flex-shrink:0;flex-grow:0}.custom-checkbox{position:relative;display:flex}.custom-checkbox .custom-control-input{margin-right:10px}.custom-checkbox .custom-control-input:before{content:"";position:absolute;left:0;top:0;width:100%;height:100%}.custom-checkbox .custom-control-label{margin:0}.inpostizi-checkboxes-options .custom-control{padding:10px}.card-header{display:flex;align-items:center}.card-header .material-icons{margin-right:10px}.table{font-size:13px}.table td,.table thead th{padding:10px;border:0;color:#363a41}.table thead{border:0;border-bottom:2px solid #25b9d7}.table tbody{border:0}.table tbody tr{border-bottom:1px solid #eceeef}.table .label{font-size:12px}.table .btn-group{display:flex;justify-content:flex-end}.table .btn{padding:5px;display:block}.label{white-space:normal} + +/*# sourceMappingURL=admin-legacy.css.map*/ \ No newline at end of file diff --git a/modules/inpostizi/views/css/admin/admin-legacy.css.map b/modules/inpostizi/views/css/admin/admin-legacy.css.map new file mode 100644 index 00000000..3f8c4844 --- /dev/null +++ b/modules/inpostizi/views/css/admin/admin-legacy.css.map @@ -0,0 +1 @@ +{"version":3,"file":"css/admin/admin-legacy.css","mappings":"AACA,WACE,kBACA,qBACA,mBACA,eACA,kBACA,yBACA,kBACA,kBAGF,iBACE,kBACA,UAGF,gCACE,mBAGF,wCACE,yBAGF,iBACE,kBACA,UACA,WACA,UACA,YACA,sBACA,SACA,sBACA,kBACA,yBACA,cACA,gBACA,eACA,8BACA,eAKA,4CACE,WAGF,sCACE,kBACA,MACA,OACA,UACA,UACA,UACA,YACA,WACA,yBACA,4BAGF,iEACE,yBACA,SAGF,qCACE,kBACA,iBAGF,qCACE,eCxEF,qBACE,aAGF,qBACE,aCNJ,4BACE,aACA,eAEA,kFAEE,aAQJ,KACE,WACA,WACA,eAGF,UACE,cACA,WACA,eAGF,YACE,cACA,eAEA,cACE,eACA,gBC9BA,yBACA,YAEE,wBAEF,YAEE,0BAEF,YAEE,2BAEF,YAEE,yBAfF,8BACA,YAEE,6BAEF,YAEE,+BAEF,YAEE,gCAEF,YAEE,8BAfF,6BACA,YAEE,4BAEF,YAEE,8BAEF,YAEE,+BAEF,YAEE,6BAfF,4BACA,YAEE,2BAEF,YAEE,6BAEF,YAEE,8BAEF,YAEE,4BAfF,8BACA,YAEE,6BAEF,YAEE,+BAEF,YAEE,gCAEF,YAEE,8BAfF,4BACA,YAEE,2BAEF,YAEE,6BAEF,YAEE,8BAEF,YAEE,4BAfF,0BACA,YAEE,yBAEF,YAEE,2BAEF,YAEE,4BAEF,YAEE,0BAfF,+BACA,YAEE,8BAEF,YAEE,gCAEF,YAEE,iCAEF,YAEE,+BAfF,8BACA,YAEE,6BAEF,YAEE,+BAEF,YAEE,gCAEF,YAEE,8BAfF,6BACA,YAEE,4BAEF,YAEE,8BAEF,YAEE,+BAEF,YAEE,6BAfF,+BACA,YAEE,8BAEF,YAEE,gCAEF,YAEE,iCAEF,YAEE,+BAfF,6BACA,YAEE,4BAEF,YAEE,8BAEF,YAEE,+BAEF,YAEE,6BAQF,iCACA,cAEE,+BAEF,cAEE,iCAEF,cAEE,kCAEF,cAEE,gCAfF,gCACA,cAEE,8BAEF,cAEE,gCAEF,cAEE,iCAEF,cAEE,+BAfF,8BACA,cAEE,4BAEF,cAEE,8BAEF,cAEE,+BAEF,cAEE,6BAfF,gCACA,cAEE,8BAEF,cAEE,gCAEF,cAEE,iCAEF,cAEE,+BAfF,8BACA,cAEE,4BAEF,cAEE,8BAEF,cAEE,+BAEF,cAEE,6BAMN,+BACA,kBAEE,2BAEF,kBAEE,6BAEF,kBAEE,8BAEF,kBAEE,4BC5DF,UACE,oBAGF,cACE,wBAGF,iBACE,2BAGF,eACE,yBAGF,gBACE,0BAGF,QACE,oCAGF,YACE,wCAGF,eACE,2CAGF,aACE,yCAGF,cACE,0CCtCF,aACE,uBAGF,YACE,sBAGF,YACE,sBAGF,WACE,gBAGF,YACE,iBAGF,aACE,kBAGF,oBACE,mBAGF,iBACE,gBAGF,sBACE,qBAGF,wBACE,uBAGF,yBACE,8BAGF,wBACE,6BAGF,qBACE,yBAGF,uBACE,2BCnDA,kJAGE,cACA,gBACA,eACA,cAGF,iCACE,cCVF,yBACE,mBAEA,gCACE,WACA,WACA,cAIJ,6CACE,cACA,8BAKF,oCACE,qBAGF,kCACE,cACA,eACA,eACA,cAIJ,UACE,cACA,YAGF,iBACE,kBACA,aAEA,uCACE,kBAEA,8CACE,WACA,kBACA,OACA,MACA,WACA,YAIJ,uCACE,SAIJ,8CACE,aC1DF,aACE,aACA,mBAEA,6BACE,kBCNJ,OACE,eAEA,0BAEE,aACA,SACA,cAGF,aACE,SACA,gCAGF,aACE,SAEA,gBACE,gCAIJ,cACE,eAGF,kBACE,aACA,yBAGF,YACE,YACA,cClCJ,OACE,mB","sources":["webpack://inpost-pay/./src/css/admin-legacy/components/form/_ps-switch.scss","webpack://inpost-pay/./src/css/admin-legacy/override/backwards-compatibility/_display.scss","webpack://inpost-pay/./src/css/admin-legacy/override/backwards-compatibility/_grid.scss","webpack://inpost-pay/./src/css/admin-legacy/override/backwards-compatibility/_spacing.scss","webpack://inpost-pay/./src/css/admin-legacy/override/backwards-compatibility/_border.scss","webpack://inpost-pay/./src/css/admin-legacy/override/backwards-compatibility/_utils.scss","webpack://inpost-pay/./src/css/admin-legacy/override/_typography.scss","webpack://inpost-pay/./src/css/admin-legacy/override/_form.scss","webpack://inpost-pay/./src/css/admin-legacy/override/_card.scss","webpack://inpost-pay/./src/css/admin-legacy/override/_table.scss","webpack://inpost-pay/./src/css/admin-legacy/override/_label.scss"],"sourcesContent":["\n.ps-switch {\n position: relative;\n display: inline-block;\n min-width: 8.125rem;\n margin-top: 3px;\n font-size: .813rem;\n background-color: #eff1f2;\n border-radius: 1px;\n text-align: center\n}\n\n.ps-switch input {\n position: absolute;\n opacity: 0\n}\n\n.ps-switch input:disabled+label {\n cursor: not-allowed\n}\n\n.ps-switch input:disabled:checked+label {\n background-color: #bbcdd2\n}\n\n.ps-switch label {\n position: relative;\n z-index: 2;\n float: left;\n width: 50%;\n height: 100%;\n padding: .231em .769em;\n margin: 0;\n vertical-align: middle;\n text-align: center;\n text-transform: uppercase;\n color: #bbcdd2;\n font-weight: 400;\n cursor: pointer;\n transition: color .2s ease-out;\n font-size: 12px;\n}\n\n.content-div {\n\n .ps-switch input:checked + label {\n color: #fff\n }\n\n .ps-switch > .slide-button {\n position: absolute;\n top: 0;\n left: 0;\n padding: 0;\n z-index: 1;\n width: 50%;\n height: 100%;\n color: #fff;\n background-color: #c05c67;\n transition: all .3s ease-out\n }\n\n .ps-switch input:last-of-type:checked ~ .slide-button {\n background-color: #2eacce;\n left: 50%\n }\n\n .ps-switch.ps-switch-sm {\n min-width: 6.25rem;\n font-size: .75rem\n }\n\n .ps-switch.ps-switch-lg {\n font-size: 1rem\n }\n\n}\n",".content-div {\n .d-none {\n display: none;\n }\n\n .d-flex {\n display: flex;\n }\n}\n",".row-flex {\n display: flex;\n flex-wrap: wrap;\n\n &:after,\n &:before {\n display: none;\n }\n}\n\n.content-div .row {\n @extend .row-flex;\n}\n\n.col {\n flex: 1 1 0;\n width: auto;\n max-width: 100%;\n}\n\n.col-auto {\n flex: 0 0 auto;\n width: auto;\n max-width: none;\n}\n\n.no-gutters {\n margin-left: 0;\n margin-right: 0;\n\n > * {\n padding-left: 0;\n padding-right: 0;\n }\n}\n","@each $prop, $abbrev in (margin: m, padding: p) {\n @each $size, $length in $spacers {\n .#{$abbrev}-#{$size} { #{$prop}: $length !important; }\n .#{$abbrev}t-#{$size},\n .#{$abbrev}y-#{$size} {\n #{$prop}-top: $length !important;\n }\n .#{$abbrev}r-#{$size},\n .#{$abbrev}x-#{$size} {\n #{$prop}-right: $length !important;\n }\n .#{$abbrev}b-#{$size},\n .#{$abbrev}y-#{$size} {\n #{$prop}-bottom: $length !important;\n }\n .#{$abbrev}l-#{$size},\n .#{$abbrev}x-#{$size} {\n #{$prop}-left: $length !important;\n }\n }\n}\n\n// Negative margins (e.g., where `.mb-n1` is negative version of `.mb-1`)\n@each $size, $length in $spacers {\n @if $size != 0 {\n .m-n#{$size} { margin: -$length !important; }\n .mt-n#{$size},\n .my-n#{$size} {\n margin-top: -$length !important;\n }\n .mr-n#{$size},\n .mx-n#{$size} {\n margin-right: -$length !important;\n }\n .mb-n#{$size},\n .my-n#{$size} {\n margin-bottom: -$length !important;\n }\n .ml-n#{$size},\n .mx-n#{$size} {\n margin-left: -$length !important;\n }\n }\n}\n\n// Some special margin utils\n.m-auto { margin: auto !important; }\n.mt-auto,\n.my-auto {\n margin-top: auto !important;\n}\n.mr-auto,\n.mx-auto {\n margin-right: auto !important;\n}\n.mb-auto,\n.my-auto {\n margin-bottom: auto !important;\n}\n.ml-auto,\n.mx-auto {\n margin-left: auto !important;\n}\n","\n.border-0 {\n border: 0 !important;\n}\n\n.border-top-0 {\n border-top: 0 !important;\n}\n\n.border-bottom-0 {\n border-bottom: 0 !important;\n}\n\n.border-left-0 {\n border-left: 0 !important;\n}\n\n.border-right-0 {\n border-right: 0 !important;\n}\n\n.border {\n border: 1px solid $border-color !important;\n}\n\n.border-top {\n border-top: 1px solid $border-color !important;\n}\n\n.border-bottom {\n border-bottom: 1px solid $border-color !important;\n}\n\n.border-left {\n border-left: 1px solid $border-color !important;\n}\n\n.border-right {\n border-right: 1px solid $border-color !important;\n}\n",".float-right {\n float: right !important;\n}\n\n.float-left {\n float: left !important;\n}\n\n.float-none {\n float: none !important;\n}\n\n.text-left {\n text-align: left;\n}\n\n.text-right {\n text-align: right;\n}\n\n.text-center {\n text-align: center;\n}\n\n.align-items-center {\n align-items: center;\n}\n\n.align-items-end {\n align-items: end;\n}\n\n.align-items-baseline {\n align-items: baseline;\n}\n\n.justify-content-center {\n justify-content: center;\n}\n\n.justify-content-between {\n justify-content: space-between;\n}\n\n.justify-content-around {\n justify-content: space-around;\n}\n\n.justify-content-end {\n justify-content: flex-end;\n}\n\n.justify-content-start {\n justify-content: flex-start;\n}\n","\n.content-div {\n .control-label, .form-control-label,\n .form-group .control-label,\n .form-group .form-control-label {\n padding-top: 0;\n padding-right: 0;\n padding-left: 0;\n color: #363a41;\n }\n\n .h4, h4 {\n color: #363a41;\n }\n\n}\n","\n.content-div {\n .form-group {\n margin-bottom: 1rem;\n\n &::after {\n content: \"\";\n clear: both;\n display: table;\n }\n }\n\n .input-group .input-group-addon {\n color: #6c868e;\n border-left: 1px solid #bbcdd2;\n }\n}\n\n.form-group.has-error {\n .form-control {\n border-color: #c05c67;\n }\n\n .help-block {\n color: #c05c67;\n font-size: 12px;\n margin-top: 5px;\n display: block;\n }\n}\n\n.help-box {\n flex-shrink: 0;\n flex-grow: 0;\n}\n\n.custom-checkbox {\n position: relative;\n display: flex;\n\n .custom-control-input {\n margin-right: 10px;\n\n &:before {\n content: '';\n position: absolute;\n left: 0;\n top: 0;\n width: 100%;\n height: 100%;\n }\n }\n\n .custom-control-label {\n margin: 0;\n }\n}\n\n.inpostizi-checkboxes-options .custom-control {\n padding: 10px;\n}\n","\n.card-header {\n display: flex;\n align-items: center;\n\n .material-icons {\n margin-right: 10px;\n }\n}\n",".table {\n font-size: 13px;\n\n td,\n thead th {\n padding: 10px;\n border: 0;\n color: #363a41;\n }\n\n thead {\n border: 0;\n border-bottom: 2px solid #25b9d7;\n }\n\n tbody {\n border: 0;\n\n tr {\n border-bottom: 1px solid #eceeef;\n }\n }\n\n .label {\n font-size: 12px;\n }\n\n .btn-group {\n display: flex;\n justify-content: flex-end;\n }\n\n .btn {\n padding: 5px;\n display: block;\n }\n}\n",".label {\n white-space: normal;\n}\n"],"names":[],"sourceRoot":""} \ No newline at end of file diff --git a/modules/inpostizi/views/css/admin/admin.css b/modules/inpostizi/views/css/admin/admin.css new file mode 100644 index 00000000..1926cdaa --- /dev/null +++ b/modules/inpostizi/views/css/admin/admin.css @@ -0,0 +1,3 @@ +.card-body+.card-body{border-top:1px solid #dbe6e9}.inpostizi-checkboxes .form-group [class*=col-]{padding-left:0;padding-right:0}.col-form-label.w-auto{width:auto}.inpostizi-input--w-sm{max-width:200px}.inpost-izi-switch{width:100%;padding:5px 0}.inpost-izi-switch label{margin:0;font-size:12px}.inpostizi-checkboxes-options{overflow-y:auto;max-height:50vh}.inpostizi-checkboxes-options .custom-control{padding:10px 10px 10px 35px;border-bottom:1px solid #dbe6e9}.inpostizi-checkboxes-options .custom-control-label{display:block} + +/*# sourceMappingURL=admin.css.map*/ \ No newline at end of file diff --git a/modules/inpostizi/views/css/admin/admin.css.map b/modules/inpostizi/views/css/admin/admin.css.map new file mode 100644 index 00000000..c050d78c --- /dev/null +++ b/modules/inpostizi/views/css/admin/admin.css.map @@ -0,0 +1 @@ +{"version":3,"file":"css/admin/admin.css","mappings":"AACA,sBACE,6BCCE,gDACE,eACA,gBAKN,uBACE,WCTA,uBACE,gBCFJ,mBACE,WACA,cAEA,yBACE,SACA,eCNJ,8BACE,gBACA,gBAEA,8CACE,4BAEA,gCAGF,oDACE,c","sources":["webpack://inpost-pay/./src/css/admin/override/_bootstrap.scss","webpack://inpost-pay/./src/css/admin/components/form/_misc.scss","webpack://inpost-pay/./src/css/admin/components/form/_input.scss","webpack://inpost-pay/./src/css/admin/components/form/_switch.scss","webpack://inpost-pay/./src/css/admin/components/form/_inpostizi-checkboxes-options.scss"],"sourcesContent":["\n.card-body + .card-body {\n border-top: 1px solid $border-color;\n}\n","\n.inpostizi-checkboxes {\n .form-group {\n [class*=\"col-\"] {\n padding-left: 0;\n padding-right: 0;\n }\n }\n}\n\n.col-form-label.w-auto {\n width: auto;\n}\n","\n.inpostizi-input {\n &--w-sm {\n max-width: 200px;\n }\n}\n","\n.inpost-izi-switch {\n width: 100%;\n padding: 5px 0;\n\n label {\n margin: 0;\n font-size: 12px;\n }\n}\n","\n.inpostizi-checkboxes-options {\n overflow-y: auto;\n max-height: 50vh;\n\n .custom-control {\n padding: 10px 10px 10px 35px;\n\n border-bottom: 1px solid $border-color;\n }\n\n .custom-control-label {\n display: block;\n }\n}\n"],"names":[],"sourceRoot":""} \ No newline at end of file diff --git a/modules/inpostizi/views/css/admin/consents.css b/modules/inpostizi/views/css/admin/consents.css new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/modules/inpostizi/views/css/admin/consents.css @@ -0,0 +1 @@ + diff --git a/modules/inpostizi/views/css/admin/gui.css b/modules/inpostizi/views/css/admin/gui.css new file mode 100644 index 00000000..0338c1b1 --- /dev/null +++ b/modules/inpostizi/views/css/admin/gui.css @@ -0,0 +1,3 @@ +.inpostizi-btn-preview__content{padding:20px 30px}.inpostizi-btn-preview__content--light{border:1px solid #dbe6e9;background:#f8f8f8}.inpostizi-btn-preview__content--dark{border:1px solid #606060;background:#6e6e6e}.inpostizi-material-choice-tree-container{padding:.625rem;border:1px solid #999}.inpostizi-material-choice-tree-container .choice-tree-actions{margin-bottom:10px;border-bottom:.063rem solid #999}.inpostizi-material-choice-tree-container ul.choice-tree{padding:0;margin:0}.inpostizi-material-choice-tree-container ul.choice-tree li{list-style:none;background:no-repeat 0 3px;background-size:12px 12px}.inpostizi-material-choice-tree-container ul.choice-tree li .checkbox,.inpostizi-material-choice-tree-container ul.choice-tree li .radio{padding-top:0;padding-left:20px}.inpostizi-material-choice-tree-container ul.choice-tree li .checkbox i.md-checkbox-control::before{width:16px;height:16px;margin-top:3px}.inpostizi-material-choice-tree-container ul.choice-tree li .checkbox input[type=checkbox]:checked+i.md-checkbox-control::after,.inpostizi-material-choice-tree-container ul.choice-tree li .checkbox input[type=checkbox]:indeterminate+i.md-checkbox-control::after{top:5px;left:3px;width:11px;height:7px}.inpostizi-material-choice-tree-container ul.choice-tree li ul{padding-left:1rem}.inpostizi-material-choice-tree-container ul.choice-tree .collapsed,.inpostizi-material-choice-tree-container ul.choice-tree .expanded{position:relative;background:none}.inpostizi-material-choice-tree-container ul.choice-tree .collapsed>.checkbox,.inpostizi-material-choice-tree-container ul.choice-tree .collapsed>.radio,.inpostizi-material-choice-tree-container ul.choice-tree .expanded>.checkbox,.inpostizi-material-choice-tree-container ul.choice-tree .expanded>.radio{cursor:pointer}.inpostizi-material-choice-tree-container ul.choice-tree .collapsed>.checkbox::before,.inpostizi-material-choice-tree-container ul.choice-tree .collapsed>.radio::before,.inpostizi-material-choice-tree-container ul.choice-tree .expanded>.checkbox::before,.inpostizi-material-choice-tree-container ul.choice-tree .expanded>.radio::before{position:absolute;top:-0.25rem;left:-0.1rem;font-family:"Material Icons",Arial,Helvetica,sans-serif;font-size:1.25rem;color:#6c868e;cursor:pointer}.inpostizi-material-choice-tree-container ul.choice-tree .expanded>.checkbox::before,.inpostizi-material-choice-tree-container ul.choice-tree .expanded>.radio::before{content:""}.inpostizi-material-choice-tree-container ul.choice-tree .collapsed>ul{display:none}.inpostizi-material-choice-tree-container ul.choice-tree .collapsed>.checkbox::before,.inpostizi-material-choice-tree-container ul.choice-tree .collapsed>.radio::before{content:""} + +/*# sourceMappingURL=gui.css.map*/ \ No newline at end of file diff --git a/modules/inpostizi/views/css/admin/gui.css.map b/modules/inpostizi/views/css/admin/gui.css.map new file mode 100644 index 00000000..763eee8a --- /dev/null +++ b/modules/inpostizi/views/css/admin/gui.css.map @@ -0,0 +1 @@ +{"version":3,"file":"css/admin/gui.css","mappings":"AAAA,gCCGE,iBACE,wCAEA,wBACE,mBCLS,uCDSX,wBACE,mBCTS,2CCHf,eACE,sBACA,gEAEA,kBACE,iCACA,0DAGF,SACE,SACA,6DAEA,eACE,2BACA,0BACA,0IAEA,aAEE,kBACA,qGAME,UACE,YACA,eACA,uQAQE,OAEE,SACA,WACA,WACA,gEAOV,iBACE,wIAIJ,iBAEE,gBACA,iTAEA,cAEE,iVAEA,iBACE,aACA,aACA,wDACA,kBACA,cD/DK,eCiEL,wKAQF,WACE,wEAOJ,YACE,0KAKA,WACE,C","sources":["webpack://inpost-pay/./src/css/gui.scss","webpack://inpost-pay/./src/css/gui/components/_inpostizi-btn-preview.scss","webpack://inpost-pay/./src/css/abstract/variables/_colors.scss","webpack://inpost-pay/./src/css/gui/components/form/_material_choice_tree.scss"],"sourcesContent":[".inpostizi-btn-preview__content{padding:20px 30px}.inpostizi-btn-preview__content--light{border:1px solid #dbe6e9;background:#f8f8f8}.inpostizi-btn-preview__content--dark{border:1px solid #606060;background:#6e6e6e}.inpostizi-material-choice-tree-container{padding:.625rem;border:1px solid #999}.inpostizi-material-choice-tree-container .choice-tree-actions{margin-bottom:10px;border-bottom:.063rem solid #999}.inpostizi-material-choice-tree-container ul.choice-tree{padding:0;margin:0}.inpostizi-material-choice-tree-container ul.choice-tree li{list-style:none;background:no-repeat 0 3px;background-size:12px 12px}.inpostizi-material-choice-tree-container ul.choice-tree li .checkbox,.inpostizi-material-choice-tree-container ul.choice-tree li .radio{padding-top:0;padding-left:20px}.inpostizi-material-choice-tree-container ul.choice-tree li .checkbox i.md-checkbox-control::before{width:16px;height:16px;margin-top:3px}.inpostizi-material-choice-tree-container ul.choice-tree li .checkbox input[type=checkbox]:checked+i.md-checkbox-control::after,.inpostizi-material-choice-tree-container ul.choice-tree li .checkbox input[type=checkbox]:indeterminate+i.md-checkbox-control::after{top:5px;left:3px;width:11px;height:7px}.inpostizi-material-choice-tree-container ul.choice-tree li ul{padding-left:1rem}.inpostizi-material-choice-tree-container ul.choice-tree .collapsed,.inpostizi-material-choice-tree-container ul.choice-tree .expanded{position:relative;background:none}.inpostizi-material-choice-tree-container ul.choice-tree .collapsed>.checkbox,.inpostizi-material-choice-tree-container ul.choice-tree .collapsed>.radio,.inpostizi-material-choice-tree-container ul.choice-tree .expanded>.checkbox,.inpostizi-material-choice-tree-container ul.choice-tree .expanded>.radio{cursor:pointer}.inpostizi-material-choice-tree-container ul.choice-tree .collapsed>.checkbox::before,.inpostizi-material-choice-tree-container ul.choice-tree .collapsed>.radio::before,.inpostizi-material-choice-tree-container ul.choice-tree .expanded>.checkbox::before,.inpostizi-material-choice-tree-container ul.choice-tree .expanded>.radio::before{position:absolute;top:-0.25rem;left:-0.1rem;font-family:\"Material Icons\",Arial,Helvetica,sans-serif;font-size:1.25rem;color:#6c868e;cursor:pointer}.inpostizi-material-choice-tree-container ul.choice-tree .expanded>.checkbox::before,.inpostizi-material-choice-tree-container ul.choice-tree .expanded>.radio::before{content:\"\"}.inpostizi-material-choice-tree-container ul.choice-tree .collapsed>ul{display:none}.inpostizi-material-choice-tree-container ul.choice-tree .collapsed>.checkbox::before,.inpostizi-material-choice-tree-container ul.choice-tree .collapsed>.radio::before{content:\"\"}","\n.inpostizi-btn-preview {\n\n &__content {\n padding: 20px 30px;\n\n &--light {\n border: 1px solid $border-color;\n background: $gray-200;\n }\n\n &--dark {\n border: 1px solid $gray-600;\n background: $gray-500;\n }\n }\n}\n","\n$border-color: #dbe6e9;\n$gray-200: #F8F8F8;\n$gray-500: #6e6e6e;\n$gray-600: #606060;\n$gray-light: lighten(#000, 60%);\n$gray-medium: #6c868e;\n",".inpostizi-material-choice-tree-container {\n padding: 0.625rem;\n border: 1px solid $gray-light;\n\n .choice-tree-actions {\n margin-bottom: 10px;\n border-bottom: 0.063rem solid $gray-light;\n }\n\n ul.choice-tree {\n padding: 0;\n margin: 0;\n\n li {\n list-style: none;\n background: no-repeat 0 3px;\n background-size: 12px 12px;\n\n .checkbox,\n .radio {\n padding-top: 0;\n padding-left: 20px;\n }\n\n .checkbox {\n i.md-checkbox-control {\n // make material checkbox smaller\n &::before {\n width: 16px;\n height: 16px;\n margin-top: 3px;\n }\n }\n\n input[type=\"checkbox\"] {\n &:checked,\n &:indeterminate {\n + i.md-checkbox-control {\n &::after {\n // make material checkmark smaller\n top: 5px;\n left: 3px;\n width: 11px;\n height: 7px;\n }\n }\n }\n }\n }\n\n ul {\n padding-left: 1rem;\n }\n }\n\n .collapsed,\n .expanded {\n position: relative;\n background: none;\n\n > .checkbox,\n > .radio {\n cursor: pointer;\n\n &::before {\n position: absolute;\n top: -0.25rem;\n left: -0.1rem;\n font-family: \"Material Icons\", Arial, Helvetica, sans-serif;\n font-size: 1.25rem;\n color: $gray-medium;\n cursor: pointer;\n }\n }\n }\n\n .expanded {\n > .checkbox,\n > .radio {\n &::before {\n content: \"\\E313\";\n }\n }\n }\n\n .collapsed {\n // hide inner tree when item is collapsed\n > ul {\n display: none;\n }\n\n > .checkbox,\n > .radio {\n &::before {\n content: \"\\E5CC\";\n }\n }\n }\n }\n}\n"],"names":[],"sourceRoot":""} \ No newline at end of file diff --git a/modules/inpostizi/views/css/admin/hot-products.css b/modules/inpostizi/views/css/admin/hot-products.css new file mode 100644 index 00000000..dce31567 --- /dev/null +++ b/modules/inpostizi/views/css/admin/hot-products.css @@ -0,0 +1,3 @@ +.ts-control{border:1px solid #d0d0d0;padding:8px 8px;width:100%;overflow:hidden;position:relative;z-index:1;box-sizing:border-box;box-shadow:none;border-radius:3px;display:flex;flex-wrap:wrap}.ts-wrapper.multi.has-items .ts-control{padding:calc(8px - 2px - 0) 8px calc(8px - 2px - 3px - 0)}.full .ts-control{background-color:#fff}.disabled .ts-control,.disabled .ts-control *{cursor:default !important}.focus .ts-control{box-shadow:none}.ts-control>*{vertical-align:baseline;display:inline-block}.ts-wrapper.multi .ts-control>div{cursor:pointer;margin:0 3px 3px 0;padding:2px 6px;background:#f2f2f2;color:#303030;border:0 solid #d0d0d0}.ts-wrapper.multi .ts-control>div.active{background:#e8e8e8;color:#303030;border:0 solid #cacaca}.ts-wrapper.multi.disabled .ts-control>div,.ts-wrapper.multi.disabled .ts-control>div.active{color:#7d7d7d;background:#fff;border:0 solid #fff}.ts-control>input{flex:1 1 auto;min-width:7rem;display:inline-block !important;padding:0 !important;min-height:0 !important;max-height:none !important;max-width:100% !important;margin:0 !important;text-indent:0 !important;border:0 none !important;background:none !important;line-height:inherit !important;-webkit-user-select:auto !important;-moz-user-select:auto !important;user-select:auto !important;box-shadow:none !important}.ts-control>input::-ms-clear{display:none}.ts-control>input:focus{outline:none !important}.has-items .ts-control>input{margin:0 4px !important}.ts-control.rtl{text-align:right}.ts-control.rtl.single .ts-control:after{left:15px;right:auto}.ts-control.rtl .ts-control>input{margin:0 4px 0 -2px !important}.disabled .ts-control{opacity:.5;background-color:#fafafa}.input-hidden .ts-control>input{opacity:0;position:absolute;left:-10000px}.ts-dropdown{position:absolute;top:100%;left:0;width:100%;z-index:10;border:1px solid #d0d0d0;background:#fff;margin:.25rem 0 0;border-top:0 none;box-sizing:border-box;box-shadow:0 1px 3px rgba(0,0,0,.1);border-radius:0 0 3px 3px}.ts-dropdown [data-selectable]{cursor:pointer;overflow:hidden}.ts-dropdown [data-selectable] .highlight{background:rgba(125,168,208,.2);border-radius:1px}.ts-dropdown .option,.ts-dropdown .optgroup-header,.ts-dropdown .no-results,.ts-dropdown .create{padding:5px 8px}.ts-dropdown .option,.ts-dropdown [data-disabled],.ts-dropdown [data-disabled] [data-selectable].option{cursor:inherit;opacity:.5}.ts-dropdown [data-selectable].option{opacity:1;cursor:pointer}.ts-dropdown .optgroup:first-child .optgroup-header{border-top:0 none}.ts-dropdown .optgroup-header{color:#303030;background:#fff;cursor:default}.ts-dropdown .active{background-color:#f5fafd;color:#495c68}.ts-dropdown .active.create{color:#495c68}.ts-dropdown .create{color:rgba(48,48,48,.5)}.ts-dropdown .spinner{display:inline-block;width:30px;height:30px;margin:5px 8px}.ts-dropdown .spinner::after{content:" ";display:block;width:24px;height:24px;margin:3px;border-radius:50%;border:5px solid #d0d0d0;border-color:#d0d0d0 rgba(0,0,0,0) #d0d0d0 rgba(0,0,0,0);animation:lds-dual-ring 1.2s linear infinite}@keyframes lds-dual-ring{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.ts-dropdown-content{overflow:hidden auto;max-height:200px;scroll-behavior:smooth}.ts-wrapper.plugin-drag_drop .ts-dragging{color:rgba(0,0,0,0) !important}.ts-wrapper.plugin-drag_drop .ts-dragging>*{visibility:hidden !important}.plugin-checkbox_options:not(.rtl) .option input{margin-right:.5rem}.plugin-checkbox_options.rtl .option input{margin-left:.5rem}.plugin-clear_button{--ts-pr-clear-button: 1em}.plugin-clear_button .clear-button{opacity:0;position:absolute;top:50%;transform:translateY(-50%);right:calc(8px - 6px);margin-right:0 !important;background:rgba(0,0,0,0) !important;transition:opacity .5s;cursor:pointer}.plugin-clear_button.form-select .clear-button,.plugin-clear_button.single .clear-button{right:max(var(--ts-pr-caret),8px)}.plugin-clear_button.focus.has-items .clear-button,.plugin-clear_button:not(.disabled):hover.has-items .clear-button{opacity:1}.ts-wrapper .dropdown-header{position:relative;padding:10px 8px;border-bottom:1px solid #d0d0d0;background:color-mix(#fff, #d0d0d0, 85%);border-radius:3px 3px 0 0}.ts-wrapper .dropdown-header-close{position:absolute;right:8px;top:50%;color:#303030;opacity:.4;margin-top:-12px;line-height:20px;font-size:20px !important}.ts-wrapper .dropdown-header-close:hover{color:#000}.plugin-dropdown_input.focus.dropdown-active .ts-control{box-shadow:none;border:1px solid #d0d0d0}.plugin-dropdown_input .dropdown-input{border:1px solid #d0d0d0;border-width:0 0 1px;display:block;padding:8px 8px;box-shadow:none;width:100%;background:rgba(0,0,0,0)}.plugin-dropdown_input .items-placeholder{border:0 none !important;box-shadow:none !important;width:100%}.plugin-dropdown_input.has-items .items-placeholder,.plugin-dropdown_input.dropdown-active .items-placeholder{display:none !important}.ts-wrapper.plugin-input_autogrow.has-items .ts-control>input{min-width:0}.ts-wrapper.plugin-input_autogrow.has-items.focus .ts-control>input{flex:none;min-width:4px}.ts-wrapper.plugin-input_autogrow.has-items.focus .ts-control>input::-moz-placeholder{color:rgba(0,0,0,0)}.ts-wrapper.plugin-input_autogrow.has-items.focus .ts-control>input::placeholder{color:rgba(0,0,0,0)}.ts-dropdown.plugin-optgroup_columns .ts-dropdown-content{display:flex}.ts-dropdown.plugin-optgroup_columns .optgroup{border-right:1px solid #f2f2f2;border-top:0 none;flex-grow:1;flex-basis:0;min-width:0}.ts-dropdown.plugin-optgroup_columns .optgroup:last-child{border-right:0 none}.ts-dropdown.plugin-optgroup_columns .optgroup::before{display:none}.ts-dropdown.plugin-optgroup_columns .optgroup-header{border-top:0 none}.ts-wrapper.plugin-remove_button .item{display:inline-flex;align-items:center}.ts-wrapper.plugin-remove_button .item .remove{color:inherit;text-decoration:none;vertical-align:middle;display:inline-block;padding:0 6px;border-radius:0 2px 2px 0;box-sizing:border-box}.ts-wrapper.plugin-remove_button .item .remove:hover{background:rgba(0,0,0,.05)}.ts-wrapper.plugin-remove_button.disabled .item .remove:hover{background:none}.ts-wrapper.plugin-remove_button .remove-single{position:absolute;right:0;top:0;font-size:23px}.ts-wrapper.plugin-remove_button:not(.rtl) .item{padding-right:0 !important}.ts-wrapper.plugin-remove_button:not(.rtl) .item .remove{border-left:1px solid #d0d0d0;margin-left:6px}.ts-wrapper.plugin-remove_button:not(.rtl) .item.active .remove{border-left-color:#cacaca}.ts-wrapper.plugin-remove_button:not(.rtl).disabled .item .remove{border-left-color:#fff}.ts-wrapper.plugin-remove_button.rtl .item{padding-left:0 !important}.ts-wrapper.plugin-remove_button.rtl .item .remove{border-right:1px solid #d0d0d0;margin-right:6px}.ts-wrapper.plugin-remove_button.rtl .item.active .remove{border-right-color:#cacaca}.ts-wrapper.plugin-remove_button.rtl.disabled .item .remove{border-right-color:#fff}:root{--ts-pr-clear-button: 0px;--ts-pr-caret: 0px;--ts-pr-min: .75rem}.ts-wrapper.single .ts-control,.ts-wrapper.single .ts-control input{cursor:pointer}.ts-control:not(.rtl){padding-right:max(var(--ts-pr-min),var(--ts-pr-clear-button) + var(--ts-pr-caret)) !important}.ts-control.rtl{padding-left:max(var(--ts-pr-min),var(--ts-pr-clear-button) + var(--ts-pr-caret)) !important}.ts-wrapper{position:relative}.ts-dropdown,.ts-control,.ts-control input{color:#303030;font-family:inherit;font-size:13px;line-height:18px}.ts-control,.ts-wrapper.single.input-active .ts-control{background:#fff;cursor:text}.ts-hidden-accessible{border:0 !important;clip:rect(0 0 0 0) !important;-webkit-clip-path:inset(50%) !important;clip-path:inset(50%) !important;overflow:hidden !important;padding:0 !important;position:absolute !important;width:1px !important;white-space:nowrap !important} + +/*# sourceMappingURL=hot-products.css.map*/ \ No newline at end of file diff --git a/modules/inpostizi/views/css/admin/support.css b/modules/inpostizi/views/css/admin/support.css new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/modules/inpostizi/views/css/admin/support.css @@ -0,0 +1 @@ + diff --git a/modules/inpostizi/views/css/front/product.4eeb6b5c5ec8bd44d908.css b/modules/inpostizi/views/css/front/product.4eeb6b5c5ec8bd44d908.css new file mode 100644 index 00000000..1843786d --- /dev/null +++ b/modules/inpostizi/views/css/front/product.4eeb6b5c5ec8bd44d908.css @@ -0,0 +1 @@ +.product-add-to-cart .product-quantity{flex-wrap:wrap}.product-add-to-cart .product-quantity .inpost-izi-btn-wrapper{width:100%} \ No newline at end of file diff --git a/modules/inpostizi/views/css/product.6f69cd7d93f20866f321.css.map b/modules/inpostizi/views/css/product.6f69cd7d93f20866f321.css.map new file mode 100644 index 00000000..7949821c --- /dev/null +++ b/modules/inpostizi/views/css/product.6f69cd7d93f20866f321.css.map @@ -0,0 +1 @@ +{"version":3,"file":"css/product.6f69cd7d93f20866f321.css","mappings":"AACE,uCACE,eAEA,+DACE,W","sources":["webpack://inpost-pay/./src/css/product.scss"],"sourcesContent":[".product-add-to-cart {\n .product-quantity {\n flex-wrap: wrap;\n\n .inpost-izi-btn-wrapper {\n width: 100%;\n }\n }\n}\n"],"names":[],"sourceRoot":""} \ No newline at end of file diff --git a/modules/inpostizi/views/entrypoints.json b/modules/inpostizi/views/entrypoints.json new file mode 100644 index 00000000..102b32a1 --- /dev/null +++ b/modules/inpostizi/views/entrypoints.json @@ -0,0 +1,14 @@ +{ + "entrypoints": { + "v2": { + "js": [ + "../../views/js/front/v2.6af1a84763a244f25b41.js" + ] + }, + "product": { + "css": [ + "../../views/css/front/product.4eeb6b5c5ec8bd44d908.css" + ] + } + } +} \ No newline at end of file diff --git a/modules/inpostizi/views/img/admin/banner_admin.png b/modules/inpostizi/views/img/admin/banner_admin.png new file mode 100644 index 00000000..242f512b Binary files /dev/null and b/modules/inpostizi/views/img/admin/banner_admin.png differ diff --git a/modules/inpostizi/views/js/admin/admin-legacy.js b/modules/inpostizi/views/js/admin/admin-legacy.js new file mode 100644 index 00000000..919ea341 --- /dev/null +++ b/modules/inpostizi/views/js/admin/admin-legacy.js @@ -0,0 +1 @@ +(()=>{(()=>{var _={};(()=>{"use strict"})()})();})(); diff --git a/modules/inpostizi/views/js/admin/admin.js b/modules/inpostizi/views/js/admin/admin.js new file mode 100644 index 00000000..ece2ac99 --- /dev/null +++ b/modules/inpostizi/views/js/admin/admin.js @@ -0,0 +1,49 @@ +(()=>{(()=>{"use strict";var E={104:u=>{var l=typeof Reflect=="object"?Reflect:null,h=l&&typeof l.apply=="function"?l.apply:function(e,t,r){return Function.prototype.apply.call(e,t,r)},L;l&&typeof l.ownKeys=="function"?L=l.ownKeys:Object.getOwnPropertySymbols?L=function(e){return Object.getOwnPropertyNames(e).concat(Object.getOwnPropertySymbols(e))}:L=function(e){return Object.getOwnPropertyNames(e)};function m(n){console&&console.warn&&console.warn(n)}var y=Number.isNaN||function(e){return e!==e};function i(){i.init.call(this)}u.exports=i,i.EventEmitter=i,i.prototype._events=void 0,i.prototype._eventsCount=0,i.prototype._maxListeners=void 0;var _=10;function f(n){if(typeof n!="function")throw new TypeError('The "listener" argument must be of type Function. Received type '+typeof n)}Object.defineProperty(i,"defaultMaxListeners",{enumerable:!0,get:function(){return _},set:function(n){if(typeof n!="number"||n<0||y(n))throw new RangeError('The value of "defaultMaxListeners" is out of range. It must be a non-negative number. Received '+n+".");_=n}}),i.init=function(){(this._events===void 0||this._events===Object.getPrototypeOf(this)._events)&&(this._events=Object.create(null),this._eventsCount=0),this._maxListeners=this._maxListeners||void 0},i.prototype.setMaxListeners=function(e){if(typeof e!="number"||e<0||y(e))throw new RangeError('The value of "n" is out of range. It must be a non-negative number. Received '+e+".");return this._maxListeners=e,this};function c(n){return n._maxListeners===void 0?i.defaultMaxListeners:n._maxListeners}i.prototype.getMaxListeners=function(){return c(this)},i.prototype.emit=function(e){for(var t=[],r=1;r0&&(o=t[0]),o instanceof Error)throw o;var p=new Error("Unhandled error."+(o?" ("+o.message+")":""));throw p.context=o,p}var b=a[e];if(b===void 0)return!1;if(typeof b=="function")h(b,this,t);else for(var j=b.length,M=I(b,j),r=0;r0&&o.length>s&&!o.warned){o.warned=!0;var p=new Error("Possible EventEmitter memory leak detected. "+o.length+" "+String(e)+" listeners added. Use emitter.setMaxListeners() to increase limit");p.name="MaxListenersExceededWarning",p.emitter=n,p.type=e,p.count=o.length,m(p)}return n}i.prototype.addListener=function(e,t){return d(this,e,t,!1)},i.prototype.on=i.prototype.addListener,i.prototype.prependListener=function(e,t){return d(this,e,t,!0)};function g(){if(!this.fired)return this.target.removeListener(this.type,this.wrapFn),this.fired=!0,arguments.length===0?this.listener.call(this.target):this.listener.apply(this.target,arguments)}function w(n,e,t){var r={fired:!1,wrapFn:void 0,target:n,type:e,listener:t},s=g.bind(r);return s.listener=t,r.wrapFn=s,s}i.prototype.once=function(e,t){return f(t),this.on(e,w(this,e,t)),this},i.prototype.prependOnceListener=function(e,t){return f(t),this.prependListener(e,w(this,e,t)),this},i.prototype.removeListener=function(e,t){var r,s,a,o,p;if(f(t),s=this._events,s===void 0)return this;if(r=s[e],r===void 0)return this;if(r===t||r.listener===t)--this._eventsCount===0?this._events=Object.create(null):(delete s[e],s.removeListener&&this.emit("removeListener",e,r.listener||t));else if(typeof r!="function"){for(a=-1,o=r.length-1;o>=0;o--)if(r[o]===t||r[o].listener===t){p=r[o].listener,a=o;break}if(a<0)return this;a===0?r.shift():C(r,a),r.length===1&&(s[e]=r[0]),s.removeListener!==void 0&&this.emit("removeListener",e,p||t)}return this},i.prototype.off=i.prototype.removeListener,i.prototype.removeAllListeners=function(e){var t,r,s;if(r=this._events,r===void 0)return this;if(r.removeListener===void 0)return arguments.length===0?(this._events=Object.create(null),this._eventsCount=0):r[e]!==void 0&&(--this._eventsCount===0?this._events=Object.create(null):delete r[e]),this;if(arguments.length===0){var a=Object.keys(r),o;for(s=0;s=0;s--)this.removeListener(e,t[s]);return this};function O(n,e,t){var r=n._events;if(r===void 0)return[];var s=r[e];return s===void 0?[]:typeof s=="function"?t?[s.listener||s]:[s]:t?k(s):I(s,s.length)}i.prototype.listeners=function(e){return O(this,e,!0)},i.prototype.rawListeners=function(e){return O(this,e,!1)},i.listenerCount=function(n,e){return typeof n.listenerCount=="function"?n.listenerCount(e):S.call(n,e)},i.prototype.listenerCount=S;function S(n){var e=this._events;if(e!==void 0){var t=e[n];if(typeof t=="function")return 1;if(t!==void 0)return t.length}return 0}i.prototype.eventNames=function(){return this._eventsCount>0?L(this._events):[]};function I(n,e){for(var t=new Array(e),r=0;r{var l=u&&u.__esModule?()=>u.default:()=>u;return v.d(l,{a:l}),l},v.d=(u,l)=>{for(var h in l)v.o(l,h)&&!v.o(u,h)&&Object.defineProperty(u,h,{enumerable:!0,get:l[h]})},v.o=(u,l)=>Object.prototype.hasOwnProperty.call(u,l);var N={};(()=>{var u=v(104),l=v.n(u);/** + * Copyright since 2007 PrestaShop SA and Contributors + * PrestaShop is an International Registered Trademark & Property of PrestaShop SA + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.md. + * It is also available through the world-wide-web at this URL: + * https://opensource.org/licenses/OSL-3.0 + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@prestashop.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade PrestaShop to newer + * versions in the future. If you wish to customize PrestaShop for your + * needs please refer to https://devdocs.prestashop.com/ for more information. + * + * @author PrestaShop SA and Contributors + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */const h=new(l()),L=null;/** + * Copyright since 2007 PrestaShop SA and Contributors + * PrestaShop is an International Registered Trademark & Property of PrestaShop SA + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.md. + * It is also available through the world-wide-web at this URL: + * https://opensource.org/licenses/OSL-3.0 + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@prestashop.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade PrestaShop to newer + * versions in the future. If you wish to customize PrestaShop for your + * needs please refer to https://devdocs.prestashop.com/ for more information. + * + * @author PrestaShop SA and Contributors + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */const{$:m}=window;class y{constructor(f){const c=f||{};return this.localeItemSelector=c.localeItemSelector||".js-locale-item",this.localeButtonSelector=c.localeButtonSelector||".js-locale-btn",this.localeInputSelector=c.localeInputSelector||".js-locale-input",this.selectedLocale=m(this.localeItemSelector).data("locale"),m("body").on("click",this.localeItemSelector,this.toggleLanguage.bind(this)),h.on("languageSelected",this.toggleInputs.bind(this)),{localeItemSelector:this.localeItemSelector,localeButtonSelector:this.localeButtonSelector,localeInputSelector:this.localeInputSelector,refreshFormInputs:d=>{this.refreshInputs(d)},getSelectedLocale:()=>this.selectedLocale}}refreshInputs(f){this.selectedLocale&&h.emit("languageSelected",{selectedLocale:this.selectedLocale,form:f})}toggleLanguage(f){const c=m(f.target),d=c.closest("form");this.selectedLocale=c.data("locale"),this.refreshInputs(d)}toggleInputs(f){const{form:c}=f;this.selectedLocale=f.selectedLocale;const d=c.find(this.localeButtonSelector),g=d.data("change-language-url");d.text(this.selectedLocale),c.find(this.localeInputSelector).addClass("d-none"),c.find(`${this.localeInputSelector}.js-locale-${this.selectedLocale}`).removeClass("d-none"),g&&this.saveSelectedLanguage(g,this.selectedLocale)}saveSelectedLanguage(f,c){m.post({url:f,data:{language_iso_code:c}})}}const i=y;$(document).ready(()=>{new i;const _=c=>{const d=$(c.currentTarget),g=d.data("target");$(`input[name="${g}"]`).prop("checked",d.prop("checked"))},f=c=>{if(!c.clickEvent)return!0;if($(c.clickEvent.target).closest(".dropdown-menu").length)return!1};$(".js-inpostizi-accept-all-options-checkbox").on("change",_),$('[data-prevent-close-on-inside-click="true"]').on("hide.bs.dropdown",f)})})()})();})(); + +//# sourceMappingURL=admin.js.map \ No newline at end of file diff --git a/modules/inpostizi/views/js/admin/cart-rules.js b/modules/inpostizi/views/js/admin/cart-rules.js new file mode 100644 index 00000000..c56324eb --- /dev/null +++ b/modules/inpostizi/views/js/admin/cart-rules.js @@ -0,0 +1,3 @@ +(()=>{(()=>{"use strict";var l={};(e=>{document.readyState==="loading"?document.addEventListener("DOMContentLoaded",e):e()})(()=>{const e=document.getElementById("inpostizi_form_tab"),t=document.getElementById("cart_rule_form");e!==null&&t!==null&&t.appendChild(e.content.cloneNode(!0));const n=document.getElementById("inpostizi_nav_link"),o=document.getElementById("cart_rule_link_actions").closest("ul");n!==null&&o!==null&&o.appendChild(n.content.cloneNode(!0))})})();})(); + +//# sourceMappingURL=cart-rules.js.map \ No newline at end of file diff --git a/modules/inpostizi/views/js/admin/consents.js b/modules/inpostizi/views/js/admin/consents.js new file mode 100644 index 00000000..8b48d600 --- /dev/null +++ b/modules/inpostizi/views/js/admin/consents.js @@ -0,0 +1,49 @@ +(()=>{(()=>{"use strict";var V={104:p=>{var f=typeof Reflect=="object"?Reflect:null,y=f&&typeof f.apply=="function"?f.apply:function(e,n,i){return Function.prototype.apply.call(e,n,i)},b;f&&typeof f.ownKeys=="function"?b=f.ownKeys:Object.getOwnPropertySymbols?b=function(e){return Object.getOwnPropertyNames(e).concat(Object.getOwnPropertySymbols(e))}:b=function(e){return Object.getOwnPropertyNames(e)};function u(s){console&&console.warn&&console.warn(s)}var _=Number.isNaN||function(e){return e!==e};function c(){c.init.call(this)}p.exports=c,c.EventEmitter=c,c.prototype._events=void 0,c.prototype._eventsCount=0,c.prototype._maxListeners=void 0;var I=10;function d(s){if(typeof s!="function")throw new TypeError('The "listener" argument must be of type Function. Received type '+typeof s)}Object.defineProperty(c,"defaultMaxListeners",{enumerable:!0,get:function(){return I},set:function(s){if(typeof s!="number"||s<0||_(s))throw new RangeError('The value of "defaultMaxListeners" is out of range. It must be a non-negative number. Received '+s+".");I=s}}),c.init=function(){(this._events===void 0||this._events===Object.getPrototypeOf(this)._events)&&(this._events=Object.create(null),this._eventsCount=0),this._maxListeners=this._maxListeners||void 0},c.prototype.setMaxListeners=function(e){if(typeof e!="number"||e<0||_(e))throw new RangeError('The value of "n" is out of range. It must be a non-negative number. Received '+e+".");return this._maxListeners=e,this};function g(s){return s._maxListeners===void 0?c.defaultMaxListeners:s._maxListeners}c.prototype.getMaxListeners=function(){return g(this)},c.prototype.emit=function(e){for(var n=[],i=1;i0&&(l=n[0]),l instanceof Error)throw l;var v=new Error("Unhandled error."+(l?" ("+l.message+")":""));throw v.context=l,v}var C=h[e];if(C===void 0)return!1;if(typeof C=="function")y(C,this,n);else for(var x=C.length,k=W(C,x),i=0;i0&&l.length>a&&!l.warned){l.warned=!0;var v=new Error("Possible EventEmitter memory leak detected. "+l.length+" "+String(e)+" listeners added. Use emitter.setMaxListeners() to increase limit");v.name="MaxListenersExceededWarning",v.emitter=s,v.type=e,v.count=l.length,u(v)}return s}c.prototype.addListener=function(e,n){return w(this,e,n,!1)},c.prototype.on=c.prototype.addListener,c.prototype.prependListener=function(e,n){return w(this,e,n,!0)};function O(){if(!this.fired)return this.target.removeListener(this.type,this.wrapFn),this.fired=!0,arguments.length===0?this.listener.call(this.target):this.listener.apply(this.target,arguments)}function L(s,e,n){var i={fired:!1,wrapFn:void 0,target:s,type:e,listener:n},a=O.bind(i);return a.listener=n,i.wrapFn=a,a}c.prototype.once=function(e,n){return d(n),this.on(e,L(this,e,n)),this},c.prototype.prependOnceListener=function(e,n){return d(n),this.prependListener(e,L(this,e,n)),this},c.prototype.removeListener=function(e,n){var i,a,h,l,v;if(d(n),a=this._events,a===void 0)return this;if(i=a[e],i===void 0)return this;if(i===n||i.listener===n)--this._eventsCount===0?this._events=Object.create(null):(delete a[e],a.removeListener&&this.emit("removeListener",e,i.listener||n));else if(typeof i!="function"){for(h=-1,l=i.length-1;l>=0;l--)if(i[l]===n||i[l].listener===n){v=i[l].listener,h=l;break}if(h<0)return this;h===0?i.shift():B(i,h),i.length===1&&(a[e]=i[0]),a.removeListener!==void 0&&this.emit("removeListener",e,v||n)}return this},c.prototype.off=c.prototype.removeListener,c.prototype.removeAllListeners=function(e){var n,i,a;if(i=this._events,i===void 0)return this;if(i.removeListener===void 0)return arguments.length===0?(this._events=Object.create(null),this._eventsCount=0):i[e]!==void 0&&(--this._eventsCount===0?this._events=Object.create(null):delete i[e]),this;if(arguments.length===0){var h=Object.keys(i),l;for(a=0;a=0;a--)this.removeListener(e,n[a]);return this};function M(s,e,n){var i=s._events;if(i===void 0)return[];var a=i[e];return a===void 0?[]:typeof a=="function"?n?[a.listener||a]:[a]:n?N(a):W(a,a.length)}c.prototype.listeners=function(e){return M(this,e,!0)},c.prototype.rawListeners=function(e){return M(this,e,!1)},c.listenerCount=function(s,e){return typeof s.listenerCount=="function"?s.listenerCount(e):E.call(s,e)},c.prototype.listenerCount=E;function E(s){var e=this._events;if(e!==void 0){var n=e[s];if(typeof n=="function")return 1;if(n!==void 0)return n.length}return 0}c.prototype.eventNames=function(){return this._eventsCount>0?b(this._events):[]};function W(s,e){for(var n=new Array(e),i=0;i{var f=p&&p.__esModule?()=>p.default:()=>p;return S.d(f,{a:f}),f},S.d=(p,f)=>{for(var y in f)S.o(f,y)&&!S.o(p,y)&&Object.defineProperty(p,y,{enumerable:!0,get:f[y]})},S.o=(p,f)=>Object.prototype.hasOwnProperty.call(p,f);var re={};(()=>{var p=Object.defineProperty,f=(r,t,o)=>t in r?p(r,t,{enumerable:!0,configurable:!0,writable:!0,value:o}):r[t]=o,y=(r,t,o)=>(f(r,typeof t!="symbol"?t+"":t,o),o),b=(r,t,o)=>{if(!t.has(r))throw TypeError("Cannot "+o)},u=(r,t,o)=>(b(r,t,"read from private field"),o?o.call(r):t.get(r)),_=(r,t,o)=>{if(t.has(r))throw TypeError("Cannot add the same private member more than once");t instanceof WeakSet?t.add(r):t.set(r,o)},c=(r,t,o,m)=>(b(r,t,"write to private field"),m?m.call(r,o):t.set(r,o),o),I=(r,t,o,m)=>({set _(j){c(r,t,j,o)},get _(){return u(r,t,m)}}),d=(r,t,o)=>(b(r,t,"access private method"),o),g,w,O,L,M,E,W,B,N,s,e,n,i,a,h,l,v,C,x,k;const z="js-collection-entry",A=class{constructor(t){_(this,W),_(this,N),_(this,e),_(this,i),_(this,h),_(this,v),_(this,x),_(this,g,{entries:".js-collection-entries-container",entry:`.${z}`,addEntry:".js-add-collection-entry",removeEntry:".js-remove-collection-entry",maxCountMessage:".js-max-collection-count-message"}),_(this,w,void 0),_(this,O,void 0),_(this,L,void 0),_(this,M,void 0),_(this,E,null),this.wrapper=t,c(this,O,d(this,x,k).call(this,u(this,g).entries)),c(this,L,d(this,v,C).call(this,u(this,g).entry,u(this,O)).length),c(this,w,"maxCount"in this.wrapper.dataset?Number(this.wrapper.dataset.maxCount):null),c(this,M,u(this,L)),c(this,E,d(this,x,k).call(this,u(this,g).addEntry)),d(this,W,B).call(this)}hasMaxEntries(){return u(this,w)!==null&&u(this,L)>=u(this,w)}};g=new WeakMap,w=new WeakMap,O=new WeakMap,L=new WeakMap,M=new WeakMap,E=new WeakMap,W=new WeakSet,B=function(){u(this,E)!==null&&u(this,E).addEventListener("click",()=>d(this,N,s).call(this)),d(this,v,C).call(this,u(this,g).removeEntry).forEach(r=>d(this,e,n).call(this,r)),d(this,h,l).call(this)},N=new WeakSet,s=function(){if(u(this,w)!==null&&u(this,L)>=u(this,w))throw new Error("Cannot add a new collection entry.");const r=document.createElement("div");r.classList.add(z),r.innerHTML=this.wrapper.dataset.prototype.replace(/__name__/g,I(this,M)._++),u(this,O).append(r),++I(this,L)._;const t=r.querySelector(u(this,g).removeEntry);t!==null&&d(this,e,n).call(this,t),d(this,h,l).call(this),this.wrapper.dispatchEvent(new CustomEvent(A.events.entryAdded,{detail:r,bubbles:!0}))},e=new WeakSet,n=function(r){r.addEventListener("click",t=>{d(this,i,a).call(this,t.target)})},i=new WeakSet,a=function(r){const t=r.closest(u(this,g).entry);t.remove(),--I(this,L)._,d(this,h,l).call(this),this.wrapper.dispatchEvent(new CustomEvent(A.events.entryRemoved,{detail:t}))},h=new WeakSet,l=function(){if(u(this,E)===null||u(this,w)===null)return;const r=d(this,x,k).call(this,u(this,g).maxCountMessage);u(this,E).disabled=u(this,L)>=u(this,w),r!==null&&r.classList.toggle("d-none",!u(this,E).disabled)},v=new WeakSet,C=function(r,t=this.wrapper){const o=t.querySelectorAll(r);return Array.from(o).filter(m=>m.closest(A.selector)===this.wrapper)},x=new WeakSet,k=function(r,t=this.wrapper){var o;return(o=d(this,v,C).call(this,r,t).at(0))!=null?o:null},y(A,"selector",".js-collection-form"),y(A,"events",{entryAdded:"collectionEntryAdded",entryRemoved:"collectionEntryRemoved"});let R=A;var D=(r,t,o)=>{if(!t.has(r))throw TypeError("Cannot "+o)},T=(r,t,o)=>{if(t.has(r))throw TypeError("Cannot add the same private member more than once");t instanceof WeakSet?t.add(r):t.set(r,o)},X=(r,t,o,m)=>(D(r,t,"write to private field"),m?m.call(r,o):t.set(r,o),o),q=(r,t,o)=>(D(r,t,"access private method"),o),K,U,J,F,G;class Y{constructor(t){T(this,U),T(this,F),T(this,K,void 0),this.wrapper=t,X(this,K,new R(this.wrapper)),q(this,U,J).call(this)}}K=new WeakMap,U=new WeakSet,J=function(){this.wrapper.querySelectorAll(R.selector).forEach(q(this,F,G)),this.wrapper.addEventListener(R.events.entryAdded,r=>{const t=r.detail.querySelector(R.selector);q(this,F,G).call(this,t)})},F=new WeakSet,G=function(r){new R(r)};var Z=S(104),ee=S.n(Z);/** + * Copyright since 2007 PrestaShop SA and Contributors + * PrestaShop is an International Registered Trademark & Property of PrestaShop SA + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.md. + * It is also available through the world-wide-web at this URL: + * https://opensource.org/licenses/OSL-3.0 + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@prestashop.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade PrestaShop to newer + * versions in the future. If you wish to customize PrestaShop for your + * needs please refer to https://devdocs.prestashop.com/ for more information. + * + * @author PrestaShop SA and Contributors + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */const Q=new(ee()),se=null;/** + * Copyright since 2007 PrestaShop SA and Contributors + * PrestaShop is an International Registered Trademark & Property of PrestaShop SA + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.md. + * It is also available through the world-wide-web at this URL: + * https://opensource.org/licenses/OSL-3.0 + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@prestashop.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade PrestaShop to newer + * versions in the future. If you wish to customize PrestaShop for your + * needs please refer to https://devdocs.prestashop.com/ for more information. + * + * @author PrestaShop SA and Contributors + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */const{$:P}=window;class te{constructor(t){const o=t||{};return this.localeItemSelector=o.localeItemSelector||".js-locale-item",this.localeButtonSelector=o.localeButtonSelector||".js-locale-btn",this.localeInputSelector=o.localeInputSelector||".js-locale-input",this.selectedLocale=P(this.localeItemSelector).data("locale"),P("body").on("click",this.localeItemSelector,this.toggleLanguage.bind(this)),Q.on("languageSelected",this.toggleInputs.bind(this)),{localeItemSelector:this.localeItemSelector,localeButtonSelector:this.localeButtonSelector,localeInputSelector:this.localeInputSelector,refreshFormInputs:m=>{this.refreshInputs(m)},getSelectedLocale:()=>this.selectedLocale}}refreshInputs(t){this.selectedLocale&&Q.emit("languageSelected",{selectedLocale:this.selectedLocale,form:t})}toggleLanguage(t){const o=P(t.target),m=o.closest("form");this.selectedLocale=o.data("locale"),this.refreshInputs(m)}toggleInputs(t){const{form:o}=t;this.selectedLocale=t.selectedLocale;const m=o.find(this.localeButtonSelector),j=m.data("change-language-url");m.text(this.selectedLocale),o.find(this.localeInputSelector).addClass("d-none"),o.find(`${this.localeInputSelector}.js-locale-${this.selectedLocale}`).removeClass("d-none"),j&&this.saveSelectedLanguage(j,this.selectedLocale)}saveSelectedLanguage(t,o){P.post({url:t,data:{language_iso_code:o}})}}const ne=te;$(document).ready(()=>{const r=document.getElementById("consents_configuration_consents");new Y(r),r.addEventListener(R.events.entryAdded,t=>{$(t.detail).find('[data-toggle="popover"]').popover()}),new ne})})()})();})(); + +//# sourceMappingURL=consents.js.map \ No newline at end of file diff --git a/modules/inpostizi/views/js/admin/gui.js b/modules/inpostizi/views/js/admin/gui.js new file mode 100644 index 00000000..a3fe57ef --- /dev/null +++ b/modules/inpostizi/views/js/admin/gui.js @@ -0,0 +1,26 @@ +(()=>{(()=>{"use strict";var w={};(()=>{const c=i=>{document.readyState==="loading"?document.addEventListener("DOMContentLoaded",i):i()};/** + * Copyright since 2007 PrestaShop SA and Contributors + * PrestaShop is an International Registered Trademark & Property of PrestaShop SA + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.md. + * It is also available through the world-wide-web at this URL: + * https://opensource.org/licenses/OSL-3.0 + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@prestashop.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade PrestaShop to newer + * versions in the future. If you wish to customize PrestaShop for your + * needs please refer to https://devdocs.prestashop.com/ for more information. + * + * @author PrestaShop SA and Contributors + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */const{$:s}=window;class p{constructor(n){return this.$container=s(n),this.$container.on("click",".js-input-wrapper",e=>{const t=s(e.currentTarget);this.toggleChildTree(t)}),this.$container.on("click",".js-toggle-choice-tree-action",e=>{const t=s(e.currentTarget);this.toggleTree(t)}),{enableAutoCheckChildren:()=>this.enableAutoCheckChildren(),enableAllInputs:()=>this.enableAllInputs(),disableAllInputs:()=>this.disableAllInputs()}}enableAutoCheckChildren(){this.$container.on("change",'input[type="checkbox"]',n=>{const e=s(n.currentTarget);e.closest("li").find('ul input[type="checkbox"]').prop("checked",e.is(":checked"))})}enableAllInputs(){this.$container.find("input").removeAttr("disabled")}disableAllInputs(){this.$container.find("input").attr("disabled","disabled")}toggleChildTree(n){const e=n.closest("li");if(e.hasClass("expanded")){e.removeClass("expanded").addClass("collapsed");return}e.hasClass("collapsed")&&e.removeClass("collapsed").addClass("expanded")}toggleTree(n){const e=n.closest(".js-choice-tree-container"),t=n.data("action"),o={addClass:{expand:"expanded",collapse:"collapsed"},removeClass:{expand:"collapsed",collapse:"expanded"},nextAction:{expand:"collapse",collapse:"expand"},text:{expand:"collapsed-text",collapse:"expanded-text"},icon:{expand:"collapsed-icon",collapse:"expanded-icon"}};e.find("li").each((x,m)=>{const d=s(m);d.hasClass(o.removeClass[t])&&d.removeClass(o.removeClass[t]).addClass(o.addClass[t])}),n.data("action",o.nextAction[t]),n.find(".material-icons").text(n.data(o.icon[t])),n.find(".js-toggle-text").text(n.data(o.text[t]))}}const a={form:'[name="gui_configuration"]',selects:".js-widget-attribute-provider",previewContents:".js-inpostizi-btn-preview-content"};let l=null;const r=()=>{var i;typeof window.handleInpostIziButtons=="function"&&window.handleInpostIziButtons(),l!==null&&l.refresh(),typeof window.inpostizi_merchant_client_id!="undefined"&&typeof((i=window==null?void 0:window.InPostPayWidget)==null?void 0:i.init)=="function"&&(l=window.InPostPayWidget.init({merchantClientId:window.inpostizi_merchant_client_id}))},h=(i,n)=>{const e=n.getAttribute("data-type"),t=i.querySelector(`${a.previewContents}[data-type="${e}"]`);t&&n.replaceWith(t),r()},u=i=>{const e=new DOMParser().parseFromString(i,"text/html");if(!e)return;document.querySelectorAll(a.previewContents).forEach(o=>{h(e,o)})},C=()=>{const i=document.querySelector(a.form),n=document.querySelectorAll(a.selects),e=()=>{const t=new FormData(i);t.append("gui_configuration[invalid_extra_field]","temporary valid form submission countermeasure"),fetch("",{headers:{"content-type":"application/x-www-form-urlencoded; charset=UTF-8","x-requested-with":"XMLHttpRequest"},body:new URLSearchParams(t).toString(),method:"POST"}).then(o=>o.text()).then(u)};n.forEach(t=>{t.addEventListener("change",e)})},f=()=>{const i=document.querySelector(".js-choice-tree-container");i!==null&&new p(i).enableAutoCheckChildren()};c(()=>{f(),r()}),c(C)})()})();})(); + +//# sourceMappingURL=gui.js.map \ No newline at end of file diff --git a/modules/inpostizi/views/js/admin/hot-products.js b/modules/inpostizi/views/js/admin/hot-products.js new file mode 100644 index 00000000..a7a457ce --- /dev/null +++ b/modules/inpostizi/views/js/admin/hot-products.js @@ -0,0 +1,3 @@ +(()=>{var Cn=Object.defineProperty;var xn=(B,F,H)=>F in B?Cn(B,F,{enumerable:!0,configurable:!0,writable:!0,value:H}):B[F]=H;var R=(B,F,H)=>(xn(B,typeof F!="symbol"?F+"":F,H),H);(()=>{"use strict";var B={};(()=>{function F(n,e){n.split(/\s+/).forEach(t=>{e(t)})}class H{constructor(){this._events={}}on(e,t){F(e,i=>{const s=this._events[i]||[];s.push(t),this._events[i]=s})}off(e,t){var i=arguments.length;if(i===0){this._events={};return}F(e,s=>{if(i===1){delete this._events[s];return}const o=this._events[s];o!==void 0&&(o.splice(o.indexOf(t),1),this._events[s]=o)})}trigger(e,...t){var i=this;F(e,s=>{const o=i._events[s];o!==void 0&&o.forEach(r=>{r.apply(i,t)})})}}function He(n){return n.plugins={},class extends n{constructor(){super(...arguments),this.plugins={names:[],settings:{},requested:{},loaded:{}}}static define(e,t){n.plugins[e]={name:e,fn:t}}initializePlugins(e){var t,i;const s=this,o=[];if(Array.isArray(e))e.forEach(r=>{typeof r=="string"?o.push(r):(s.plugins.settings[r.name]=r.options,o.push(r.name))});else if(e)for(t in e)e.hasOwnProperty(t)&&(s.plugins.settings[t]=e[t],o.push(t));for(;i=o.shift();)s.require(i)}loadPlugin(e){var t=this,i=t.plugins,s=n.plugins[e];if(!n.plugins.hasOwnProperty(e))throw new Error('Unable to find "'+e+'" plugin');i.requested[e]=!0,i.loaded[e]=s.fn.apply(t,[t.plugins.settings[e]||{}]),i.names.push(e)}require(e){var t=this,i=t.plugins;if(!t.plugins.loaded.hasOwnProperty(e)){if(i.requested[e])throw new Error('Plugin has circular dependency ("'+e+'")');t.loadPlugin(e)}return i.loaded[e]}}}const Y=n=>(n=n.filter(Boolean),n.length<2?n[0]||"":Ve(n)==1?"["+n.join("")+"]":"(?:"+n.join("|")+")"),pe=n=>{if(!Ne(n))return n.join("");let e="",t=0;const i=()=>{t>1&&(e+="{"+t+"}")};return n.forEach((s,o)=>{if(s===n[o-1]){t++;return}i(),e+=s,t=1}),i(),e},fe=n=>{let e=Array.from(n);return Y(e)},Ne=n=>new Set(n).size!==n.length,z=n=>(n+"").replace(/([\$\(\)\*\+\.\?\[\]\^\{\|\}\\])/gu,"\\$1"),Ve=n=>n.reduce((e,t)=>Math.max(e,Re(t)),0),Re=n=>Array.from(n).length,he=n=>{if(n.length===1)return[[n]];let e=[];const t=n.substring(1);return he(t).forEach(function(s){let o=s.slice(0);o[0]=n.charAt(0)+o[0],e.push(o),o=s.slice(0),o.unshift(n.charAt(0)),e.push(o)}),e},Be=[[0,65535]],Ke="[\u0300-\u036F\xB7\u02BE\u02BC]";let U,ge;const je=3,se={},me={"/":"\u2044\u2215",0:"\u07C0",a:"\u2C65\u0250\u0251",aa:"\uA733",ae:"\xE6\u01FD\u01E3",ao:"\uA735",au:"\uA737",av:"\uA739\uA73B",ay:"\uA73D",b:"\u0180\u0253\u0183",c:"\uA73F\u0188\u023C\u2184",d:"\u0111\u0257\u0256\u1D05\u018C\uABB7\u0501\u0266",e:"\u025B\u01DD\u1D07\u0247",f:"\uA77C\u0192",g:"\u01E5\u0260\uA7A1\u1D79\uA77F\u0262",h:"\u0127\u2C68\u2C76\u0265",i:"\u0268\u0131",j:"\u0249\u0237",k:"\u0199\u2C6A\uA741\uA743\uA745\uA7A3",l:"\u0142\u019A\u026B\u2C61\uA749\uA747\uA781\u026D",m:"\u0271\u026F\u03FB",n:"\uA7A5\u019E\u0272\uA791\u1D0E\u043B\u0509",o:"\xF8\u01FF\u0254\u0275\uA74B\uA74D\u1D11",oe:"\u0153",oi:"\u01A3",oo:"\uA74F",ou:"\u0223",p:"\u01A5\u1D7D\uA751\uA753\uA755\u03C1",q:"\uA757\uA759\u024B",r:"\u024D\u027D\uA75B\uA7A7\uA783",s:"\xDF\u023F\uA7A9\uA785\u0282",t:"\u0167\u01AD\u0288\u2C66\uA787",th:"\xFE",tz:"\uA729",u:"\u0289",v:"\u028B\uA75F\u028C",vy:"\uA761",w:"\u2C73",y:"\u01B4\u024F\u1EFF",z:"\u01B6\u0225\u0240\u2C6C\uA763",hv:"\u0195"};for(let n in me){let e=me[n]||"";for(let t=0;t{U===void 0&&(U=qe(n||Be))},ve=(n,e="NFKD")=>n.normalize(e),Q=n=>Array.from(n).reduce((e,t)=>e+Ue(t),""),Ue=n=>(n=ve(n).toLowerCase().replace(ze,e=>se[e]||""),ve(n,"NFC"));function*Qe(n){for(const[e,t]of n)for(let i=e;i<=t;i++){let s=String.fromCharCode(i),o=Q(s);o!=s.toLowerCase()&&(o.length>je||o.length!=0&&(yield{folded:o,composed:s,code_point:i}))}}const Ge=n=>{const e={},t=(i,s)=>{const o=e[i]||new Set,r=new RegExp("^"+fe(o)+"$","iu");s.match(r)||(o.add(z(s)),e[i]=o)};for(let i of Qe(n))t(i.folded,i.folded),t(i.folded,i.composed);return e},qe=n=>{const e=Ge(n),t={};let i=[];for(let o in e){let r=e[o];r&&(t[o]=fe(r)),o.length>1&&i.push(z(o))}i.sort((o,r)=>r.length-o.length);const s=Y(i);return ge=new RegExp("^"+s,"u"),t},We=(n,e=1)=>{let t=0;return n=n.map(i=>(U[i]&&(t+=i.length),U[i]||i)),t>=e?pe(n):""},Je=(n,e=1)=>(e=Math.max(e,n.length-1),Y(he(n).map(t=>We(t,e)))),_e=(n,e=!0)=>{let t=n.length>1?1:0;return Y(n.map(i=>{let s=[];const o=e?i.length():i.length()-1;for(let r=0;r{for(const t of e){if(t.start!=n.start||t.end!=n.end||t.substrs.join("")!==n.substrs.join(""))continue;let i=n.parts;const s=r=>{for(const l of i){if(l.start===r.start&&l.substr===r.substr)return!1;if(!(r.length==1||l.length==1)&&(r.startl.start||l.startr.start))return!0}return!1};if(!(t.parts.filter(s).length>0))return!0}return!1};class G{constructor(){R(this,"parts");R(this,"substrs");R(this,"start");R(this,"end");this.parts=[],this.substrs=[],this.start=0,this.end=0}add(e){e&&(this.parts.push(e),this.substrs.push(e.substr),this.start=Math.min(e.start,this.start),this.end=Math.max(e.end,this.end))}last(){return this.parts[this.parts.length-1]}length(){return this.parts.length}clone(e,t){let i=new G,s=JSON.parse(JSON.stringify(this.parts)),o=s.pop();for(const c of s)i.add(c);let r=t.substr.substring(0,e-o.start),l=r.length;return i.add({start:o.start,end:o.start+l,length:l,substr:r}),i}}const Ze=n=>{Ye(),n=Q(n);let e="",t=[new G];for(let i=0;i0){c=c.sort((d,u)=>d.length()-u.length());for(let d of c)Xe(d,t)||t.push(d);continue}if(i>0&&a.size==1&&!a.has("3")){e+=_e(t,!1);let d=new G;const u=t[0];u&&d.add(u.last()),t=[d]}}return e+=_e(t,!0),e},et=(n,e)=>{if(n)return n[e]},tt=(n,e)=>{if(n){for(var t,i=e.split(".");(t=i.shift())&&(n=n[t]););return n}},re=(n,e,t)=>{var i,s;return!n||(n=n+"",e.regex==null)||(s=n.search(e.regex),s===-1)?0:(i=e.string.length/n.length,s===0&&(i+=.5),i*t)},oe=(n,e)=>{var t=n[e];if(typeof t=="function")return t;t&&!Array.isArray(t)&&(n[e]=[t])},q=(n,e)=>{if(Array.isArray(n))n.forEach(e);else for(var t in n)n.hasOwnProperty(t)&&e(n[t],t)},nt=(n,e)=>typeof n=="number"&&typeof e=="number"?n>e?1:ne?1:e>n?-1:0);class it{constructor(e,t){R(this,"items");R(this,"settings");this.items=e,this.settings=t||{diacritics:!0}}tokenize(e,t,i){if(!e||!e.length)return[];const s=[],o=e.split(/\s+/);var r;return i&&(r=new RegExp("^("+Object.keys(i).map(z).join("|")+"):(.*)$")),o.forEach(l=>{let c,a=null,d=null;r&&(c=l.match(r))&&(a=c[1],l=c[2]),l.length>0&&(this.settings.diacritics?d=Ze(l)||null:d=z(l),d&&t&&(d="\\b"+d)),s.push({string:l,regex:d?new RegExp(d,"iu"):null,field:a})}),s}getScoreFunction(e,t){var i=this.prepareSearch(e,t);return this._getScoreFunction(i)}_getScoreFunction(e){const t=e.tokens,i=t.length;if(!i)return function(){return 0};const s=e.options.fields,o=e.weights,r=s.length,l=e.getAttrFn;if(!r)return function(){return 1};const c=function(){return r===1?function(a,d){const u=s[0].field;return re(l(d,u),a,o[u]||1)}:function(a,d){var u=0;if(a.field){const f=l(d,a.field);!a.regex&&f?u+=1/r:u+=re(f,a,1)}else q(o,(f,w)=>{u+=re(l(d,w),a,f)});return u/r}}();return i===1?function(a){return c(t[0],a)}:e.options.conjunction==="and"?function(a){var d,u=0;for(let f of t){if(d=c(f,a),d<=0)return 0;u+=d}return u/i}:function(a){var d=0;return q(t,u=>{d+=c(u,a)}),d/i}}getSortFunction(e,t){var i=this.prepareSearch(e,t);return this._getSortFunction(i)}_getSortFunction(e){var t,i=[];const s=this,o=e.options,r=!e.query&&o.sort_empty?o.sort_empty:o.sort;if(typeof r=="function")return r.bind(this);const l=function(a,d){return a==="$score"?d.score:e.getAttrFn(s.items[d.id],a)};if(r)for(let a of r)(e.query||a.field!=="$score")&&i.push(a);if(e.query){t=!0;for(let a of i)if(a.field==="$score"){t=!1;break}t&&i.unshift({field:"$score",direction:"desc"})}else i=i.filter(a=>a.field!=="$score");return i.length?function(a,d){var u,f;for(let w of i)if(f=w.field,u=(w.direction==="desc"?-1:1)*nt(l(f,a),l(f,d)),u)return u;return 0}:null}prepareSearch(e,t){const i={};var s=Object.assign({},t);if(oe(s,"sort"),oe(s,"sort_empty"),s.fields){oe(s,"fields");const o=[];s.fields.forEach(r=>{typeof r=="string"&&(r={field:r,weight:1}),o.push(r),i[r.field]="weight"in r?r.weight:1}),s.fields=o}return{options:s,query:e.toLowerCase().trim(),tokens:this.tokenize(e,s.respect_word_boundaries,i),total:0,items:[],weights:i,getAttrFn:s.nesting?tt:et}}search(e,t){var i=this,s,o;o=this.prepareSearch(e,t),t=o.options,e=o.query;const r=t.score||i._getScoreFunction(o);e.length?q(i.items,(c,a)=>{s=r(c),(t.filter===!1||s>0)&&o.items.push({score:s,id:a})}):q(i.items,(c,a)=>{o.items.push({score:1,id:a})});const l=i._getSortFunction(o);return l&&o.items.sort(l),o.total=o.items.length,typeof t.limit=="number"&&(o.items=o.items.slice(0,t.limit)),o}}const T=n=>typeof n=="undefined"||n===null?null:W(n),W=n=>typeof n=="boolean"?n?"1":"0":n+"",le=n=>(n+"").replace(/&/g,"&").replace(//g,">").replace(/"/g,"""),st=(n,e)=>e>0?window.setTimeout(n,e):(n.call(null),null),rt=(n,e)=>{var t;return function(i,s){var o=this;t&&(o.loading=Math.max(o.loading-1,0),clearTimeout(t)),t=setTimeout(function(){t=null,o.loadedSearches[i]=!0,n.call(o,i,s)},e)}},ye=(n,e,t)=>{var i,s=n.trigger,o={};n.trigger=function(){var r=arguments[0];if(e.indexOf(r)!==-1)o[r]=arguments;else return s.apply(n,arguments)},t.apply(n,[]),n.trigger=s;for(i of e)i in o&&s.apply(n,o[i])},ot=n=>({start:n.selectionStart||0,length:(n.selectionEnd||0)-(n.selectionStart||0)}),C=(n,e=!1)=>{n&&(n.preventDefault(),e&&n.stopPropagation())},L=(n,e,t,i)=>{n.addEventListener(e,t,i)},N=(n,e)=>{if(!e||!e[n])return!1;var t=(e.altKey?1:0)+(e.ctrlKey?1:0)+(e.shiftKey?1:0)+(e.metaKey?1:0);return t===1},ae=(n,e)=>{const t=n.getAttribute("id");return t||(n.setAttribute("id",e),e)},Oe=n=>n.replace(/[\\"']/g,"\\$&"),V=(n,e)=>{e&&n.append(e)},x=(n,e)=>{if(Array.isArray(n))n.forEach(e);else for(var t in n)n.hasOwnProperty(t)&&e(n[t],t)},P=n=>{if(n.jquery)return n[0];if(n instanceof HTMLElement)return n;if(we(n)){var e=document.createElement("template");return e.innerHTML=n.trim(),e.content.firstChild}return document.querySelector(n)},we=n=>typeof n=="string"&&n.indexOf("<")>-1,lt=n=>n.replace(/['"\\]/g,"\\$&"),ce=(n,e)=>{var t=document.createEvent("HTMLEvents");t.initEvent(e,!0,!1),n.dispatchEvent(t)},J=(n,e)=>{Object.assign(n.style,e)},k=(n,...e)=>{var t=be(e);n=Se(n),n.map(i=>{t.map(s=>{i.classList.add(s)})})},M=(n,...e)=>{var t=be(e);n=Se(n),n.map(i=>{t.map(s=>{i.classList.remove(s)})})},be=n=>{var e=[];return x(n,t=>{typeof t=="string"&&(t=t.trim().split(/[\t\n\f\r\s]/)),Array.isArray(t)&&(e=e.concat(t))}),e.filter(Boolean)},Se=n=>(Array.isArray(n)||(n=[n]),n),ue=(n,e,t)=>{if(!(t&&!t.contains(n)))for(;n&&n.matches;){if(n.matches(e))return n;n=n.parentNode}},Ae=(n,e=0)=>e>0?n[n.length-1]:n[0],at=n=>Object.keys(n).length===0,Ce=(n,e)=>{if(!n)return-1;e=e||n.nodeName;for(var t=0;n=n.previousElementSibling;)n.matches(e)&&t++;return t},S=(n,e)=>{x(e,(t,i)=>{t==null?n.removeAttribute(i):n.setAttribute(i,""+t)})},de=(n,e)=>{n.parentNode&&n.parentNode.replaceChild(e,n)},ct=(n,e)=>{if(e===null)return;if(typeof e=="string"){if(!e.length)return;e=new RegExp(e,"i")}const t=o=>{var r=o.data.match(e);if(r&&o.data.length>0){var l=document.createElement("span");l.className="highlight";var c=o.splitText(r.index);c.splitText(r[0].length);var a=c.cloneNode(!0);return l.appendChild(a),de(c,l),1}return 0},i=o=>{o.nodeType===1&&o.childNodes&&!/(script|style)/i.test(o.tagName)&&(o.className!=="highlight"||o.tagName!=="SPAN")&&Array.from(o.childNodes).forEach(r=>{s(r)})},s=o=>o.nodeType===3?t(o):(i(o),0);s(n)},ut=n=>{var e=n.querySelectorAll("span.highlight");Array.prototype.forEach.call(e,function(t){var i=t.parentNode;i.replaceChild(t.firstChild,t),i.normalize()})},dt=65,pt=13,ft=27,ht=37,gt=38,mt=39,vt=40,xe=8,_t=46,Ie=9,X=(typeof navigator=="undefined"?!1:/Mac/.test(navigator.userAgent))?"metaKey":"ctrlKey",Le={options:[],optgroups:[],plugins:[],delimiter:",",splitOn:null,persist:!0,diacritics:!0,create:null,createOnBlur:!1,createFilter:null,highlight:!0,openOnFocus:!0,shouldOpen:null,maxOptions:50,maxItems:null,hideSelected:null,duplicates:!1,addPrecedence:!1,selectOnTab:!1,preload:null,allowEmptyOption:!1,refreshThrottle:300,loadThrottle:300,loadingClass:"loading",dataAttr:null,optgroupField:"optgroup",valueField:"value",labelField:"text",disabledField:"disabled",optgroupLabelField:"label",optgroupValueField:"value",lockOptgroupOrder:!1,sortField:"$order",searchField:["text"],searchConjunction:"and",mode:null,wrapperClass:"ts-wrapper",controlClass:"ts-control",dropdownClass:"ts-dropdown",dropdownContentClass:"ts-dropdown-content",itemClass:"item",optionClass:"option",dropdownParent:null,controlInput:'',copyClassesToDropdown:!1,placeholder:null,hidePlaceholder:null,shouldLoad:function(n){return n.length>0},render:{}};function Ee(n,e){var t=Object.assign({},Le,e),i=t.dataAttr,s=t.labelField,o=t.valueField,r=t.disabledField,l=t.optgroupField,c=t.optgroupLabelField,a=t.optgroupValueField,d=n.tagName.toLowerCase(),u=n.getAttribute("placeholder")||n.getAttribute("data-placeholder");if(!u&&!t.allowEmptyOption){let v=n.querySelector('option[value=""]');v&&(u=v.textContent)}var f={placeholder:u,options:[],optgroups:[],items:[],maxItems:null},w=()=>{var v,b=f.options,y={},h=1;let A=0;var D=m=>{var _=Object.assign({},m.dataset),g=i&&_[i];return typeof g=="string"&&g.length&&(_=Object.assign(_,JSON.parse(g))),_},te=(m,_)=>{var g=T(m.value);if(g!=null&&!(!g&&!t.allowEmptyOption)){if(y.hasOwnProperty(g)){if(_){var I=y[g][l];I?Array.isArray(I)?I.push(_):y[g][l]=[I,_]:y[g][l]=_}}else{var O=D(m);O[s]=O[s]||m.textContent,O[o]=O[o]||g,O[r]=O[r]||m.disabled,O[l]=O[l]||_,O.$option=m,O.$order=O.$order||++A,y[g]=O,b.push(O)}m.selected&&f.items.push(g)}},j=m=>{var _,g;g=D(m),g[c]=g[c]||m.getAttribute("label")||"",g[a]=g[a]||h++,g[r]=g[r]||m.disabled,g.$order=g.$order||++A,f.optgroups.push(g),_=g[a],x(m.children,I=>{te(I,_)})};f.maxItems=n.hasAttribute("multiple")?null:1,x(n.children,m=>{v=m.tagName.toLowerCase(),v==="optgroup"?j(m):v==="option"&&te(m)})},p=()=>{const v=n.getAttribute(i);if(v)f.options=JSON.parse(v),x(f.options,y=>{f.items.push(y[o])});else{var b=n.value.trim()||"";if(!t.allowEmptyOption&&!b.length)return;const y=b.split(t.delimiter);x(y,h=>{const A={};A[s]=h,A[o]=h,f.options.push(A)}),f.items=y}};return d==="select"?w():p(),Object.assign({},Le,f,e)}var ke=0;class E extends He(H){constructor(e,t){super(),this.order=0,this.isOpen=!1,this.isDisabled=!1,this.isReadOnly=!1,this.isInvalid=!1,this.isValid=!0,this.isLocked=!1,this.isFocused=!1,this.isInputHidden=!1,this.isSetup=!1,this.ignoreFocus=!1,this.ignoreHover=!1,this.hasOptions=!1,this.lastValue="",this.caretPos=0,this.loading=0,this.loadedSearches={},this.activeOption=null,this.activeItems=[],this.optgroups={},this.options={},this.userOptions={},this.items=[],this.refreshTimeout=null,ke++;var i,s=P(e);if(s.tomselect)throw new Error("Tom Select already initialized on this element");s.tomselect=this;var o=window.getComputedStyle&&window.getComputedStyle(s,null);i=o.getPropertyValue("direction");const r=Ee(s,t);this.settings=r,this.input=s,this.tabIndex=s.tabIndex||0,this.is_select_tag=s.tagName.toLowerCase()==="select",this.rtl=/rtl/i.test(i),this.inputId=ae(s,"tomselect-"+ke),this.isRequired=s.required,this.sifter=new it(this.options,{diacritics:r.diacritics}),r.mode=r.mode||(r.maxItems===1?"single":"multi"),typeof r.hideSelected!="boolean"&&(r.hideSelected=r.mode==="multi"),typeof r.hidePlaceholder!="boolean"&&(r.hidePlaceholder=r.mode!=="multi");var l=r.createFilter;typeof l!="function"&&(typeof l=="string"&&(l=new RegExp(l)),l instanceof RegExp?r.createFilter=b=>l.test(b):r.createFilter=b=>this.settings.duplicates||!this.options[b]),this.initializePlugins(r.plugins),this.setupCallbacks(),this.setupTemplates();const c=P("
"),a=P("
"),d=this._render("dropdown"),u=P('
'),f=this.input.getAttribute("class")||"",w=r.mode;var p;if(k(c,r.wrapperClass,f,w),k(a,r.controlClass),V(c,a),k(d,r.dropdownClass,w),r.copyClassesToDropdown&&k(d,f),k(u,r.dropdownContentClass),V(d,u),P(r.dropdownParent||c).appendChild(d),we(r.controlInput)){p=P(r.controlInput);var v=["autocorrect","autocapitalize","autocomplete","spellcheck"];x(v,b=>{s.getAttribute(b)&&S(p,{[b]:s.getAttribute(b)})}),p.tabIndex=-1,a.appendChild(p),this.focus_node=p}else r.controlInput?(p=P(r.controlInput),this.focus_node=p):(p=P(""),this.focus_node=a);this.wrapper=c,this.dropdown=d,this.dropdown_content=u,this.control=a,this.control_input=p,this.setup()}setup(){const e=this,t=e.settings,i=e.control_input,s=e.dropdown,o=e.dropdown_content,r=e.wrapper,l=e.control,c=e.input,a=e.focus_node,d={passive:!0},u=e.inputId+"-ts-dropdown";S(o,{id:u}),S(a,{role:"combobox","aria-haspopup":"listbox","aria-expanded":"false","aria-controls":u});const f=ae(a,e.inputId+"-ts-control"),w="label[for='"+lt(e.inputId)+"']",p=document.querySelector(w),v=e.focus.bind(e);if(p){L(p,"click",v),S(p,{for:f});const h=ae(p,e.inputId+"-ts-label");S(a,{"aria-labelledby":h}),S(o,{"aria-labelledby":h})}if(r.style.width=c.style.width,e.plugins.names.length){const h="plugin-"+e.plugins.names.join(" plugin-");k([r,s],h)}(t.maxItems===null||t.maxItems>1)&&e.is_select_tag&&S(c,{multiple:"multiple"}),t.placeholder&&S(i,{placeholder:t.placeholder}),!t.splitOn&&t.delimiter&&(t.splitOn=new RegExp("\\s*"+z(t.delimiter)+"+\\s*")),t.load&&t.loadThrottle&&(t.load=rt(t.load,t.loadThrottle)),L(s,"mousemove",()=>{e.ignoreHover=!1}),L(s,"mouseenter",h=>{var A=ue(h.target,"[data-selectable]",s);A&&e.onOptionHover(h,A)},{capture:!0}),L(s,"click",h=>{const A=ue(h.target,"[data-selectable]");A&&(e.onOptionSelect(h,A),C(h,!0))}),L(l,"click",h=>{var A=ue(h.target,"[data-ts-item]",l);if(A&&e.onItemSelect(h,A)){C(h,!0);return}i.value==""&&(e.onClick(),C(h,!0))}),L(a,"keydown",h=>e.onKeyDown(h)),L(i,"keypress",h=>e.onKeyPress(h)),L(i,"input",h=>e.onInput(h)),L(a,"blur",h=>e.onBlur(h)),L(a,"focus",h=>e.onFocus(h)),L(i,"paste",h=>e.onPaste(h));const b=h=>{const A=h.composedPath()[0];if(!r.contains(A)&&!s.contains(A)){e.isFocused&&e.blur(),e.inputState();return}A==i&&e.isOpen?h.stopPropagation():C(h,!0)},y=()=>{e.isOpen&&e.positionDropdown()};L(document,"mousedown",b),L(window,"scroll",y,d),L(window,"resize",y,d),this._destroy=()=>{document.removeEventListener("mousedown",b),window.removeEventListener("scroll",y),window.removeEventListener("resize",y),p&&p.removeEventListener("click",v)},this.revertSettings={innerHTML:c.innerHTML,tabIndex:c.tabIndex},c.tabIndex=-1,c.insertAdjacentElement("afterend",e.wrapper),e.sync(!1),t.items=[],delete t.optgroups,delete t.options,L(c,"invalid",()=>{e.isValid&&(e.isValid=!1,e.isInvalid=!0,e.refreshState())}),e.updateOriginalInput(),e.refreshItems(),e.close(!1),e.inputState(),e.isSetup=!0,c.disabled?e.disable():c.readOnly?e.setReadOnly(!0):e.enable(),e.on("change",this.onChange),k(c,"tomselected","ts-hidden-accessible"),e.trigger("initialize"),t.preload===!0&&e.preload()}setupOptions(e=[],t=[]){this.addOptions(e),x(t,i=>{this.registerOptionGroup(i)})}setupTemplates(){var e=this,t=e.settings.labelField,i=e.settings.optgroupLabelField,s={optgroup:o=>{let r=document.createElement("div");return r.className="optgroup",r.appendChild(o.options),r},optgroup_header:(o,r)=>'
'+r(o[i])+"
",option:(o,r)=>"
"+r(o[t])+"
",item:(o,r)=>"
"+r(o[t])+"
",option_create:(o,r)=>'
Add '+r(o.input)+"
",no_results:()=>'
No results found
',loading:()=>'
',not_loading:()=>{},dropdown:()=>"
"};e.settings.render=Object.assign({},s,e.settings.render)}setupCallbacks(){var e,t,i={initialize:"onInitialize",change:"onChange",item_add:"onItemAdd",item_remove:"onItemRemove",item_select:"onItemSelect",clear:"onClear",option_add:"onOptionAdd",option_remove:"onOptionRemove",option_clear:"onOptionClear",optgroup_add:"onOptionGroupAdd",optgroup_remove:"onOptionGroupRemove",optgroup_clear:"onOptionGroupClear",dropdown_open:"onDropdownOpen",dropdown_close:"onDropdownClose",type:"onType",load:"onLoad",focus:"onFocus",blur:"onBlur"};for(e in i)t=this.settings[i[e]],t&&this.on(e,t)}sync(e=!0){const t=this,i=e?Ee(t.input,{delimiter:t.settings.delimiter}):t.settings;t.setupOptions(i.options,i.optgroups),t.setValue(i.items||[],!0),t.lastQuery=null}onClick(){var e=this;if(e.activeItems.length>0){e.clearActiveItems(),e.focus();return}e.isFocused&&e.isOpen?e.blur():e.focus()}onMouseDown(){}onChange(){ce(this.input,"input"),ce(this.input,"change")}onPaste(e){var t=this;if(t.isInputHidden||t.isLocked){C(e);return}t.settings.splitOn&&setTimeout(()=>{var i=t.inputValue();if(i.match(t.settings.splitOn)){var s=i.trim().split(t.settings.splitOn);x(s,o=>{T(o)&&(this.options[o]?t.addItem(o):t.createItem(o))})}},0)}onKeyPress(e){var t=this;if(t.isLocked){C(e);return}var i=String.fromCharCode(e.keyCode||e.which);if(t.settings.create&&t.settings.mode==="multi"&&i===t.settings.delimiter){t.createItem(),C(e);return}}onKeyDown(e){var t=this;if(t.ignoreHover=!0,t.isLocked){e.keyCode!==Ie&&C(e);return}switch(e.keyCode){case dt:if(N(X,e)&&t.control_input.value==""){C(e),t.selectAll();return}break;case ft:t.isOpen&&(C(e,!0),t.close()),t.clearActiveItems();return;case vt:if(!t.isOpen&&t.hasOptions)t.open();else if(t.activeOption){let i=t.getAdjacent(t.activeOption,1);i&&t.setActiveOption(i)}C(e);return;case gt:if(t.activeOption){let i=t.getAdjacent(t.activeOption,-1);i&&t.setActiveOption(i)}C(e);return;case pt:t.canSelect(t.activeOption)?(t.onOptionSelect(e,t.activeOption),C(e)):(t.settings.create&&t.createItem()||document.activeElement==t.control_input&&t.isOpen)&&C(e);return;case ht:t.advanceSelection(-1,e);return;case mt:t.advanceSelection(1,e);return;case Ie:t.settings.selectOnTab&&(t.canSelect(t.activeOption)&&(t.onOptionSelect(e,t.activeOption),C(e)),t.settings.create&&t.createItem()&&C(e));return;case xe:case _t:t.deleteSelection(e);return}t.isInputHidden&&!N(X,e)&&C(e)}onInput(e){if(this.isLocked)return;const t=this.inputValue();if(this.lastValue!==t){if(this.lastValue=t,t==""){this._onInput();return}this.refreshTimeout&&window.clearTimeout(this.refreshTimeout),this.refreshTimeout=st(()=>{this.refreshTimeout=null,this._onInput()},this.settings.refreshThrottle)}}_onInput(){const e=this.lastValue;this.settings.shouldLoad.call(this,e)&&this.load(e),this.refreshOptions(),this.trigger("type",e)}onOptionHover(e,t){this.ignoreHover||this.setActiveOption(t,!1)}onFocus(e){var t=this,i=t.isFocused;if(t.isDisabled||t.isReadOnly){t.blur(),C(e);return}t.ignoreFocus||(t.isFocused=!0,t.settings.preload==="focus"&&t.preload(),i||t.trigger("focus"),t.activeItems.length||(t.inputState(),t.refreshOptions(!!t.settings.openOnFocus)),t.refreshState())}onBlur(e){if(document.hasFocus()!==!1){var t=this;if(t.isFocused){t.isFocused=!1,t.ignoreFocus=!1;var i=()=>{t.close(),t.setActiveItem(),t.setCaret(t.items.length),t.trigger("blur")};t.settings.create&&t.settings.createOnBlur?t.createItem(null,i):i()}}}onOptionSelect(e,t){var i,s=this;t.parentElement&&t.parentElement.matches("[data-disabled]")||(t.classList.contains("create")?s.createItem(null,()=>{s.settings.closeAfterSelect&&s.close()}):(i=t.dataset.value,typeof i!="undefined"&&(s.lastQuery=null,s.addItem(i),s.settings.closeAfterSelect&&s.close(),!s.settings.hideSelected&&e.type&&/click/.test(e.type)&&s.setActiveOption(t))))}canSelect(e){return!!(this.isOpen&&e&&this.dropdown_content.contains(e))}onItemSelect(e,t){var i=this;return!i.isLocked&&i.settings.mode==="multi"?(C(e),i.setActiveItem(t,e),!0):!1}canLoad(e){return!(!this.settings.load||this.loadedSearches.hasOwnProperty(e))}load(e){const t=this;if(!t.canLoad(e))return;k(t.wrapper,t.settings.loadingClass),t.loading++;const i=t.loadCallback.bind(t);t.settings.load.call(t,e,i)}loadCallback(e,t){const i=this;i.loading=Math.max(i.loading-1,0),i.lastQuery=null,i.clearActiveOption(),i.setupOptions(e,t),i.refreshOptions(i.isFocused&&!i.isInputHidden),i.loading||M(i.wrapper,i.settings.loadingClass),i.trigger("load",e,t)}preload(){var e=this.wrapper.classList;e.contains("preloaded")||(e.add("preloaded"),this.load(""))}setTextboxValue(e=""){var t=this.control_input,i=t.value!==e;i&&(t.value=e,ce(t,"update"),this.lastValue=e)}getValue(){return this.is_select_tag&&this.input.hasAttribute("multiple")?this.items:this.items.join(this.settings.delimiter)}setValue(e,t){var i=t?[]:["change"];ye(this,i,()=>{this.clear(t),this.addItems(e,t)})}setMaxItems(e){e===0&&(e=null),this.settings.maxItems=e,this.refreshState()}setActiveItem(e,t){var i=this,s,o,r,l,c,a;if(i.settings.mode!=="single"){if(!e){i.clearActiveItems(),i.isFocused&&i.inputState();return}if(s=t&&t.type.toLowerCase(),s==="click"&&N("shiftKey",t)&&i.activeItems.length){for(a=i.getLastActive(),r=Array.prototype.indexOf.call(i.control.children,a),l=Array.prototype.indexOf.call(i.control.children,e),r>l&&(c=r,r=l,l=c),o=r;o<=l;o++)e=i.control.children[o],i.activeItems.indexOf(e)===-1&&i.setActiveItemClass(e);C(t)}else s==="click"&&N(X,t)||s==="keydown"&&N("shiftKey",t)?e.classList.contains("active")?i.removeActiveItem(e):i.setActiveItemClass(e):(i.clearActiveItems(),i.setActiveItemClass(e));i.inputState(),i.isFocused||i.focus()}}setActiveItemClass(e){const t=this,i=t.control.querySelector(".last-active");i&&M(i,"last-active"),k(e,"active last-active"),t.trigger("item_select",e),t.activeItems.indexOf(e)==-1&&t.activeItems.push(e)}removeActiveItem(e){var t=this.activeItems.indexOf(e);this.activeItems.splice(t,1),M(e,"active")}clearActiveItems(){M(this.activeItems,"active"),this.activeItems=[]}setActiveOption(e,t=!0){e!==this.activeOption&&(this.clearActiveOption(),e&&(this.activeOption=e,S(this.focus_node,{"aria-activedescendant":e.getAttribute("id")}),S(e,{"aria-selected":"true"}),k(e,"active"),t&&this.scrollToOption(e)))}scrollToOption(e,t){if(!e)return;const i=this.dropdown_content,s=i.clientHeight,o=i.scrollTop||0,r=e.offsetHeight,l=e.getBoundingClientRect().top-i.getBoundingClientRect().top+o;l+r>s+o?this.scroll(l-s+r,t):l{e.setActiveItemClass(i)}))}inputState(){var e=this;e.control.contains(e.control_input)&&(S(e.control_input,{placeholder:e.settings.placeholder}),e.activeItems.length>0||!e.isFocused&&e.settings.hidePlaceholder&&e.items.length>0?(e.setTextboxValue(),e.isInputHidden=!0):(e.settings.hidePlaceholder&&e.items.length>0&&S(e.control_input,{placeholder:""}),e.isInputHidden=!1),e.wrapper.classList.toggle("input-hidden",e.isInputHidden))}inputValue(){return this.control_input.value.trim()}focus(){var e=this;e.isDisabled||e.isReadOnly||(e.ignoreFocus=!0,e.control_input.offsetWidth?e.control_input.focus():e.focus_node.focus(),setTimeout(()=>{e.ignoreFocus=!1,e.onFocus()},0))}blur(){this.focus_node.blur(),this.onBlur()}getScoreFunction(e){return this.sifter.getScoreFunction(e,this.getSearchOptions())}getSearchOptions(){var e=this.settings,t=e.sortField;return typeof e.sortField=="string"&&(t=[{field:e.sortField}]),{fields:e.searchField,conjunction:e.searchConjunction,sort:t,nesting:e.nesting}}search(e){var t,i,s=this,o=this.getSearchOptions();if(s.settings.score&&(i=s.settings.score.call(s,e),typeof i!="function"))throw new Error('Tom Select "score" setting must be a function that returns a function');return e!==s.lastQuery?(s.lastQuery=e,t=s.sifter.search(e,Object.assign(o,{score:i})),s.currentResults=t):t=Object.assign({},s.currentResults),s.settings.hideSelected&&(t.items=t.items.filter(r=>{let l=T(r.id);return!(l&&s.items.indexOf(l)!==-1)})),t}refreshOptions(e=!0){var t,i,s,o,r,l,c,a,d,u;const f={},w=[];var p=this,v=p.inputValue();const b=v===p.lastQuery||v==""&&p.lastQuery==null;var y=p.search(v),h=null,A=p.settings.shouldOpen||!1,D=p.dropdown_content;b&&(h=p.activeOption,h&&(d=h.closest("[data-group]"))),o=y.items.length,typeof p.settings.maxOptions=="number"&&(o=Math.min(o,p.settings.maxOptions)),o>0&&(A=!0);const te=(m,_)=>{let g=f[m];if(g!==void 0){let O=w[g];if(O!==void 0)return[g,O.fragment]}let I=document.createDocumentFragment();return g=w.length,w.push({fragment:I,order:_,optgroup:m}),[g,I]};for(t=0;t0&&(O=O.cloneNode(!0),S(O,{id:g.$id+"-clone-"+i,"aria-selected":null}),O.classList.add("ts-cloned"),M(O,"active"),p.activeOption&&p.activeOption.dataset.value==_&&d&&d.dataset.group===r.toString()&&(h=O)),An.appendChild(O),r!=""&&(f[r]=Sn)}}p.settings.lockOptgroupOrder&&w.sort((m,_)=>m.order-_.order),c=document.createDocumentFragment(),x(w,m=>{let _=m.fragment,g=m.optgroup;if(!_||!_.children.length)return;let I=p.optgroups[g];if(I!==void 0){let O=document.createDocumentFragment(),ne=p.render("optgroup_header",I);V(O,ne),V(O,_);let ie=p.render("optgroup",{group:I,options:O});V(c,ie)}else V(c,_)}),D.innerHTML="",V(D,c),p.settings.highlight&&(ut(D),y.query.length&&y.tokens.length&&x(y.tokens,m=>{ct(D,m.regex)}));var j=m=>{let _=p.render(m,{input:v});return _&&(A=!0,D.insertBefore(_,D.firstChild)),_};if(p.loading?j("loading"):p.settings.shouldLoad.call(p,v)?y.items.length===0&&j("no_results"):j("not_loading"),a=p.canCreate(v),a&&(u=j("option_create")),p.hasOptions=y.items.length>0||a,A){if(y.items.length>0){if(!h&&p.settings.mode==="single"&&p.items[0]!=null&&(h=p.getOption(p.items[0])),!D.contains(h)){let m=0;u&&!p.settings.addPrecedence&&(m=1),h=p.selectable()[m]}}else u&&(h=u);e&&!p.isOpen&&(p.open(),p.scrollToOption(h,"auto")),p.setActiveOption(h)}else p.clearActiveOption(),e&&p.isOpen&&p.close(!1)}selectable(){return this.dropdown_content.querySelectorAll("[data-selectable]")}addOption(e,t=!1){const i=this;if(Array.isArray(e))return i.addOptions(e,t),!1;const s=T(e[i.settings.valueField]);return s===null||i.options.hasOwnProperty(s)?!1:(e.$order=e.$order||++i.order,e.$id=i.inputId+"-opt-"+e.$order,i.options[s]=e,i.lastQuery=null,t&&(i.userOptions[s]=t,i.trigger("option_add",s,e)),s)}addOptions(e,t=!1){x(e,i=>{this.addOption(i,t)})}registerOption(e){return this.addOption(e)}registerOptionGroup(e){var t=T(e[this.settings.optgroupValueField]);return t===null?!1:(e.$order=e.$order||++this.order,this.optgroups[t]=e,t)}addOptionGroup(e,t){var i;t[this.settings.optgroupValueField]=e,(i=this.registerOptionGroup(t))&&this.trigger("optgroup_add",i,t)}removeOptionGroup(e){this.optgroups.hasOwnProperty(e)&&(delete this.optgroups[e],this.clearCache(),this.trigger("optgroup_remove",e))}clearOptionGroups(){this.optgroups={},this.clearCache(),this.trigger("optgroup_clear")}updateOption(e,t){const i=this;var s,o;const r=T(e),l=T(t[i.settings.valueField]);if(r===null)return;const c=i.options[r];if(c==null)return;if(typeof l!="string")throw new Error("Value must be set in option data");const a=i.getOption(r),d=i.getItem(r);if(t.$order=t.$order||c.$order,delete i.options[r],i.uncacheValue(l),i.options[l]=t,a){if(i.dropdown_content.contains(a)){const u=i._render("option",t);de(a,u),i.activeOption===a&&i.setActiveOption(u)}a.remove()}d&&(o=i.items.indexOf(r),o!==-1&&i.items.splice(o,1,l),s=i._render("item",t),d.classList.contains("active")&&k(s,"active"),de(d,s)),i.lastQuery=null}removeOption(e,t){const i=this;e=W(e),i.uncacheValue(e),delete i.userOptions[e],delete i.options[e],i.lastQuery=null,i.trigger("option_remove",e),i.removeItem(e,t)}clearOptions(e){const t=(e||this.clearFilter).bind(this);this.loadedSearches={},this.userOptions={},this.clearCache();const i={};x(this.options,(s,o)=>{t(s,o)&&(i[o]=s)}),this.options=this.sifter.items=i,this.lastQuery=null,this.trigger("option_clear")}clearFilter(e,t){return this.items.indexOf(t)>=0}getOption(e,t=!1){const i=T(e);if(i===null)return null;const s=this.options[i];if(s!=null){if(s.$div)return s.$div;if(t)return this._render("option",s)}return null}getAdjacent(e,t,i="option"){var s=this,o;if(!e)return null;i=="item"?o=s.controlChildren():o=s.dropdown_content.querySelectorAll("[data-selectable]");for(let r=0;r0?o[r+1]:o[r-1];return null}getItem(e){if(typeof e=="object")return e;var t=T(e);return t!==null?this.control.querySelector(`[data-value="${Oe(t)}"]`):null}addItems(e,t){var i=this,s=Array.isArray(e)?e:[e];s=s.filter(r=>i.items.indexOf(r)===-1);const o=s[s.length-1];s.forEach(r=>{i.isPending=r!==o,i.addItem(r,t)})}addItem(e,t){var i=t?[]:["change","dropdown_close"];ye(this,i,()=>{var s,o;const r=this,l=r.settings.mode,c=T(e);if(!(c&&r.items.indexOf(c)!==-1&&(l==="single"&&r.close(),l==="single"||!r.settings.duplicates))&&!(c===null||!r.options.hasOwnProperty(c))&&(l==="single"&&r.clear(t),!(l==="multi"&&r.isFull()))){if(s=r._render("item",r.options[c]),r.control.contains(s)&&(s=s.cloneNode(!0)),o=r.isFull(),r.items.splice(r.caretPos,0,c),r.insertAtCaret(s),r.isSetup){if(!r.isPending&&r.settings.hideSelected){let a=r.getOption(c),d=r.getAdjacent(a,1);d&&r.setActiveOption(d)}!r.isPending&&!r.settings.closeAfterSelect&&r.refreshOptions(r.isFocused&&l!=="single"),r.settings.closeAfterSelect!=!1&&r.isFull()?r.close():r.isPending||r.positionDropdown(),r.trigger("item_add",c,s),r.isPending||r.updateOriginalInput({silent:t})}(!r.isPending||!o&&r.isFull())&&(r.inputState(),r.refreshState())}})}removeItem(e=null,t){const i=this;if(e=i.getItem(e),!e)return;var s,o;const r=e.dataset.value;s=Ce(e),e.remove(),e.classList.contains("active")&&(o=i.activeItems.indexOf(e),i.activeItems.splice(o,1),M(e,"active")),i.items.splice(s,1),i.lastQuery=null,!i.settings.persist&&i.userOptions.hasOwnProperty(r)&&i.removeOption(r,t),s{}){arguments.length===3&&(t=arguments[2]),typeof t!="function"&&(t=()=>{});var i=this,s=i.caretPos,o;if(e=e||i.inputValue(),!i.canCreate(e))return t(),!1;i.lock();var r=!1,l=c=>{if(i.unlock(),!c||typeof c!="object")return t();var a=T(c[i.settings.valueField]);if(typeof a!="string")return t();i.setTextboxValue(),i.addOption(c,!0),i.setCaret(s),i.addItem(a),t(c),r=!0};return typeof i.settings.create=="function"?o=i.settings.create.call(this,e,l):o={[i.settings.labelField]:e,[i.settings.valueField]:e},r||l(o),!0}refreshItems(){var e=this;e.lastQuery=null,e.isSetup&&e.addItems(e.items),e.updateOriginalInput(),e.refreshState()}refreshState(){const e=this;e.refreshValidityState();const t=e.isFull(),i=e.isLocked;e.wrapper.classList.toggle("rtl",e.rtl);const s=e.wrapper.classList;s.toggle("focus",e.isFocused),s.toggle("disabled",e.isDisabled),s.toggle("readonly",e.isReadOnly),s.toggle("required",e.isRequired),s.toggle("invalid",!e.isValid),s.toggle("locked",i),s.toggle("full",t),s.toggle("input-active",e.isFocused&&!e.isInputHidden),s.toggle("dropdown-active",e.isOpen),s.toggle("has-options",at(e.options)),s.toggle("has-items",e.items.length>0)}refreshValidityState(){var e=this;e.input.validity&&(e.isValid=e.input.validity.valid,e.isInvalid=!e.isValid)}isFull(){return this.settings.maxItems!==null&&this.items.length>=this.settings.maxItems}updateOriginalInput(e={}){const t=this;var i,s;const o=t.input.querySelector('option[value=""]');if(t.is_select_tag){let c=function(a,d,u){return a||(a=P('")),a!=o&&t.input.append(a),r.push(a),(a!=o||l>0)&&(a.selected=!0),a};const r=[],l=t.input.querySelectorAll("option:checked").length;t.input.querySelectorAll("option:checked").forEach(a=>{a.selected=!1}),t.items.length==0&&t.settings.mode=="single"?c(o,"",""):t.items.forEach(a=>{if(i=t.options[a],s=i[t.settings.labelField]||"",r.includes(i.$option)){const d=t.input.querySelector(`option[value="${Oe(a)}"]:not(:checked)`);c(d,a,s)}else i.$option=c(i.$option,a,s)})}else t.input.value=t.getValue();t.isSetup&&(e.silent||t.trigger("change",t.getValue()))}open(){var e=this;e.isLocked||e.isOpen||e.settings.mode==="multi"&&e.isFull()||(e.isOpen=!0,S(e.focus_node,{"aria-expanded":"true"}),e.refreshState(),J(e.dropdown,{visibility:"hidden",display:"block"}),e.positionDropdown(),J(e.dropdown,{visibility:"visible",display:"block"}),e.focus(),e.trigger("dropdown_open",e.dropdown))}close(e=!0){var t=this,i=t.isOpen;e&&(t.setTextboxValue(),t.settings.mode==="single"&&t.items.length&&t.inputState()),t.isOpen=!1,S(t.focus_node,{"aria-expanded":"false"}),J(t.dropdown,{display:"none"}),t.settings.hideSelected&&t.clearActiveOption(),t.refreshState(),i&&t.trigger("dropdown_close",t.dropdown)}positionDropdown(){if(this.settings.dropdownParent==="body"){var e=this.control,t=e.getBoundingClientRect(),i=e.offsetHeight+t.top+window.scrollY,s=t.left+window.scrollX;J(this.dropdown,{width:t.width+"px",top:i+"px",left:s+"px"})}}clear(e){var t=this;if(t.items.length){var i=t.controlChildren();x(i,s=>{t.removeItem(s,!0)}),t.inputState(),e||t.updateOriginalInput(),t.trigger("clear")}}insertAtCaret(e){const t=this,i=t.caretPos,s=t.control;s.insertBefore(e,s.children[i]||null),t.setCaret(i+1)}deleteSelection(e){var t,i,s,o,r=this;t=e&&e.keyCode===xe?-1:1,i=ot(r.control_input);const l=[];if(r.activeItems.length)o=Ae(r.activeItems,t),s=Ce(o),t>0&&s++,x(r.activeItems,c=>l.push(c));else if((r.isFocused||r.settings.mode==="single")&&r.items.length){const c=r.controlChildren();let a;t<0&&i.start===0&&i.length===0?a=c[r.caretPos-1]:t>0&&i.start===r.inputValue().length&&(a=c[r.caretPos]),a!==void 0&&l.push(a)}if(!r.shouldDelete(l,e))return!1;for(C(e,!0),typeof s!="undefined"&&r.setCaret(s);l.length;)r.removeItem(l.pop());return r.inputState(),r.positionDropdown(),r.refreshOptions(!1),!0}shouldDelete(e,t){const i=e.map(s=>s.dataset.value);return!(!i.length||typeof this.settings.onDelete=="function"&&this.settings.onDelete(i,t)===!1)}advanceSelection(e,t){var i,s,o=this;o.rtl&&(e*=-1),!o.inputValue().length&&(N(X,t)||N("shiftKey",t)?(i=o.getLastActive(e),i?i.classList.contains("active")?s=o.getAdjacent(i,e,"item"):s=i:e>0?s=o.control_input.nextElementSibling:s=o.control_input.previousElementSibling,s&&(s.classList.contains("active")&&o.removeActiveItem(i),o.setActiveItemClass(s))):o.moveCaret(e))}moveCaret(e){}getLastActive(e){let t=this.control.querySelector(".last-active");if(t)return t;var i=this.control.querySelectorAll(".active");if(i)return Ae(i,e)}setCaret(e){this.caretPos=this.items.length}controlChildren(){return Array.from(this.control.querySelectorAll("[data-ts-item]"))}lock(){this.setLocked(!0)}unlock(){this.setLocked(!1)}setLocked(e=this.isReadOnly||this.isDisabled){this.isLocked=e,this.refreshState()}disable(){this.setDisabled(!0),this.close()}enable(){this.setDisabled(!1)}setDisabled(e){this.focus_node.tabIndex=e?-1:this.tabIndex,this.isDisabled=e,this.input.disabled=e,this.control_input.disabled=e,this.setLocked()}setReadOnly(e){this.isReadOnly=e,this.input.readOnly=e,this.control_input.readOnly=e,this.setLocked()}destroy(){var e=this,t=e.revertSettings;e.trigger("destroy"),e.off(),e.wrapper.remove(),e.dropdown.remove(),e.input.innerHTML=t.innerHTML,e.input.tabIndex=t.tabIndex,M(e.input,"tomselected","ts-hidden-accessible"),e._destroy(),delete e.input.tomselect}render(e,t){var i,s;const o=this;if(typeof this.settings.render[e]!="function"||(s=o.settings.render[e].call(this,t,le),!s))return null;if(s=P(s),e==="option"||e==="option_create"?t[o.settings.disabledField]?S(s,{"aria-disabled":"true"}):S(s,{"data-selectable":""}):e==="optgroup"&&(i=t.group[o.settings.optgroupValueField],S(s,{"data-group":i}),t.group[o.settings.disabledField]&&S(s,{"data-disabled":""})),e==="option"||e==="item"){const r=W(t[o.settings.valueField]);S(s,{"data-value":r}),e==="item"?(k(s,o.settings.itemClass),S(s,{"data-ts-item":""})):(k(s,o.settings.optionClass),S(s,{role:"option",id:t.$id}),t.$div=s,o.options[r]=t)}return s}_render(e,t){const i=this.render(e,t);if(i==null)throw"HTMLElement expected";return i}clearCache(){x(this.options,e=>{e.$div&&(e.$div.remove(),delete e.$div)})}uncacheValue(e){const t=this.getOption(e);t&&t.remove()}canCreate(e){return this.settings.create&&e.length>0&&this.settings.createFilter.call(this,e)}hook(e,t,i){var s=this,o=s[t];s[t]=function(){var r,l;return e==="after"&&(r=o.apply(s,arguments)),l=i.apply(s,arguments),e==="instead"?l:(e==="before"&&(r=o.apply(s,arguments)),r)}}}const yt=(n,e,t,i)=>{n.addEventListener(e,t,i)};function Ot(){yt(this.input,"change",()=>{this.sync()})}const wt=n=>typeof n=="undefined"||n===null?null:bt(n),bt=n=>typeof n=="boolean"?n?"1":"0":n+"",Fe=(n,e=!1)=>{n&&(n.preventDefault(),e&&n.stopPropagation())},St=n=>{if(n.jquery)return n[0];if(n instanceof HTMLElement)return n;if(At(n)){var e=document.createElement("template");return e.innerHTML=n.trim(),e.content.firstChild}return document.querySelector(n)},At=n=>typeof n=="string"&&n.indexOf("<")>-1;function Ct(n){var e=this,t=e.onOptionSelect;e.settings.hideSelected=!1;const i=Object.assign({className:"tomselect-checkbox",checkedClassNames:void 0,uncheckedClassNames:void 0},n);var s=function(l,c){c?(l.checked=!0,i.uncheckedClassNames&&l.classList.remove(...i.uncheckedClassNames),i.checkedClassNames&&l.classList.add(...i.checkedClassNames)):(l.checked=!1,i.checkedClassNames&&l.classList.remove(...i.checkedClassNames),i.uncheckedClassNames&&l.classList.add(...i.uncheckedClassNames))},o=function(l){setTimeout(()=>{var c=l.querySelector("input."+i.className);c instanceof HTMLInputElement&&s(c,l.classList.contains("selected"))},1)};e.hook("after","setupTemplates",()=>{var r=e.settings.render.option;e.settings.render.option=(l,c)=>{var a=St(r.call(e,l,c)),d=document.createElement("input");i.className&&d.classList.add(i.className),d.addEventListener("click",function(f){Fe(f)}),d.type="checkbox";const u=wt(l[e.settings.valueField]);return s(d,!!(u&&e.items.indexOf(u)>-1)),a.prepend(d),a}}),e.on("item_remove",r=>{var l=e.getOption(r);l&&(l.classList.remove("selected"),o(l))}),e.on("item_add",r=>{var l=e.getOption(r);l&&o(l)}),e.hook("instead","onOptionSelect",(r,l)=>{if(l.classList.contains("selected")){l.classList.remove("selected"),e.removeItem(l.dataset.value),e.refreshOptions(),Fe(r,!0);return}t.call(e,r,l),o(l)})}const xt=n=>{if(n.jquery)return n[0];if(n instanceof HTMLElement)return n;if(It(n)){var e=document.createElement("template");return e.innerHTML=n.trim(),e.content.firstChild}return document.querySelector(n)},It=n=>typeof n=="string"&&n.indexOf("<")>-1;function Lt(n){const e=this,t=Object.assign({className:"clear-button",title:"Clear All",html:i=>`
`},n);e.on("initialize",()=>{var i=xt(t.html(t));i.addEventListener("click",s=>{e.isLocked||(e.clear(),e.settings.mode==="single"&&e.settings.allowEmptyOption&&e.addItem(""),s.preventDefault(),s.stopPropagation())}),e.control.appendChild(i)})}const Et=(n,e=!1)=>{n&&(n.preventDefault(),e&&n.stopPropagation())},K=(n,e,t,i)=>{n.addEventListener(e,t,i)},kt=(n,e)=>{if(Array.isArray(n))n.forEach(e);else for(var t in n)n.hasOwnProperty(t)&&e(n[t],t)},Ft=n=>{if(n.jquery)return n[0];if(n instanceof HTMLElement)return n;if(Tt(n)){var e=document.createElement("template");return e.innerHTML=n.trim(),e.content.firstChild}return document.querySelector(n)},Tt=n=>typeof n=="string"&&n.indexOf("<")>-1,Pt=(n,e)=>{kt(e,(t,i)=>{t==null?n.removeAttribute(i):n.setAttribute(i,""+t)})},Dt=(n,e)=>{var t;(t=n.parentNode)==null||t.insertBefore(e,n.nextSibling)},$t=(n,e)=>{var t;(t=n.parentNode)==null||t.insertBefore(e,n)},Mt=(n,e)=>{do{var t;if(e=(t=e)==null?void 0:t.previousElementSibling,n==e)return!0}while(e&&e.previousElementSibling);return!1};function Ht(){var n=this;if(n.settings.mode!=="multi")return;var e=n.lock,t=n.unlock;let i=!0,s;n.hook("after","setupTemplates",()=>{var o=n.settings.render.item;n.settings.render.item=(r,l)=>{const c=Ft(o.call(n,r,l));Pt(c,{draggable:"true"});const a=v=>{i||Et(v),v.stopPropagation()},d=v=>{s=c,setTimeout(()=>{c.classList.add("ts-dragging")},0)},u=v=>{v.preventDefault(),c.classList.add("ts-drag-over"),w(c,s)},f=()=>{c.classList.remove("ts-drag-over")},w=(v,b)=>{b!==void 0&&(Mt(b,c)?Dt(v,b):$t(v,b))},p=()=>{var v;document.querySelectorAll(".ts-drag-over").forEach(y=>y.classList.remove("ts-drag-over")),(v=s)==null||v.classList.remove("ts-dragging"),s=void 0;var b=[];n.control.querySelectorAll("[data-value]").forEach(y=>{if(y.dataset.value){let h=y.dataset.value;h&&b.push(h)}}),n.setValue(b)};return K(c,"mousedown",a),K(c,"dragstart",d),K(c,"dragenter",u),K(c,"dragover",u),K(c,"dragleave",f),K(c,"dragend",p),c}}),n.hook("instead","lock",()=>(i=!1,e.call(n))),n.hook("instead","unlock",()=>(i=!0,t.call(n)))}const Nt=(n,e=!1)=>{n&&(n.preventDefault(),e&&n.stopPropagation())},Vt=n=>{if(n.jquery)return n[0];if(n instanceof HTMLElement)return n;if(Rt(n)){var e=document.createElement("template");return e.innerHTML=n.trim(),e.content.firstChild}return document.querySelector(n)},Rt=n=>typeof n=="string"&&n.indexOf("<")>-1;function Bt(n){const e=this,t=Object.assign({title:"Untitled",headerClass:"dropdown-header",titleRowClass:"dropdown-header-title",labelClass:"dropdown-header-label",closeClass:"dropdown-header-close",html:i=>'
'+i.title+'×
'},n);e.on("initialize",()=>{var i=Vt(t.html(t)),s=i.querySelector("."+t.closeClass);s&&s.addEventListener("click",o=>{Nt(o,!0),e.close()}),e.dropdown.insertBefore(i,e.dropdown.firstChild)})}const Kt=(n,e)=>{if(Array.isArray(n))n.forEach(e);else for(var t in n)n.hasOwnProperty(t)&&e(n[t],t)},jt=(n,...e)=>{var t=zt(e);n=Yt(n),n.map(i=>{t.map(s=>{i.classList.remove(s)})})},zt=n=>{var e=[];return Kt(n,t=>{typeof t=="string"&&(t=t.trim().split(/[\t\n\f\r\s]/)),Array.isArray(t)&&(e=e.concat(t))}),e.filter(Boolean)},Yt=n=>(Array.isArray(n)||(n=[n]),n),Ut=(n,e)=>{if(!n)return-1;e=e||n.nodeName;for(var t=0;n=n.previousElementSibling;)n.matches(e)&&t++;return t};function Qt(){var n=this;n.hook("instead","setCaret",e=>{n.settings.mode==="single"||!n.control.contains(n.control_input)?e=n.items.length:(e=Math.max(0,Math.min(n.items.length,e)),e!=n.caretPos&&!n.isPending&&n.controlChildren().forEach((t,i)=>{i{if(!n.isFocused)return;const t=n.getLastActive(e);if(t){const i=Ut(t);n.setCaret(e>0?i+1:i),n.setActiveItem(),jt(t,"last-active")}else n.setCaret(n.caretPos+e)})}const Gt=27,qt=9,Wt=(n,e=!1)=>{n&&(n.preventDefault(),e&&n.stopPropagation())},Jt=(n,e,t,i)=>{n.addEventListener(e,t,i)},Xt=(n,e)=>{if(Array.isArray(n))n.forEach(e);else for(var t in n)n.hasOwnProperty(t)&&e(n[t],t)},Te=n=>{if(n.jquery)return n[0];if(n instanceof HTMLElement)return n;if(Zt(n)){var e=document.createElement("template");return e.innerHTML=n.trim(),e.content.firstChild}return document.querySelector(n)},Zt=n=>typeof n=="string"&&n.indexOf("<")>-1,en=(n,...e)=>{var t=tn(e);n=nn(n),n.map(i=>{t.map(s=>{i.classList.add(s)})})},tn=n=>{var e=[];return Xt(n,t=>{typeof t=="string"&&(t=t.trim().split(/[\t\n\f\r\s]/)),Array.isArray(t)&&(e=e.concat(t))}),e.filter(Boolean)},nn=n=>(Array.isArray(n)||(n=[n]),n);function sn(){const n=this;n.settings.shouldOpen=!0,n.hook("before","setup",()=>{n.focus_node=n.control,en(n.control_input,"dropdown-input");const e=Te(' +{hook h='displayProductActions' product=$product}