Repository: boldcommerce/magento2-ordercomments Branch: master Commit: 2f0feb02b422 Files: 68 Total size: 78.1 KB Directory structure: gitextract_gvlihgyr/ ├── Api/ │ ├── Data/ │ │ └── OrderCommentInterface.php │ ├── GuestOrderCommentManagementInterface.php │ └── OrderCommentManagementInterface.php ├── Block/ │ └── Order/ │ └── Comment.php ├── Controller/ │ └── Cart/ │ └── UpdateComment.php ├── LICENSE ├── Model/ │ ├── Config/ │ │ └── Source/ │ │ └── Collapse.php │ ├── Config.php │ ├── Data/ │ │ └── OrderComment.php │ ├── GuestOrderCommentManagement.php │ ├── OrderCommentConfigProvider.php │ └── OrderCommentManagement.php ├── Observer/ │ └── AddOrderCommentToOrder.php ├── Plugin/ │ ├── Block/ │ │ └── Adminhtml/ │ │ └── SalesOrderViewInfo.php │ └── Model/ │ └── Order/ │ └── LoadOrderComment.php ├── README.md ├── Setup/ │ ├── InstallData.php │ └── Uninstall.php ├── Test/ │ ├── Integration/ │ │ ├── Model/ │ │ │ ├── GuestOrderCommentManagementTest.php │ │ │ └── OrderCommentManagementTest.php │ │ └── Observer/ │ │ └── AddOrderCommentToOrderTest.php │ └── Unit/ │ ├── Model/ │ │ ├── GuestOrderCommentManagementTest.php │ │ └── OrderCommentManagementTest.php │ └── Observer/ │ └── AddOrderCommentToOrderTest.php ├── ViewModel/ │ └── Comment.php ├── composer.json ├── etc/ │ ├── adminhtml/ │ │ ├── di.xml │ │ └── system.xml │ ├── config.xml │ ├── di.xml │ ├── events.xml │ ├── extension_attributes.xml │ ├── frontend/ │ │ ├── di.xml │ │ └── routes.xml │ ├── module.xml │ └── webapi.xml ├── i18n/ │ ├── ar_SA.csv │ ├── bn_BD.csv │ ├── cs_CZ.csv │ ├── de_CH.csv │ ├── de_DE.csv │ ├── el_GR.csv │ ├── es_ES.csv │ ├── fr_FR.csv │ ├── he_IL.csv │ ├── hu_HU.csv │ ├── it_IT.csv │ ├── ja_JP.csv │ ├── nl_NL.csv │ ├── pl_PL.csv │ ├── sl_SI.csv │ ├── sv_SE.csv │ └── th_TH.csv ├── registration.php └── view/ ├── adminhtml/ │ ├── layout/ │ │ └── sales_order_view.xml │ ├── templates/ │ │ └── order/ │ │ └── view/ │ │ └── comments.phtml │ └── ui_component/ │ └── sales_order_grid.xml └── frontend/ ├── layout/ │ ├── checkout_cart_index.xml │ ├── checkout_index_index.xml │ └── sales_order_view.xml ├── templates/ │ ├── cart/ │ │ └── comment.phtml │ └── order/ │ └── view/ │ └── comment.phtml └── web/ ├── css/ │ └── source/ │ └── _module.less ├── js/ │ ├── model/ │ │ └── checkout/ │ │ └── order-comment-validator.js │ └── view/ │ └── checkout/ │ ├── order-comment-block.js │ └── order-comment-validator.js └── template/ └── checkout/ ├── form-content.html └── order-comment-block.html ================================================ FILE CONTENTS ================================================ ================================================ FILE: Api/Data/OrderCommentInterface.php ================================================ coreRegistry = $registry; $this->_isScopePrivate = true; $this->_template = 'order/view/comment.phtml'; parent::__construct($context, $data); } public function getOrder() : Order { return $this->coreRegistry->registry('current_order'); } public function getOrderComment(): string { return trim((string) $this->getOrder()->getData(OrderComment::COMMENT_FIELD_NAME)); } public function hasOrderComment() : bool { return strlen($this->getOrderComment()) > 0; } public function getOrderCommentHtml() : string { return nl2br($this->escapeHtml($this->getOrderComment())); } } ================================================ FILE: Controller/Cart/UpdateComment.php ================================================ logger = $logger; $this->orderCommentManagement = $orderCommentManagement; $this->orderCommentFactory = $orderCommentFactory; } /** * Saves the comment to the quote * * @return ResponseInterface|Redirect|ResultInterface */ public function execute() { try { $comment = trim($this->getRequest()->getParam('order_comment', '')); $cartQuote = $this->cart->getQuote(); $commentObj = $this->orderCommentFactory->create(); $commentObj->setComment($comment); $this->orderCommentManagement->saveOrderComment($cartQuote->getId(), $commentObj); $this->messageManager->addSuccessMessage( __( 'Your comment has been saved.' ) ); } catch (LocalizedException $e) { $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { $this->messageManager->addErrorMessage(__('There was an error when updating the quote.')); $this->logger->critical($e->getMessage(), ['exception' => $e->getTraceAsString()]); } return $this->_goBack(); } } ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2017 Bold Commerce BV 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. ================================================ FILE: Model/Config/Source/Collapse.php ================================================ toArray(); $result = []; foreach ($options as $value => $label) { $result[] = [ 'value' => $value, 'label' => $label ]; } return $result; } /** * Get options in "key-value" format * * @return array */ public function toArray() { return [ 0 => __('Starts with field closed'), 1 => __('Starts with field opened'), 2 => __('Render field without collapse') ]; } } ================================================ FILE: Model/Config.php ================================================ scopeConfig = $scopeConfig; } /** * @param mixed $website * @return bool */ public function canShowInCheckout($website = null): bool { return $this->scopeConfig->isSetFlag(self::XML_PATH_CONFIG_SHOW_IN_CHECKOUT, ScopeInterface::SCOPE_WEBSITE, $website); } /** * @param mixed $website * @return bool */ public function canShowInAccount($website = null): bool { return $this->scopeConfig->isSetFlag(self::XML_PATH_CONFIG_SHOW_IN_ACCOUNT, ScopeInterface::SCOPE_WEBSITE, $website); } /** * @param mixed $website * @return bool */ public function canShowInCart($website = null): bool { return $this->scopeConfig->isSetFlag(self::XML_PATH_CONFIG_SHOW_IN_CART, ScopeInterface::SCOPE_WEBSITE, $website); } /** * @param mixed $website * @return mixed */ public function getMaximumCharacterLength($website = null) { return $this->scopeConfig->getValue(self::XML_PATH_CONFIG_MAX_LENGTH, ScopeInterface::SCOPE_WEBSITE, $website); } /** * @param mixed $website * @return mixed */ public function getInitialCollapseState($website = null) { return $this->scopeConfig->getValue(self::XML_PATH_CONFIG_FIELD_COLLAPSE_STATE, ScopeInterface::SCOPE_WEBSITE, $website); } } ================================================ FILE: Model/Data/OrderComment.php ================================================ _get(static::COMMENT_FIELD_NAME); } /** * @param string $comment * @return $this */ public function setComment($comment) { return $this->setData(static::COMMENT_FIELD_NAME, $comment); } } ================================================ FILE: Model/GuestOrderCommentManagement.php ================================================ quoteIdMaskFactory = $quoteIdMaskFactory; $this->orderCommentManagement = $orderCommentManagement; } /** * {@inheritDoc} */ public function saveOrderComment( $cartId, \Bold\OrderComment\Api\Data\OrderCommentInterface $orderComment ) { $quoteIdMask = $this->quoteIdMaskFactory->create()->load($cartId, 'masked_id'); return $this->orderCommentManagement->saveOrderComment($quoteIdMask->getQuoteId(), $orderComment); } } ================================================ FILE: Model/OrderCommentConfigProvider.php ================================================ scopeConfig = $scopeConfig; $this->checkoutSession = $checkoutSession; } /** * Prepare data for use in checkout javascript component * * @return array * @throws \Magento\Framework\Exception\LocalizedException * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function getConfig() { $comment = ''; if ($this->checkoutSession->getQuoteId()) { $comment = $this->checkoutSession->getQuote()->getData(OrderComment::COMMENT_FIELD_NAME) ?: ''; } return [ 'show_in_checkout' => $this->scopeConfig->isSetFlag(self::CONFIG_SHOW_IN_CHECKOUT, ScopeInterface::SCOPE_WEBSITE), 'max_length' => (int) $this->scopeConfig->getValue(self::CONFIG_MAX_LENGTH, ScopeInterface::SCOPE_WEBSITE), 'comment_initial_collapse_state' => (int) $this->scopeConfig->getValue(self::CONFIG_FIELD_COLLAPSE_STATE, ScopeInterface::SCOPE_WEBSITE), 'existing_comment' => $comment ]; } } ================================================ FILE: Model/OrderCommentManagement.php ================================================ quoteRepository = $quoteRepository; $this->scopeConfig = $scopeConfig; } /** * @param int $cartId * @param \Bold\OrderComment\Api\Data\OrderCommentInterface $orderComment * @return null|string * @throws CouldNotSaveException * @throws NoSuchEntityException */ public function saveOrderComment( $cartId, \Bold\OrderComment\Api\Data\OrderCommentInterface $orderComment ) { $quote = $this->quoteRepository->getActive($cartId); if (!$quote->getItemsCount()) { throw new NoSuchEntityException(__('Cart %1 doesn\'t contain products', $cartId)); } $comment = $orderComment->getComment(); $this->validateComment($comment); try { $quote->setData(OrderComment::COMMENT_FIELD_NAME, strip_tags($comment)); $this->quoteRepository->save($quote); } catch (\Exception $e) { throw new CouldNotSaveException(__('The order comment could not be saved')); } return $comment; } /** * @param string $comment * @throws ValidatorException */ protected function validateComment($comment) { $maxLength = $this->scopeConfig->getValue(OrderCommentConfigProvider::CONFIG_MAX_LENGTH); if ($maxLength && (mb_strlen($comment) > $maxLength)) { throw new ValidatorException(__('Comment is too long')); } } } ================================================ FILE: Observer/AddOrderCommentToOrder.php ================================================ getEvent()->getOrder(); /** @var $quote \Magento\Quote\Model\Quote $quote */ $quote = $observer->getEvent()->getQuote(); $order->setData(OrderComment::COMMENT_FIELD_NAME, $quote->getData(OrderComment::COMMENT_FIELD_NAME)); } } ================================================ FILE: Plugin/Block/Adminhtml/SalesOrderViewInfo.php ================================================ getLayout()->getBlock('boldcommerce_order_comments'); if ($commentBlock !== false && $subject->getNameInLayout() == 'order_info') { $commentBlock->setOrderComment($subject->getOrder()->getData(OrderComment::COMMENT_FIELD_NAME)); $result = $result . $commentBlock->toHtml(); } return $result; } } ================================================ FILE: Plugin/Model/Order/LoadOrderComment.php ================================================ orderFactory = $orderFactory; $this->orderExtensionFactory = $extensionFactory; } public function afterGet( OrderRepositoryInterface $subject, OrderInterface $resultOrder ) { $this->setOrderComment($resultOrder); return $resultOrder; } public function afterGetList( OrderRepositoryInterface $subject, \Magento\Sales\Api\Data\OrderSearchResultInterface $orderSearchResult ) { foreach ($orderSearchResult->getItems() as $order) { $this->setOrderComment($order); } return $orderSearchResult; } public function setOrderComment(OrderInterface $order) { if ($order instanceof \Magento\Sales\Model\Order) { $value = $order->getBoldOrderComment(); } else { $temp = $this->getOrderFactory()->create(); $temp->load($order->getId()); $value = $temp->getBoldOrderComment(); } $extensionAttributes = $order->getExtensionAttributes(); $orderExtension = $extensionAttributes ? $extensionAttributes : $this->getOrderExtensionFactory()->create(); $orderExtension->setBoldOrderComment($value); $order->setExtensionAttributes($orderExtension); } public function getOrderFactory() { return $this->orderFactory; } public function getOrderExtensionFactory() { return $this->orderExtensionFactory; } } ================================================ FILE: README.md ================================================ # Bold Commerce: Magento 2 Order Comments ## Description This extension allows customers to place a comment during the checkout. The comment field is displayed in the billing step right above the place order button. Additionally, there is also the option of showing the comment field on the cart page. Store owners can then see these comments in the backend on the order grid and on the order view page. ### Checkout view ![comment box closed](docs/checkout_comment_closed.png) ![comment box opened](docs/checkout_comment_opened.png) ### Admin panel ![admin panel](docs/admin_panel.png) ## Emails Add the "order comment" to new order emails by referencing [the code here](https://github.com/boldcommerce/magento2-ordercomments/issues/6#issuecomment-328515806). ## Configuration There are several [configuration options](https://github.com/boldcommerce/magento2-ordercomments/blob/master/etc/adminhtml/system.xml) for this extension, which can be found at **STORES > Configuration > SALES > Sales > Order Comments**. ## Installation ``` composer require boldcommerce/magento2-ordercomments php bin/magento module:enable Bold_OrderComment php bin/magento setup:upgrade ``` ## Changelog 1.8.5 ============= * Third party contribution: PHP 8.1 bugfix `Deprecated Functionality: trim(): Passing null to parameter` when viewing an order in the my orders section that doesn't have an order comment [#72](https://github.com/boldcommerce/magento2-ordercomments/pull/72) 1.8.4 ============= * Third party contribution: PHP 8.1 support [#71](https://github.com/boldcommerce/magento2-ordercomments/pull/71) * Third party contribution: Spanish translations [#70](https://github.com/boldcommerce/magento2-ordercomments/pull/70) * Third party contribution: Thai translations [#67](https://github.com/boldcommerce/magento2-ordercomments/pull/67) 1.8.2 ============= * Third party contribution: Bengali translations [#65](https://github.com/boldcommerce/magento2-ordercomments/pull/65) 1.8.1 ============= * fix bug introduced with 1.8.0 in checkout `Cannot read property 'length' of null` 1.8.0 ============= * new feature: ability to show the comment field on the cart page based on a admin configuration setting. 1.7.1 ============= * upgrade tests to phpunit 6 1.7.0 ============= * Added website scope configuration setting to toggle visibility of comment field. [#59](https://github.com/boldcommerce/magento2-ordercomments/pull/59) 1.6.5 ============= * Third party contribution: PHP 7.4 support added in composer [#55](https://github.com/boldcommerce/magento2-ordercomments/pull/55) * Third party contribution: Japanese translations added [#52](https://github.com/boldcommerce/magento2-ordercomments/pull/52) * Third party contribution: Fix typo in Italian translation [#51](https://github.com/boldcommerce/magento2-ordercomments/pull/51) * Third party contribution: Hungarian Translations added [#50](https://github.com/boldcommerce/magento2-ordercomments/pull/50) * Third party contribution: New sections added in readme [#48](https://github.com/boldcommerce/magento2-ordercomments/pull/48) 1.6.4 ============= * Third party contribution: php 7.3 support in composer [#45](https://github.com/boldcommerce/magento2-ordercomments/pull/45) * Third party contribution: French translations [#43](https://github.com/boldcommerce/magento2-ordercomments/pull/43) * Third party contribution: Polish translations [#40](https://github.com/boldcommerce/magento2-ordercomments/pull/40) * Third party contribution: Czech translations [#39](https://github.com/boldcommerce/magento2-ordercomments/pull/39) 1.6.3 ============= * Third party contribution: move form selector in order-comment-validator.js to a separate method to improve extensibility through mixins [#36](https://github.com/boldcommerce/magento2-ordercomments/pull/36) 1.6.2 ============= * Third party contribution: fix duplicate comment field on admin sales invoice view [#31](https://github.com/boldcommerce/magento2-ordercomments/pull/31) * Third party contribution: fix typo and added some code improvements to the install script [#30](https://github.com/boldcommerce/magento2-ordercomments/pull/30) 1.6.1 ============= * Third party contribution: Enabled PHP 7.2 support [#29](https://github.com/boldcommerce/magento2-ordercomments/pull/29) 1.6.0 ============= * Third party contribution: Hebrew translations [#28](https://github.com/boldcommerce/magento2-ordercomments/pull/28) 1.5.0 ============= * Third party contribution: Form selector fallback for compatability with external changes that move the comment field [#24](https://github.com/boldcommerce/magento2-ordercomments/pull/24) 1.4.1 ============= * Third party contribution: Fixed it_IT translation csv [#20](https://github.com/boldcommerce/magento2-ordercomments/pull/20) 1.4.0 ============= * Third party contribution: Made the comment available in the order list web api `V1/orders` [#18](https://github.com/boldcommerce/magento2-ordercomments/pull/18) 1.3.0 ============= * UX changes to the max comment length feature [#15](https://github.com/boldcommerce/magento2-ordercomments/issue/15) * Made the comment available in the order detail web api `V1/orders/{id}` [#15](https://github.com/boldcommerce/magento2-ordercomments/issue/15) 1.2.0 ============= * added setting to change initial collapse state of comment field (closed/opened/no collapse) [#14](https://github.com/boldcommerce/magento2-ordercomments/issue/14) 1.1.4 ============= * updated composer.json to allow PHP 7.1 1.1.3 ============= * Third party contribution: Dutch translations [#10](https://github.com/boldcommerce/magento2-ordercomments/pull/10) * Third party contribution: Italian translations [#11](https://github.com/boldcommerce/magento2-ordercomments/pull/11) 1.1.2 ============= * Fix for fatal error on admin order view page when used with some other extensions [#9](https://github.com/boldcommerce/magento2-ordercomments/issues/9) 1.1.1 ============= * Third party contribution: Swedish translations and fixes in German translations [#5](https://github.com/boldcommerce/magento2-ordercomments/pull/5) 1.1.0 ============= * Third party contribution: German translations [#2](https://github.com/boldcommerce/magento2-ordercomments/pull/2) * Third party contribution: Optional configuration for maximum comment length [#3](https://github.com/boldcommerce/magento2-ordercomments/pull/3) * Third party contribution: Show order comments in customer account [#4](https://github.com/boldcommerce/magento2-ordercomments/pull/4) 1.0.0 ============= initial version ## Technical To take in account third party payment extensions using custom implementations of Magento_Checkout/js/action/place-order.js to submit the order, this extension sends the order comment in a separate request during the validation, before the order is placed. It should therefore work out of the box. ## Uninstall If you installed this module through composer, then you can run `php bin/magento module:uninstall Bold_OrderComment` to automatically remove the code and drop the columns added by this extension. *note:* the uninstall command seems bugged and might get stuck at `Removing code from Magento codebase:` (It worked fine for me on a 2.1.0 install but not on a 2.1.4 install). When this happens you should exit with `ctrl+c` and run ``` composer update php bin/magento maintenance:disable ``` See [github issue 3544](https://github.com/magento/magento2/issues/3544) Alternatively you can manually remove the extension and remove the column `bold_order_comment` from the tables * quote * sales_order * sales_order_grid ## License MIT ================================================ FILE: Setup/InstallData.php ================================================ salesSetupFactory = $salesSetupFactory; $this->quoteSetupFactory = $quoteSetupFactory; } /** * @param ModuleDataSetupInterface $setup * @param ModuleContextInterface $context * @return void */ public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context) { $setup->startSetup(); /** @var \Magento\Quote\Setup\QuoteSetup $quoteInstaller */ $quoteInstaller = $this->quoteSetupFactory->create(['resourceName' => 'quote_setup', 'setup' => $setup]); /** @var \Magento\Sales\Setup\SalesSetup $salesInstaller */ $salesInstaller = $this->salesSetupFactory->create(['resourceName' => 'sales_setup', 'setup' => $setup]); $quoteInstaller->addAttribute( 'quote', OrderComment::COMMENT_FIELD_NAME, ['type' => Table::TYPE_TEXT, 'length' => '64k', 'nullable' => true] ); $salesInstaller->addAttribute( 'order', OrderComment::COMMENT_FIELD_NAME, ['type' => Table::TYPE_TEXT, 'length' => '64k', 'nullable' => true, 'grid' => true] ); $setup->endSetup(); } } ================================================ FILE: Setup/Uninstall.php ================================================ startSetup(); $setup->getConnection()->dropColumn( $setup->getTable('quote'), OrderComment::COMMENT_FIELD_NAME ); $setup->getConnection()->dropColumn( $setup->getTable('sales_order'), OrderComment::COMMENT_FIELD_NAME ); $setup->getConnection()->dropColumn( $setup->getTable('sales_order_grid'), OrderComment::COMMENT_FIELD_NAME ); $setup->endSetup(); } } ================================================ FILE: Test/Integration/Model/GuestOrderCommentManagementTest.php ================================================ create('\Magento\Quote\Model\Quote'); $quote->load('test01', 'reserved_order_id'); /** @var \Magento\Quote\Model\QuoteIdMask $quoteMask */ $quoteMask = $objectManager->create('\Magento\Quote\Model\QuoteIdMask'); $quoteMask->load($quote->getId(), 'quote_id'); $model = $objectManager->create('\Bold\OrderComment\Api\GuestOrderCommentManagementInterface'); $data = $objectManager->create('\Bold\OrderComment\Api\Data\OrderCommentInterface'); $data->setComment($comment); $model->saveOrderComment($quoteMask->getMaskedId(), $data); $quote->load('test01', 'reserved_order_id'); self::assertEquals($comment, $quote->getData(OrderComment::COMMENT_FIELD_NAME)); } } ================================================ FILE: Test/Integration/Model/OrderCommentManagementTest.php ================================================ create('\Magento\Quote\Model\Quote'); $quote->load('test01', 'reserved_order_id'); $model = $objectManager->create('\Bold\OrderComment\Api\OrderCommentManagementInterface'); $data = $objectManager->create('\Bold\OrderComment\Api\Data\OrderCommentInterface'); $data->setComment($comment); $model->saveOrderComment($quote->getId(), $data); $quote->load('test01', 'reserved_order_id'); self::assertEquals($comment, $quote->getData(OrderComment::COMMENT_FIELD_NAME)); } } ================================================ FILE: Test/Integration/Observer/AddOrderCommentToOrderTest.php ================================================ create('\Magento\Quote\Model\Quote'); $quote->load('test01', 'reserved_order_id'); $quote->setData(OrderComment::COMMENT_FIELD_NAME, $comment); $quote->save(); /** @var \Magento\Quote\Api\CartManagementInterface $model */ $model = $objectManager->create('\Magento\Quote\Api\CartManagementInterface'); /** @var \Magento\Sales\Model\Order $order */ $order = $model->submit($quote); self::assertEquals($comment, $order->getData(OrderComment::COMMENT_FIELD_NAME)); } } ================================================ FILE: Test/Unit/Model/GuestOrderCommentManagementTest.php ================================================ quoteRepositoryMock = $this->createMock('\Magento\Quote\Api\CartRepositoryInterface'); $this->quoteMock = $this->createMock( '\Magento\Quote\Model\Quote', [ 'getItemsCount', 'save', '__wakeup' ], [], '', false ); $this->orderCommentManagementMock = $this->createMock( 'Bold\OrderComment\Model\OrderCommentManagement', [], [], '', false ); $this->maskedCartId = 'f216207248d65c789b17be8545e0aa73'; $this->cartId = 123; $guestCartTestHelper = new GuestCartTestHelper($this); list($this->quoteIdMaskFactoryMock, $this->quoteIdMaskMock) = $guestCartTestHelper->mockQuoteIdMask( $this->maskedCartId, $this->cartId ); $this->testObject = $objectManager->getObject( 'Bold\OrderComment\Model\GuestOrderCommentManagement', [ 'orderCommentManagement' => $this->orderCommentManagementMock, 'quoteIdMaskFactory' => $this->quoteIdMaskFactoryMock ] ); } public function testSaveComment() { $comment = 'test comment'; $orderCommentMock = $this->getMockBuilder('\Bold\OrderComment\Model\Data\OrderComment') ->disableOriginalConstructor() ->getMock(); $this->orderCommentManagementMock->expects($this->once()) ->method('saveOrderComment') ->with($this->cartId, $orderCommentMock) ->willReturn($comment); $result = $this->testObject->saveOrderComment($this->maskedCartId, $orderCommentMock); $this->assertEquals($comment, $result); } } ================================================ FILE: Test/Unit/Model/OrderCommentManagementTest.php ================================================ quoteRepositoryMock = $this->createMock(CartRepositoryInterface::class); $this->quoteMock = $this->createPartialMock( Quote::class, [ 'getItemsCount', 'save', '__wakeup' ] ); $this->configMock = $this->getMockForAbstractClass( ScopeConfigInterface::class ); $this->testObject = new OrderCommentManagement($this->quoteRepositoryMock, $this->configMock); } /** * @expectedException \Magento\Framework\Exception\NoSuchEntityException * @expectedExceptionMessage Cart 123 doesn't contain products */ public function testSaveCommentWithEmptyCart() { $this->setupQuoteRepositoryMockQueries(123, 0); $this->testObject->saveOrderComment(123, $this->mockOrderComment()); } /** * @expectedException \Magento\Framework\Exception\CouldNotSaveException * @expectedExceptionMessage The order comment could not be saved */ public function testSaveCommentWhenCouldNotSaveQuote() { $cartId = 123; $cartItemCount = 12; $this->setupQuoteRepositoryMockQueries($cartId, $cartItemCount); $exceptionMessage = 'The order comment could not be saved'; $exception = new \Magento\Framework\Exception\CouldNotSaveException(__($exceptionMessage)); $this->quoteRepositoryMock->expects($this->once()) ->method('save') ->with($this->quoteMock) ->willThrowException($exception); $this->testObject->saveOrderComment($cartId, $this->mockOrderComment()); } /** * @expectedException \Magento\Framework\Exception\ValidatorException * @expectedExceptionMessage Comment is too long */ public function testSaveCommentThatIsTooLong() { $cartId = 123; $cartItemCount = 12; $comment = '123456789'; $this->configMock ->method('getValue') ->with(OrderCommentConfigProvider::CONFIG_MAX_LENGTH) ->willReturn(8); $this->setupQuoteRepositoryMockQueries($cartId, $cartItemCount); $this->quoteRepositoryMock->expects($this->never()) ->method('save'); $this->testObject->saveOrderComment($cartId, $this->mockOrderComment($comment)); } public function testSaveComment() { $cartId = 123; $comment = 'test comment'; $cartItemCount = 12; $this->setupQuoteRepositoryMockQueries($cartId, $cartItemCount); $this->quoteRepositoryMock->expects($this->once()) ->method('save') ->with($this->quoteMock) ->will($this->returnSelf()); $this->testObject->saveOrderComment($cartId, $this->mockOrderComment($comment)); $this->assertEquals($comment, $this->quoteMock->getData(OrderComment::COMMENT_FIELD_NAME)); } public function testSaveCommentWithTags() { $cartId = 123; $cartItemCount = 12; $comment = 'test comment'; $this->setupQuoteRepositoryMockQueries($cartId, $cartItemCount); $this->quoteRepositoryMock->expects($this->once()) ->method('save') ->with($this->quoteMock) ->will($this->returnSelf()); $this->testObject->saveOrderComment($cartId, $this->mockOrderComment($comment)); $this->assertEquals(strip_tags($comment), $this->quoteMock->getData(OrderComment::COMMENT_FIELD_NAME)); } private function setupQuoteRepositoryMockQueries(int $cartId, int $cartItemCount) { $this->quoteRepositoryMock->expects($this->once()) ->method('getActive')->with($cartId)->will($this->returnValue($this->quoteMock)); $this->quoteMock->expects($this->once())->method('getItemsCount')->will($this->returnValue($cartItemCount)); } /** * @return \PHPUnit_Framework_MockObject_MockObject|OrderCommentInterface */ private function mockOrderComment(string $comment = null): \PHPUnit_Framework_MockObject_MockObject { $orderCommentMock = $this->getMockBuilder(OrderComment::class) ->disableOriginalConstructor() ->getMock(); if ($comment !== null) { $orderCommentMock->expects($this->once()) ->method('getComment') ->willReturn($comment); } return $orderCommentMock; } } ================================================ FILE: Test/Unit/Observer/AddOrderCommentToOrderTest.php ================================================ objectManager = new ObjectManager($this); $this->observer = new AddOrderCommentToOrder(); } public function testExecute() { $comment = 'test comment'; $observerMock = $this->createMock('Magento\Framework\Event\Observer'); $eventMock = $this->createPartialMock('Magento\Framework\Event', ['getData']); $quoteMock = $this->createPartialMock('Magento\Quote\Model\Quote', ['getData']); $orderMock = $this->createPartialMock('Magento\Sales\Model\Order', []); $map = [ ['quote', null, $quoteMock], ['order', null, $orderMock] ]; $observerMock->expects($this->atLeast(2)) ->method('getEvent') ->willReturn($eventMock); $eventMock->expects($this->atLeast(2)) ->method('getData') ->will($this->returnValueMap($map)); $quoteMock->expects($this->atLeastOnce()) ->method('getData') ->with(OrderComment::COMMENT_FIELD_NAME) ->willReturn($comment); $this->observer->execute($observerMock); $this->assertEquals($comment, $orderMock->getData(OrderComment::COMMENT_FIELD_NAME)); } } ================================================ FILE: ViewModel/Comment.php ================================================ config = $config; $this->checkoutSession = $checkoutSession; } /** * Get the current comment from quote * * @return string|null * @throws \Magento\Framework\Exception\LocalizedException * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function getComment(): ?string { if ($this->checkoutSession->getQuoteId()) { return $this->checkoutSession->getQuote()->getData(OrderComment::COMMENT_FIELD_NAME); } return null; } /** * Get Max Length validation classes if character restriction is enabled * * @return string */ public function getExtraClass(): string { $class = ''; if ($maxLength = $this->config->getMaximumCharacterLength()) { $class .= 'validate-length maximum-length-' . $maxLength; } return $class; } } ================================================ FILE: composer.json ================================================ { "name": "boldcommerce/magento2-ordercomments", "description": "Magento 2 Module to add a comment field above the place order button in the checkout", "require": { "php": "^7.0.0|^8.0.0" }, "type": "magento2-module", "keywords": ["magento2"], "license": "MIT", "authors": [ { "name": "Bold Commerce BV", "email": "info@boldcommerce.nl" } ], "autoload": { "files": [ "registration.php" ], "psr-4": { "Bold\\OrderComment\\": "" } } } ================================================ FILE: etc/adminhtml/di.xml ================================================ ================================================ FILE: etc/adminhtml/system.xml ================================================
Magento\Config\Model\Config\Source\Yesno Toggle visibility of the order comments section in the checkout Magento\Config\Model\Config\Source\Yesno Toggle visibility of order comments on the cart page Leave empty for no limit Magento\Config\Model\Config\Source\Yesno Bold\OrderComment\Model\Config\Source\Collapse
================================================ FILE: etc/config.xml ================================================ 1 1 0 ================================================ FILE: etc/di.xml ================================================ sales_order.bold_order_comment ================================================ FILE: etc/events.xml ================================================ ================================================ FILE: etc/extension_attributes.xml ================================================ ================================================ FILE: etc/frontend/di.xml ================================================ Bold\OrderComment\Model\OrderCommentConfigProvider ================================================ FILE: etc/frontend/routes.xml ================================================ ================================================ FILE: etc/module.xml ================================================ ================================================ FILE: etc/webapi.xml ================================================ %cart_id% ================================================ FILE: i18n/ar_SA.csv ================================================ "Do you have any comments regarding the order?","هل لديك اي تعليق بخصوص لطلبك؟" "Enter your comment...","اكتب تعليقك" "Order Comment","تعليق الطلب" "The order comment could not be saved","تعليق الطلب لا يمكن حفظه" "Cart %1 doesn't contain products","سلة المشتريات لا تحتوي على منتجات" ================================================ FILE: i18n/bn_BD.csv ================================================ "Do you have any comments regarding the order?","আপনার কি এই অর্ডার সংক্রান্ত কোন মন্তব্য আছে?" "Enter your comment...","আপনার মন্তব্য লিখুন..." "Order Comment","অর্ডার মন্তব্য" "The order comment could not be saved","অর্ডার মন্তব্যটি সংরক্ষণ করা যায়নি" "Cart %1 doesn't contain products","কার্ট %1 তে কোন পন্য নাই" ================================================ FILE: i18n/cs_CZ.csv ================================================ "Do you have any comments regarding the order?","Máte k objednávce nějakou poznámku?" "Enter your comment...","Vložte poznámku..." "Order Comment","Poznámky k objednávce" "The order comment could not be saved","Poznámka k objednávce nemohla být uložena" "Cart %1 doesn't contain products","V košíku %1 nejsou žádné produkty" ================================================ FILE: i18n/de_CH.csv ================================================ "Do you have any comments regarding the order?","Haben Sie Anmerkungen zur Bestellung?" "Enter your comment...","Anmerkungen eingeben..." "Order Comment","Anmerkungen" "The order comment could not be saved","Die Anmerkungen zur Bestellung konnten nicht gespeichert werden" "Cart %1 doesn't contain products","Warenkorb %1 enthält keine Produkte" ================================================ FILE: i18n/de_DE.csv ================================================ "Do you have any comments regarding the order?","Haben Sie Anmerkungen zur Bestellung?" "Enter your comment...","Anmerkungen eingeben..." "Order Comment","Anmerkungen" "The order comment could not be saved","Die Anmerkungen zur Bestellung konnten nicht gespeichert werden" "Cart %1 doesn't contain products","Warenkorb %1 enthält keine Produkte" ================================================ FILE: i18n/el_GR.csv ================================================ "Do you have any comments regarding the order?","Έχετε παρατηρήσεις σχετικά με την παραγγελία;" "Enter your comment...","Γράψτε τις παρατηρήσεις σας ..." "Order Comment","Παρατηρήσεις" "The order comment could not be saved","Οι παρατηρήσεις σας δεν μπόρεσαν να αποθηκευτούν" "Cart %1 doesn't contain products","Το καλάθι %1 δεν περιέχει προϊόντα" ================================================ FILE: i18n/es_ES.csv ================================================ "Your comment has been saved.","Se ha guardado el comentario." "There was an error when updating the quote.","Ha ocurrido un error al actualizar el carrito." "Starts with field closed","Iniciar con el campo cerrado" "Starts with field opened","Iniciar con el campo abierto" "Render field without collapse","Renderizar campo sin que se pueda plegar" "Cart %1 doesn't contain products","El carrito %1 no contiene productos" "The order comment could not be saved","No se ha podido guardar el comentario" "Comment is too long","El comentario es demasiado largo" "Order Comment","Comentario de pedido" "Edit Order Comment","Comentario de pedido" "Do you have any comments regarding the order?","¿Tienes algún comentario sobre el pedido?" "Enter your comment...","Introduce tu comentario..." "Save Comment","Guardar comentario" "Enter comment","Introducir comentario" "Remaining characters:","Caracteres restantes:" "Maximum length in characters","Carecteres máximos" "Show comments in customer account","Mostrar comentario en cuenta de cliente" "Initial collapse state on checkout page","Estado inicial del campo en el proceso de compra" ================================================ FILE: i18n/fr_FR.csv ================================================ "Do you have any comments regarding the order?","Avez-vous un commentaire à nous transmettre au sujet de cette commande ?" "Enter your comment...","Saisissez votre commentaire ici..." "Order Comment","Commentaire de commande" "The order comment could not be saved","Le commentaire de cette commande ne peut pas être enregistré" "Cart %1 doesn't contain products","Le panier %1 ne contient aucun article" "Remaining characters:","Caractères restants :" ================================================ FILE: i18n/he_IL.csv ================================================ "Do you have any comments regarding the order?","האם יש לכם הערה בנוגע להזמנה?" "Enter your comment...","הוסיפו את ההערה שלכם..." "Order Comment","הערת הזמנה" "The order comment could not be saved","לא ניתן לשמור את ההערה" "Cart %1 doesn't contain products","העגלה %1 לא מכילה מוצרים" ================================================ FILE: i18n/hu_HU.csv ================================================ "Do you have any comments regarding the order?","Üzenet a futárnak:" "Enter your comment...","Például kapucsengő, kapu, egyéb pontosítás..." "Order Comment","Rendelés megjegyzés" "The order comment could not be saved","A rendelés megjegyzést nem lehet elmenteni." "Cart %1 doesn't contain products","A kosárban nincs %1 termék" "Remaining characters:","Üzenet hossz:" ================================================ FILE: i18n/it_IT.csv ================================================ "Do you have any comments regarding the order?","Hai delle richieste in merito all'ordine?" "Enter your comment...","Inserisci qui i commenti..." "Order Comment","Commenti ordine" "The order comment could not be saved","La tua richiesta al momento non puo' essere salvata" "Cart %1 doesn't contain products","Il carrello %1 non contiene prodotti" ================================================ FILE: i18n/ja_JP.csv ================================================ "Do you have any comments regarding the order?","注文に関して何かコメントはありますか?" "Enter your comment...","コメントを入力してください..." "Order Comment","注文コメント" "The order comment could not be saved","注文コメントを保存できませんでした" "Cart %1 doesn't contain products","カート%1には商品が含まれていません" ================================================ FILE: i18n/nl_NL.csv ================================================ "Do you have any comments regarding the order?","Heb je nog opmerkingen over je bestelling?" "Enter your comment...","Opmerkingen toevoegen..." "Order Comment","Opmerkingen" "The order comment could not be saved","De opmerkingen over je bestelling kunnen niet opgeslagen worden" "Cart %1 doesn't contain products","Winkelwagen %1 bevat geen producten" ================================================ FILE: i18n/pl_PL.csv ================================================ "Do you have any comments regarding the order?","Dodaj komentarz do zamówienia" "Enter your comment...","Twój komentarz..." "Order Comment","Komentarz do zamówienia" "The order comment could not be saved","Twój komentarz nie został zapisany" "Cart %1 doesn't contain products","Koszyk %1 nie zawiera produktów" ================================================ FILE: i18n/sl_SI.csv ================================================ "Do you have any comments regarding the order?","Želite dodati komentar k vašemu naročilu?" "Enter your comment...","Vnesite svoj komentar..." "Order Comment","Komentar k naročilu" "The order comment could not be saved","Komentarja naročila ni bilo mogoče shraniti" "Cart %1 doesn't contain products","Košarica %1 je prazna" ================================================ FILE: i18n/sv_SE.csv ================================================ "Do you have any comments regarding the order?","Vill du lämna en kommentar angående din order?" "Enter your comment...","Lämna din kommentar..." "Order Comment","Orderkommentar" "The order comment could not be saved","Din kommentar kunde inte sparas" "Cart %1 doesn't contain products","Varukorg %1 innehåller inga produkter" ================================================ FILE: i18n/th_TH.csv ================================================ "Do you have any comments regarding the order?","คุณมีความคิดเห็นเกี่ยวกับคำสั่งซื้อหรือไม่?" "Enter your comment...","ใส่ความคิดเห็นของคุณที่นี่ ..." "Order Comment","ความเห็นของคำสั่งซื้อนี้" "The order comment could not be saved","ไม่สามารถบันทึกความคิดเห็นของคำสั่งซื้อนี้" "Cart %1 doesn't contain products","รถเข็น %1 ไม่มีสินค้า" ================================================ FILE: registration.php ================================================ ================================================ FILE: view/adminhtml/templates/order/view/comments.phtml ================================================ getOrderComment()):?>
escapeHtml($comment));?>
================================================ FILE: view/adminhtml/ui_component/sales_order_grid.xml ================================================ Magento_Ui/js/grid/columns/column text true Order Comment ================================================ FILE: view/frontend/layout/checkout_cart_index.xml ================================================ Bold\OrderComment\ViewModel\Comment ================================================ FILE: view/frontend/layout/checkout_index_index.xml ================================================ Bold_OrderComment/js/view/checkout/order-comment-validator Bold_OrderComment/js/view/checkout/order-comment-block ================================================ FILE: view/frontend/layout/sales_order_view.xml ================================================ ================================================ FILE: view/frontend/templates/cart/comment.phtml ================================================ getData('comment_view_model'); $oldComment = $viewModel ? $viewModel->getComment() : ''; $hasComment = !!$oldComment; $extraValidationClasses = $viewModel ? $viewModel->getExtraClass() : ''; ?>
, "openedState": "active", "saveState": false}}'>
escapeHtml(__('Edit Order Comment')) ?>
================================================ FILE: view/frontend/templates/order/view/comment.phtml ================================================ getOrderComment()):?>
getOrderCommentHtml();?>
================================================ FILE: view/frontend/web/css/source/_module.less ================================================ ._error { .order-comment-input { outline: none; border: 2px solid @checkout-field-validation__border-color; &:focus { outline: none; border: 2px solid @checkout-field-validation__border-color; box-shadow: 0 0 3px @checkout-field-validation__border-color; } } } .checkout-payment-method { .payment-option._collapsible.comment { .payment-option-content { display: block; } } } & when (@media-common = true) { .cart-order-comment { border-bottom: @border-width__base solid @border-color__base; clear: left; &:extend(.abs-discount-block all); .fieldset.order-comment { display: block; &>div { display: block; } } } } //mobile .media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__m) { .cart-order-comment { border-bottom: @border-width__base solid @border-color__base; .block>.title { border: 0; } } } //desktop .media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__m) { .cart-order-comment { &:extend(.abs-discount-block-desktop all); .lib-layout-column(2, 1, @layout-column-checkout__width-main); border: 0; box-sizing: border-box; padding-right: 4%; .block { .title { padding: 10px 0; &:after { display: inline; margin-left: @indent__s; position: static; } } &.order-comment { width: 100%; border-bottom: @border-width__base solid @border-color__base; border-top: @border-width__base solid @border-color__base; } } } } ================================================ FILE: view/frontend/web/js/model/checkout/order-comment-validator.js ================================================ define( [ 'jquery', 'Magento_Customer/js/model/customer', 'Magento_Checkout/js/model/quote', 'Magento_Checkout/js/model/url-builder', 'mage/url', 'Magento_Checkout/js/model/error-processor', 'Magento_Ui/js/model/messageList', 'mage/translate' ], function ($, customer, quote, urlBuilder, urlFormatter, errorProcessor, messageContainer, __) { 'use strict'; return { /** * Make an ajax PUT request to store the order comment in the quote. * * @returns {Boolean} */ validate: function () { var isCustomer = customer.isLoggedIn(); var form = this.getForm(); var comment = form.find('.input-text.order-comment').val(); if (this.hasMaxLength() && comment.length > this.getMaxLength()) { messageContainer.addErrorMessage({ message: __("Comment is too long") }); return false; } var quoteId = quote.getQuoteId(); var url; if (isCustomer) { url = urlBuilder.createUrl('/carts/mine/set-order-comment', {}) } else { url = urlBuilder.createUrl('/guest-carts/:cartId/set-order-comment', {cartId: quoteId}); } var payload = { cartId: quoteId, orderComment: { comment: comment } }; if (!payload.orderComment.comment) { return true; } var result = true; $.ajax({ url: urlFormatter.build(url), data: JSON.stringify(payload), global: false, contentType: 'application/json', type: 'PUT', async: false }).done( function (response) { result = true; } ).fail( function (response) { result = false; errorProcessor.process(response); } ); return result; }, getForm: function () { var form = $('.payment-method input[name="payment[method]"]:checked') .parents('.payment-method') .find('form.order-comment-form'); // Compatibility for Rubic_CleanCheckout if (!form.length) { form = $('form.order-comment-form'); } return form; }, hasMaxLength: function () { return window.checkoutConfig.max_length > 0; }, getMaxLength: function () { return window.checkoutConfig.max_length; } }; } ); ================================================ FILE: view/frontend/web/js/view/checkout/order-comment-block.js ================================================ define( [ 'jquery', 'uiComponent', 'knockout' ], function ($, Component, ko) { 'use strict'; ko.extenders.maxOrderCommentLength = function (target, maxLength) { var timer; var result = ko.computed({ read: target, write: function (val) { if (maxLength > 0) { clearTimeout(timer); if (val.length > maxLength) { var limitedVal = val.substring(0, maxLength); if (target() === limitedVal) { target.notifySubscribers(); } else { target(limitedVal); } result.css("_error"); timer = setTimeout(function () { result.css(""); }, 800); } else { target(val); result.css(""); } } else { target(val); } } }).extend({ notify: 'always' }); result.css = ko.observable(); result(target()); return result; }; function getExistingComment() { return window.checkoutConfig.existing_comment; } return Component.extend({ defaults: { template: 'Bold_OrderComment/checkout/order-comment-block' }, initialize: function() { this._super(); var self = this; this.comment = ko.observable(getExistingComment()).extend({maxOrderCommentLength: this.getMaxLength()}); this.remainingCharacters = ko.computed(function(){ return self.getMaxLength() - self.comment().length; }); }, showInCheckout: function() { return window.checkoutConfig.show_in_checkout; }, hasMaxLength: function() { return window.checkoutConfig.max_length > 0; }, getMaxLength: function () { return window.checkoutConfig.max_length; }, getInitialCollapseState: function() { return window.checkoutConfig.comment_initial_collapse_state; }, isInitialStateOpened: function() { return this.getInitialCollapseState() === 1 } }); } ); ================================================ FILE: view/frontend/web/js/view/checkout/order-comment-validator.js ================================================ define( [ 'uiComponent', 'Magento_Checkout/js/model/payment/additional-validators', 'Bold_OrderComment/js/model/checkout/order-comment-validator' ], function (Component, additionalValidators, commentValidator) { 'use strict'; additionalValidators.registerValidator(commentValidator); return Component.extend({}); } ); ================================================ FILE: view/frontend/web/template/checkout/form-content.html ================================================

================================================ FILE: view/frontend/web/template/checkout/order-comment-block.html ================================================