├── .gitignore ├── Action └── UpsertQuantity.php ├── CHANGELOG.md ├── Controller └── UpsertQty │ └── Index.php ├── LICENSE ├── Makefile.dist ├── Model ├── CheckoutConfigProvider.php ├── Config.php └── System │ └── Config │ ├── Brand.php │ ├── BrandProvider.php │ └── Source │ └── PaymentMethod.php ├── Plugin ├── AddAuthorToAdminOrderStatusHistory.php ├── AddCreatedByBeforeOrderStatusHistorySave.php ├── AddUpsertQtyScriptToProductDetails.php ├── BrandAdminhtmlConfigField.php ├── CustomizeCheckoutLayout.php ├── LoginAsCustomer │ ├── CustomizeAdminActionAllowance.php │ ├── CustomizeAdminhtmlCheckboxRendering.php │ └── CustomizeFrontendCheckboxRendering.php ├── ProductCompare │ ├── CustomizeButtonRenderingOnProductList.php │ ├── CustomizeButtonRenderingOnProductView.php │ ├── CustomizeHelper.php │ ├── CustomizeSidebarRendering.php │ └── ProceedPreventer.php ├── RemoveAdminOnlyPaymentMethodFromCheckout.php └── SwitchNewsletterNewActionToAjax.php ├── README.md ├── ViewModel ├── Customer │ └── Address │ │ └── Form │ │ └── InputMask.php └── UpsertQtyInitializer.php ├── composer.json ├── etc ├── adminhtml │ ├── di.xml │ └── system.xml ├── config.xml ├── db_schema.xml ├── di.xml ├── frontend │ ├── di.xml │ └── routes.xml └── module.xml ├── i18n ├── en_US.csv └── pl_PL.csv ├── registration.php └── view ├── adminhtml └── web │ └── images │ └── lingaro_logo.png ├── base ├── requirejs-config.js └── web │ └── js │ ├── form │ └── element │ │ └── abstract-mixin.js │ ├── input-mask.js │ ├── mage │ └── validation-mixin.js │ ├── validation-adder.js │ ├── validation │ └── input-mask.js │ └── vendor │ └── jquery.inputmask.js └── frontend ├── layout ├── catalog_product_compare_index.xml ├── catalog_product_view.xml ├── customer_account.xml ├── customer_address_form.xml └── default.xml ├── requirejs-config.js ├── templates ├── customer │ └── address │ │ └── form │ │ └── input_mask.phtml ├── newsletter │ └── script.phtml └── upsert_qty │ ├── compare.phtml │ ├── list.phtml │ └── view.phtml └── web ├── css └── source │ └── _module.less └── js ├── checkout └── view │ └── summary │ └── cart-items-mixin.js ├── newsletter └── ajax.js └── upsert-qty ├── widget-initializer.js └── widget.js /.gitignore: -------------------------------------------------------------------------------- 1 | Makefile -------------------------------------------------------------------------------- /Action/UpsertQuantity.php: -------------------------------------------------------------------------------- 1 | cart = $cart; 53 | $this->escaper = $escaper; 54 | $this->logger = $logger; 55 | $this->request = $request; 56 | $this->productRepository = $productRepository; 57 | $this->storeManager = $storeManager; 58 | $this->resolver = $resolver; 59 | } 60 | 61 | /** 62 | * @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingTraversableTypeHintSpecification 63 | */ 64 | public function execute(): array 65 | { 66 | $result = ['success' => false]; 67 | $params = $this->request->getParams(); 68 | 69 | if (!isset($params['qty']) && 70 | strlen($params['qty']) > 0 && 71 | preg_match('/[^,.0-9]/', $params['qty'])) { 72 | return array_merge($result, [ 73 | 'message' => __('Invalid value provided.') 74 | ]); 75 | } 76 | 77 | $params = ['qty' => $this->normalize($params['qty'])]; 78 | 79 | if ($params['qty'] < 0) { 80 | return array_merge($result, [ 81 | 'message' => __('Invalid value provided.') 82 | ]); 83 | } 84 | 85 | $product = $this->initProduct(); 86 | if (!$product) { 87 | return array_merge($result, [ 88 | 'message' => __('Invalid product provided.') 89 | ]); 90 | } 91 | 92 | try { 93 | if (in_array($product->getId(), $this->cart->getProductIds())) { 94 | $this->cart->updateItems([ 95 | $this->cart->getQuote()->getItemByProduct($product)->getId() => $params 96 | ])->save(); 97 | $result['success'] = true; 98 | return $result; 99 | } 100 | $this->addToCart($product, $params); 101 | $result['success'] = true; 102 | return $result; 103 | } catch (LocalizedException $e) { 104 | $result['message'] = $this->escaper->escapeHtml($e->getMessage()); 105 | } catch (Exception $e) { 106 | $result['message'] = __('We cannot add or change this item in your shopping cart right now.'); 107 | $this->logger->critical($e); 108 | } 109 | 110 | return $result; 111 | } 112 | 113 | private function initProduct():? Product 114 | { 115 | $productId = (int) $this->request->getParam('product'); 116 | if ($productId) { 117 | try { 118 | /** @var Product $product */ 119 | $product = $this->productRepository->getById( 120 | $productId, 121 | false, 122 | $this->storeManager->getStore()->getId() 123 | ); 124 | if (in_array($product->getTypeId(), self::ALLOWED_PRODUCT_TYPES)) { 125 | return $product; 126 | } 127 | } catch (NoSuchEntityException $e) { 128 | return null; 129 | } 130 | } 131 | return null; 132 | } 133 | 134 | /** 135 | * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingTraversableTypeHintSpecification 136 | * @throws LocalizedException 137 | */ 138 | private function addToCart(Product $product, array $params): void 139 | { 140 | $this->cart->addProduct($product, $params); 141 | $this->cart->save(); 142 | } 143 | 144 | private function normalize(string $itemQty): string 145 | { 146 | return (new Zend_Filter_LocalizedToNormalized(['locale' => $this->resolver->getLocale()]))->filter($itemQty); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [2.1.0] - 2023-06-01 8 | ### Changed 9 | - Added support for PHP 8.1 10 | 11 | ## [2.0.0] - 2023-05-17 12 | ### Changed 13 | - Rebranded the module to Lingaro with new major version released 14 | - Changed the css class from orba to lingaro. 15 | - Changed the branding logo from Orba to Lingaro 16 | 17 | ## [1.1.0] - 2022-02-08 18 | ### Added 19 | - Feature: Config for enabling "Upsert quantity" widget in place of "Add to cart" buttons [ZZ12-89](https://orba.atlassian.net/browse/ZZ12-89) 20 | - Feature: Config for enabling AJAX for Footer Newsletter form [ZZ12-24](https://orba.atlassian.net/browse/ZZ12-24) 21 | 22 | ## [1.0.1] - 2021-12-06 23 | ### Changed 24 | - Minor code polishing to fulfil OrbaMagento2 coding standards [ZZ12-87](https://orba.atlassian.net/browse/ZZ12-87) 25 | 26 | ## [1.0.0] - 2021-07-15 27 | ### Added 28 | - Feature: Admin name added to order comment [ZZ12-2](https://orba.atlassian.net/browse/ZZ12-2) 29 | - Feature: Config for defining admin only payment methods [ZZ12-3](https://orba.atlassian.net/browse/ZZ12-3) 30 | - Feature: Possibility to brand system.xml config fields [ZZ12-9](https://orba.atlassian.net/browse/ZZ12-9) 31 | - Feature: Config for hiding Downloadable Products section on Customer Account page [ZZ12-4](https://orba.atlassian.net/browse/ZZ12-4) 32 | - Feature: Config for disabling necessity of opt-in to Login as Customer feature [ZZ12-6](https://orba.atlassian.net/browse/ZZ12-6) 33 | - Feature: Config for always expanding cart items block in Checkout [ZZ12-7](https://orba.atlassian.net/browse/ZZ12-7) 34 | - Feature: Config for disabling Product Comparison [ZZ12-5](https://orba.atlassian.net/browse/ZZ12-5) 35 | - Feature: Config for defining input masks for Customer phone number and postcode [ZZ12-8](https://orba.atlassian.net/browse/ZZ12-8) 36 | -------------------------------------------------------------------------------- /Controller/UpsertQty/Index.php: -------------------------------------------------------------------------------- 1 | formKeyValidator = $formKeyValidator; 33 | $this->upsertQuantity = $upsertQuantity; 34 | } 35 | 36 | /** 37 | * @return ResponseInterface|ResultInterface 38 | */ 39 | public function execute() 40 | { 41 | return $this->getRequest()->isAjax() 42 | ? $this->resultFactory->create(ResultFactory::TYPE_JSON)->setData($this->upsertQuantity->execute()) 43 | : $this->resultFactory->create(ResultFactory::TYPE_REDIRECT)->setPath('/'); 44 | } 45 | 46 | /** 47 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) 48 | */ 49 | public function createCsrfValidationException(RequestInterface $request): ?InvalidRequestException 50 | { 51 | $message = __('Invalid Form Key. Please refresh the page.'); 52 | return new InvalidRequestException($this->resultFactory->create(ResultFactory::TYPE_JSON)->setData([ 53 | 'success' => false, 54 | 'message' => $message 55 | ]), [$message]); 56 | } 57 | 58 | public function validateForCsrf(RequestInterface $request):? bool 59 | { 60 | return $this->formKeyValidator->validate($request); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright © 2023 Lingaro sp. z o.o. 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. 10 | -------------------------------------------------------------------------------- /Makefile.dist: -------------------------------------------------------------------------------- 1 | COMPONENT_NAME:=module-micro-features 2 | COMPONENT:=lingaro/magento2-module-micro-features 3 | ENV_REPOSITORY:= 4 | 5 | TARGET_PATH:=source/packages/$(COMPONENT_NAME) 6 | 7 | MKTEMP:=mktemp -d 8 | TMPDIR:=$(shell $(MKTEMP)) 9 | ARCHIVE:=$(TMPDIR)/archive.tar.gz 10 | CP:=cp -R 11 | RM:=rm -rf 12 | TAR:=tar 13 | GIT:=git 14 | MKDIR=mkdir -p 15 | 16 | .PHONY: all 17 | 18 | all: 19 | $(info Running) 20 | $(info $(TMPDIR)) 21 | 22 | $(TAR) -czvf $(ARCHIVE) . 23 | $(RM) ..?* .[!.]* * 24 | $(GIT) clone $(ENV_REPOSITORY) . 25 | $(MAKE) new \ 26 | project=$(COMPONENT_NAME) \ 27 | version=2.4.5-p1 \ 28 | edition=community \ 29 | static_cases=packages/$(COMPONENT_NAME) \ 30 | unit_cases=packages/$(COMPONENT_NAME)/Test/Unit \ 31 | integration_cases=packages/$(COMPONENT_NAME)/Test/Integration 32 | $(MKDIR) $(TARGET_PATH) 33 | $(TAR) -xzvf $(ARCHIVE) -C $(TARGET_PATH) 34 | $(RM) $(TMPDIR) 35 | $(MAKE) run cmd="composer\ require\ $(COMPONENT)" 36 | -------------------------------------------------------------------------------- /Model/CheckoutConfigProvider.php: -------------------------------------------------------------------------------- 1 | config = $config; 21 | } 22 | 23 | /** 24 | * @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint 25 | */ 26 | public function getConfig(): array 27 | { 28 | return [ 29 | 'lingaroMicroFeatures' => [ 30 | 'itemsBlockExpandedByDefault' => $this->config->shouldAlwaysExpandItemsBlockOnCheckout() 31 | ] 32 | ]; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Model/Config.php: -------------------------------------------------------------------------------- 1 | scopeConfig = $scopeConfig; 33 | } 34 | 35 | /** 36 | * @return string[] 37 | */ 38 | public function getAdminOnlyPaymentMethods(): array 39 | { 40 | $value = $this->scopeConfig->getValue(static::XML_PATH_PAYMENT_ACCOUNT_ADMIN_ONLY); 41 | if (empty($value)) { 42 | return []; 43 | } 44 | return explode(',', $value); 45 | } 46 | 47 | public function isShoppingAssistanceCheckboxNeeded(): bool 48 | { 49 | return $this->scopeConfig->isSetFlag( 50 | static::XML_PATH_LOGIN_AS_CUSTOMER_GENERAL_SHOPPING_ASSISTANCE_CHECKBOX_NEEDED 51 | ); 52 | } 53 | 54 | /** 55 | * @param null|int|string $storeId 56 | */ 57 | public function shouldAlwaysExpandItemsBlockOnCheckout($storeId = null): bool 58 | { 59 | return $this->scopeConfig->isSetFlag( 60 | static::XML_PATH_CHECKOUT_OPTIONS_ALWAYS_EXPAND_ITEMS_BLOCK, 61 | ScopeInterface::SCOPE_STORE, 62 | $storeId 63 | ); 64 | } 65 | 66 | /** 67 | * @param null|int|string $storeId 68 | */ 69 | public function isProductComparisonEnabled($storeId = null): bool 70 | { 71 | return $this->scopeConfig->isSetFlag( 72 | static::XML_PATH_CATALOG_FRONTEND_ENABLE_COMPARISON, 73 | ScopeInterface::SCOPE_STORE, 74 | $storeId 75 | ); 76 | } 77 | 78 | /** 79 | * @param null|int|string $storeId 80 | */ 81 | public function isUpsertQtyEnabled($storeId = null): bool 82 | { 83 | return $this->scopeConfig->isSetFlag( 84 | static::XML_PATH_CATALOG_FRONTEND_ENABLE_UPSERT_QTY, 85 | ScopeInterface::SCOPE_STORE, 86 | $storeId 87 | ); 88 | } 89 | 90 | /** 91 | * @param null|int|string $storeId 92 | */ 93 | public function getTelephoneInputMask($storeId = null): string 94 | { 95 | return (string) $this->scopeConfig->getValue( 96 | static::XML_PATH_CUSTOMER_ADDRESS_TELEPHONE_INPUT_MASK, 97 | ScopeInterface::SCOPE_STORE, 98 | $storeId 99 | ); 100 | } 101 | 102 | /** 103 | * @param null|int|string $storeId 104 | */ 105 | public function getPostcodeInputMask($storeId = null): string 106 | { 107 | return (string) $this->scopeConfig->getValue( 108 | static::XML_PATH_CUSTOMER_ADDRESS_POSTCODE_INPUT_MASK, 109 | ScopeInterface::SCOPE_STORE, 110 | $storeId 111 | ); 112 | } 113 | 114 | /** 115 | * @param null|int|string $storeId 116 | */ 117 | public function isAjaxNewsletterEnabled($storeId = null): bool 118 | { 119 | return $this->scopeConfig->isSetFlag( 120 | static::XML_PATH_NEWSLETTER_GENERAL_ENABLE_AJAX, 121 | ScopeInterface::SCOPE_STORE, 122 | $storeId 123 | ); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /Model/System/Config/Brand.php: -------------------------------------------------------------------------------- 1 | logo = $logo; 19 | } 20 | 21 | public function getLogo(): string 22 | { 23 | return $this->logo; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Model/System/Config/BrandProvider.php: -------------------------------------------------------------------------------- 1 | brands = $brands; 30 | } 31 | 32 | public function get(string $code): Brand 33 | { 34 | if (!array_key_exists($code, $this->brands)) { 35 | throw new InvalidArgumentException('Brand "' . $code . '" is not defined.'); 36 | } 37 | return $this->brands[$code]; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Model/System/Config/Source/PaymentMethod.php: -------------------------------------------------------------------------------- 1 | paymentHelper = $paymentHelper; 22 | } 23 | 24 | /** 25 | * Returns payment methods in the following format: [['value' => '', 'label' => '