[
  {
    "path": "Api/Data/OrderCommentInterface.php",
    "content": "<?php\nnamespace Bold\\OrderComment\\Api\\Data;\n\ninterface OrderCommentInterface\n{\n    /**\n     * @return string|null\n     */\n    public function getComment();\n\n    /**\n     * @param string $comment\n     * @return null\n     */\n    public function setComment($comment);\n}\n"
  },
  {
    "path": "Api/GuestOrderCommentManagementInterface.php",
    "content": "<?php\nnamespace Bold\\OrderComment\\Api;\n\n/**\n * Interface for saving the checkout comment to the quote for guest orders\n */\ninterface GuestOrderCommentManagementInterface\n{\n    /**\n     * @param string $cartId\n     * @param \\Bold\\OrderComment\\Api\\Data\\OrderCommentInterface $orderComment\n     * @return \\Magento\\Checkout\\Api\\Data\\PaymentDetailsInterface\n     */\n    public function saveOrderComment(\n        $cartId,\n        \\Bold\\OrderComment\\Api\\Data\\OrderCommentInterface $orderComment\n    );\n}\n"
  },
  {
    "path": "Api/OrderCommentManagementInterface.php",
    "content": "<?php\nnamespace Bold\\OrderComment\\Api;\n\n/**\n * Interface for saving the checkout comment to the quote for orders of logged in users\n * @api\n */\ninterface OrderCommentManagementInterface\n{\n    /**\n     * @param int $cartId\n     * @param \\Bold\\OrderComment\\Api\\Data\\OrderCommentInterface $orderComment\n     * @return string\n     */\n    public function saveOrderComment(\n        $cartId,\n        \\Bold\\OrderComment\\Api\\Data\\OrderCommentInterface $orderComment\n    );\n}\n"
  },
  {
    "path": "Block/Order/Comment.php",
    "content": "<?php\n\nnamespace Bold\\OrderComment\\Block\\Order;\n\nuse Bold\\OrderComment\\Model\\Data\\OrderComment;\nuse Magento\\Framework\\Registry;\nuse Magento\\Framework\\View\\Element\\Template\\Context as TemplateContext;\nuse Magento\\Sales\\Model\\Order;\n\nclass Comment extends \\Magento\\Framework\\View\\Element\\Template\n{\n    /**\n     * Core registry\n     *\n     * @var \\Magento\\Framework\\Registry\n     */\n    protected $coreRegistry = null;\n\n    public function __construct(\n        TemplateContext $context,\n        Registry $registry,\n        array $data = []\n    ) {\n        $this->coreRegistry = $registry;\n        $this->_isScopePrivate = true;\n        $this->_template = 'order/view/comment.phtml';\n        parent::__construct($context, $data);\n    }\n\n    public function getOrder() : Order\n    {\n        return $this->coreRegistry->registry('current_order');\n    }\n\n    public function getOrderComment(): string\n    {\n        return trim((string) $this->getOrder()->getData(OrderComment::COMMENT_FIELD_NAME));\n    }\n\n    public function hasOrderComment() : bool\n    {\n        return strlen($this->getOrderComment()) > 0;\n    }\n\n    public function getOrderCommentHtml() : string\n    {\n        return nl2br($this->escapeHtml($this->getOrderComment()));\n    }\n}\n"
  },
  {
    "path": "Controller/Cart/UpdateComment.php",
    "content": "<?php\n/**\n * UpdateComment\n *\n * @copyright Copyright © 2020 Bold Commerce BV. All rights reserved.\n * @author    dev@boldcommerce.nl\n */\ndeclare(strict_types=1);\n\nnamespace Bold\\OrderComment\\Controller\\Cart;\n\nuse Bold\\OrderComment\\Model\\Data\\OrderCommentFactory;\nuse Bold\\OrderComment\\Model\\OrderCommentManagement;\nuse Magento\\Checkout\\Model\\Cart as CustomerCart;\nuse Magento\\Checkout\\Model\\Session as CheckoutSession;\nuse Magento\\Framework\\App\\Action\\Context;\nuse Magento\\Framework\\App\\Action\\HttpPostActionInterface as HttpPostActionInterface;\nuse Magento\\Framework\\App\\Config\\ScopeConfigInterface;\nuse Magento\\Framework\\App\\ResponseInterface;\nuse Magento\\Framework\\Controller\\Result\\Redirect;\nuse Magento\\Framework\\Controller\\ResultInterface;\nuse Magento\\Framework\\Data\\Form\\FormKey\\Validator;\nuse Magento\\Framework\\Exception\\LocalizedException;\nuse Magento\\Store\\Model\\StoreManagerInterface;\nuse Psr\\Log\\LoggerInterface;\n\nclass UpdateComment extends \\Magento\\Checkout\\Controller\\Cart implements HttpPostActionInterface\n{\n    /**\n     * @var LoggerInterface\n     */\n    protected $logger;\n\n    protected $orderCommentManagement;\n\n    protected $orderCommentFactory;\n\n    /**\n     * UpdateComment constructor.\n     * @param Context $context\n     * @param ScopeConfigInterface $scopeConfig\n     * @param CheckoutSession $checkoutSession\n     * @param StoreManagerInterface $storeManager\n     * @param Validator $formKeyValidator\n     * @param CustomerCart $cart\n     * @param LoggerInterface $logger\n     * @param OrderCommentManagement $orderCommentManagement\n     * @param OrderCommentFactory $orderCommentFactory\n     */\n    public function __construct(\n        Context $context,\n        ScopeConfigInterface $scopeConfig,\n        CheckoutSession $checkoutSession,\n        StoreManagerInterface $storeManager,\n        Validator $formKeyValidator,\n        CustomerCart $cart,\n        LoggerInterface $logger,\n        OrderCommentManagement $orderCommentManagement,\n        OrderCommentFactory $orderCommentFactory\n    ) {\n        parent::__construct($context, $scopeConfig, $checkoutSession, $storeManager, $formKeyValidator, $cart);\n        $this->logger = $logger;\n        $this->orderCommentManagement = $orderCommentManagement;\n        $this->orderCommentFactory = $orderCommentFactory;\n    }\n\n    /**\n     * Saves the comment to the quote\n     *\n     * @return ResponseInterface|Redirect|ResultInterface\n     */\n    public function execute()\n    {\n        try {\n            $comment = trim($this->getRequest()->getParam('order_comment', ''));\n            $cartQuote = $this->cart->getQuote();\n\n            $commentObj = $this->orderCommentFactory->create();\n            $commentObj->setComment($comment);\n\n            $this->orderCommentManagement->saveOrderComment($cartQuote->getId(), $commentObj);\n            $this->messageManager->addSuccessMessage(\n                __(\n                    'Your comment has been saved.'\n                )\n            );\n        } catch (LocalizedException $e) {\n            $this->messageManager->addErrorMessage($e->getMessage());\n        } catch (\\Exception $e) {\n            $this->messageManager->addErrorMessage(__('There was an error when updating the quote.'));\n            $this->logger->critical($e->getMessage(), ['exception' => $e->getTraceAsString()]);\n        }\n\n        return $this->_goBack();\n    }\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017 Bold Commerce BV\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "Model/Config/Source/Collapse.php",
    "content": "<?php\n\nnamespace Bold\\OrderComment\\Model\\Config\\Source;\n\nclass Collapse implements \\Magento\\Framework\\Option\\ArrayInterface\n{\n    /**\n     * Options getter\n     *\n     * @return array\n     */\n    public function toOptionArray()\n    {\n        $options = $this->toArray();\n        $result = [];\n\n        foreach ($options as $value => $label) {\n            $result[] = [\n                'value' => $value, 'label' => $label\n            ];\n        }\n\n        return $result;\n    }\n\n    /**\n     * Get options in \"key-value\" format\n     *\n     * @return array\n     */\n    public function toArray()\n    {\n        return [\n            0 => __('Starts with field closed'),\n            1 => __('Starts with field opened'),\n            2 => __('Render field without collapse')\n        ];\n    }\n}\n"
  },
  {
    "path": "Model/Config.php",
    "content": "<?php\n/**\n * Config\n *\n * @copyright Copyright © 2020 Bold Commerce BV. All rights reserved.\n * @author    dev@boldcommerce.nl\n */\ndeclare(strict_types=1);\n\nnamespace Bold\\OrderComment\\Model;\n\nuse Magento\\Framework\\App\\Config\\ScopeConfigInterface;\nuse Magento\\Store\\Model\\ScopeInterface;\n\nclass Config\n{\n    const XML_PATH_CONFIG_MAX_LENGTH = 'sales/ordercomments/max_length';\n\n    const XML_PATH_CONFIG_FIELD_COLLAPSE_STATE = 'sales/ordercomments/collapse_state';\n\n    const XML_PATH_CONFIG_SHOW_IN_CHECKOUT = 'sales/ordercomments/show_in_checkout';\n\n    const XML_PATH_CONFIG_SHOW_IN_ACCOUNT = 'sales/ordercomments/show_in_account';\n\n    const XML_PATH_CONFIG_SHOW_IN_CART = 'sales/ordercomments/show_in_cart';\n\n    /**\n     * @var ScopeConfigInterface\n     */\n    private $scopeConfig;\n\n    public function __construct(ScopeConfigInterface $scopeConfig)\n    {\n        $this->scopeConfig = $scopeConfig;\n    }\n\n    /**\n     * @param mixed $website\n     * @return bool\n     */\n    public function canShowInCheckout($website = null): bool\n    {\n        return $this->scopeConfig->isSetFlag(self::XML_PATH_CONFIG_SHOW_IN_CHECKOUT, ScopeInterface::SCOPE_WEBSITE, $website);\n    }\n\n    /**\n     * @param mixed $website\n     * @return bool\n     */\n    public function canShowInAccount($website = null): bool\n    {\n        return $this->scopeConfig->isSetFlag(self::XML_PATH_CONFIG_SHOW_IN_ACCOUNT, ScopeInterface::SCOPE_WEBSITE, $website);\n    }\n\n    /**\n     * @param mixed $website\n     * @return bool\n     */\n    public function canShowInCart($website = null): bool\n    {\n        return $this->scopeConfig->isSetFlag(self::XML_PATH_CONFIG_SHOW_IN_CART, ScopeInterface::SCOPE_WEBSITE, $website);\n    }\n\n    /**\n     * @param mixed $website\n     * @return mixed\n     */\n    public function getMaximumCharacterLength($website = null)\n    {\n        return $this->scopeConfig->getValue(self::XML_PATH_CONFIG_MAX_LENGTH, ScopeInterface::SCOPE_WEBSITE, $website);\n    }\n\n    /**\n     * @param mixed $website\n     * @return mixed\n     */\n    public function getInitialCollapseState($website = null)\n    {\n        return $this->scopeConfig->getValue(self::XML_PATH_CONFIG_FIELD_COLLAPSE_STATE, ScopeInterface::SCOPE_WEBSITE, $website);\n    }\n}\n"
  },
  {
    "path": "Model/Data/OrderComment.php",
    "content": "<?php\nnamespace Bold\\OrderComment\\Model\\Data;\n\nuse Bold\\OrderComment\\Api\\Data\\OrderCommentInterface;\nuse Magento\\Framework\\Api\\AbstractSimpleObject;\n\nclass OrderComment extends AbstractSimpleObject implements OrderCommentInterface\n{\n    const COMMENT_FIELD_NAME = 'bold_order_comment';\n    \n    /**\n     * @return string|null\n     */\n    public function getComment()\n    {\n        return $this->_get(static::COMMENT_FIELD_NAME);\n    }\n\n    /**\n     * @param string $comment\n     * @return $this\n     */\n    public function setComment($comment)\n    {\n        return $this->setData(static::COMMENT_FIELD_NAME, $comment);\n    }\n}\n"
  },
  {
    "path": "Model/GuestOrderCommentManagement.php",
    "content": "<?php\nnamespace Bold\\OrderComment\\Model;\n\nuse Magento\\Quote\\Model\\QuoteIdMaskFactory;\n\nclass GuestOrderCommentManagement implements \\Bold\\OrderComment\\Api\\GuestOrderCommentManagementInterface\n{\n\n    /**\n     * @var QuoteIdMaskFactory\n     */\n    protected $quoteIdMaskFactory;\n\n    /**\n     * @var \\Bold\\OrderComment\\Api\\OrderCommentManagementInterface\n     */\n    protected $orderCommentManagement;\n    \n    /**\n     * GuestOrderCommentManagement constructor.\n     * @param QuoteIdMaskFactory $quoteIdMaskFactory\n     * @param \\Bold\\OrderComment\\Api\\OrderCommentManagementInterface $orderCommentManagement\n     */\n    public function __construct(\n        QuoteIdMaskFactory $quoteIdMaskFactory,\n        \\Bold\\OrderComment\\Api\\OrderCommentManagementInterface $orderCommentManagement\n    ) {\n        $this->quoteIdMaskFactory = $quoteIdMaskFactory;\n        $this->orderCommentManagement = $orderCommentManagement;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public function saveOrderComment(\n        $cartId,\n        \\Bold\\OrderComment\\Api\\Data\\OrderCommentInterface $orderComment\n    ) {\n        $quoteIdMask = $this->quoteIdMaskFactory->create()->load($cartId, 'masked_id');\n        return $this->orderCommentManagement->saveOrderComment($quoteIdMask->getQuoteId(), $orderComment);\n    }\n}\n"
  },
  {
    "path": "Model/OrderCommentConfigProvider.php",
    "content": "<?php\n\nnamespace Bold\\OrderComment\\Model;\n\nuse Bold\\OrderComment\\Model\\Data\\OrderComment;\nuse Magento\\Checkout\\Model\\ConfigProviderInterface;\nuse Magento\\Checkout\\Model\\Session;\nuse Magento\\Framework\\App\\Config\\ScopeConfigInterface;\nuse Magento\\Store\\Model\\ScopeInterface;\n\nclass OrderCommentConfigProvider implements ConfigProviderInterface\n{\n    /**\n     * @deprecated\n     */\n    const CONFIG_MAX_LENGTH = 'sales/ordercomments/max_length';\n\n    /**\n     * @deprecated\n     */\n    const CONFIG_FIELD_COLLAPSE_STATE = 'sales/ordercomments/collapse_state';\n\n    /**\n     * @deprecated\n     */\n    const CONFIG_SHOW_IN_CHECKOUT = 'sales/ordercomments/show_in_checkout';\n\n    /**\n     * @var ScopeConfigInterface\n     */\n    private $scopeConfig;\n\n    private $checkoutSession;\n\n    /**\n     * OrderCommentConfigProvider constructor.\n     * @param ScopeConfigInterface $scopeConfig\n     * @param Session $checkoutSession\n     */\n    public function __construct(ScopeConfigInterface $scopeConfig, Session $checkoutSession)\n    {\n        $this->scopeConfig = $scopeConfig;\n        $this->checkoutSession = $checkoutSession;\n    }\n\n    /**\n     * Prepare data for use in checkout javascript component\n     *\n     * @return array\n     * @throws \\Magento\\Framework\\Exception\\LocalizedException\n     * @throws \\Magento\\Framework\\Exception\\NoSuchEntityException\n     */\n    public function getConfig()\n    {\n        $comment = '';\n        if ($this->checkoutSession->getQuoteId()) {\n            $comment = $this->checkoutSession->getQuote()->getData(OrderComment::COMMENT_FIELD_NAME) ?: '';\n        }\n\n        return [\n            'show_in_checkout' => $this->scopeConfig->isSetFlag(self::CONFIG_SHOW_IN_CHECKOUT, ScopeInterface::SCOPE_WEBSITE),\n            'max_length' => (int) $this->scopeConfig->getValue(self::CONFIG_MAX_LENGTH, ScopeInterface::SCOPE_WEBSITE),\n            'comment_initial_collapse_state' => (int) $this->scopeConfig->getValue(self::CONFIG_FIELD_COLLAPSE_STATE, ScopeInterface::SCOPE_WEBSITE),\n            'existing_comment' => $comment\n        ];\n    }\n}\n"
  },
  {
    "path": "Model/OrderCommentManagement.php",
    "content": "<?php\nnamespace Bold\\OrderComment\\Model;\n\nuse Bold\\OrderComment\\Model\\Data\\OrderComment;\nuse Magento\\Framework\\App\\Config\\ScopeConfigInterface;\nuse Magento\\Framework\\Exception\\NoSuchEntityException;\nuse Magento\\Framework\\Exception\\CouldNotSaveException;\nuse Magento\\Framework\\Exception\\ValidatorException;\n\nclass OrderCommentManagement implements \\Bold\\OrderComment\\Api\\OrderCommentManagementInterface\n{\n    /**\n     * Quote repository.\n     *\n     * @var \\Magento\\Quote\\Api\\CartRepositoryInterface\n     */\n    protected $quoteRepository;\n\n    /**\n     * @var ScopeConfigInterface\n     */\n    protected $scopeConfig;\n\n    /**\n     *\n     * @param \\Magento\\Quote\\Api\\CartRepositoryInterface $quoteRepository Quote repository.\n     */\n    public function __construct(\n        \\Magento\\Quote\\Api\\CartRepositoryInterface $quoteRepository,\n        ScopeConfigInterface $scopeConfig\n    ) {\n        $this->quoteRepository = $quoteRepository;\n        $this->scopeConfig = $scopeConfig;\n    }\n\n    /**\n     * @param int $cartId\n     * @param \\Bold\\OrderComment\\Api\\Data\\OrderCommentInterface $orderComment\n     * @return null|string\n     * @throws CouldNotSaveException\n     * @throws NoSuchEntityException\n     */\n    public function saveOrderComment(\n        $cartId,\n        \\Bold\\OrderComment\\Api\\Data\\OrderCommentInterface $orderComment\n    ) {\n        $quote = $this->quoteRepository->getActive($cartId);\n        if (!$quote->getItemsCount()) {\n            throw new NoSuchEntityException(__('Cart %1 doesn\\'t contain products', $cartId));\n        }\n        $comment = $orderComment->getComment();\n\n        $this->validateComment($comment);\n\n        try {\n            $quote->setData(OrderComment::COMMENT_FIELD_NAME, strip_tags($comment));\n            $this->quoteRepository->save($quote);\n        } catch (\\Exception $e) {\n            throw new CouldNotSaveException(__('The order comment could not be saved'));\n        }\n\n        return $comment;\n    }\n\n    /**\n     * @param string $comment\n     * @throws ValidatorException\n     */\n    protected function validateComment($comment)\n    {\n        $maxLength = $this->scopeConfig->getValue(OrderCommentConfigProvider::CONFIG_MAX_LENGTH);\n        if ($maxLength && (mb_strlen($comment) > $maxLength)) {\n            throw new ValidatorException(__('Comment is too long'));\n        }\n    }\n}\n"
  },
  {
    "path": "Observer/AddOrderCommentToOrder.php",
    "content": "<?php\nnamespace Bold\\OrderComment\\Observer;\n\nuse Bold\\OrderComment\\Model\\Data\\OrderComment;\n\nclass AddOrderCommentToOrder implements \\Magento\\Framework\\Event\\ObserverInterface\n{\n    /**\n     * transfer the order comment from the quote object to the order object during the\n     * sales_model_service_quote_submit_before event\n     *\n     * @param \\Magento\\Framework\\Event\\Observer $observer\n     * @return void\n     */\n    public function execute(\\Magento\\Framework\\Event\\Observer $observer)\n    {\n        /* @var $order \\Magento\\Sales\\Model\\Order */\n        $order = $observer->getEvent()->getOrder();\n        \n        /** @var $quote \\Magento\\Quote\\Model\\Quote $quote */\n        $quote = $observer->getEvent()->getQuote();\n\n        $order->setData(OrderComment::COMMENT_FIELD_NAME, $quote->getData(OrderComment::COMMENT_FIELD_NAME));\n    }\n}\n"
  },
  {
    "path": "Plugin/Block/Adminhtml/SalesOrderViewInfo.php",
    "content": "<?php\nnamespace Bold\\OrderComment\\Plugin\\Block\\Adminhtml;\n\nuse Bold\\OrderComment\\Model\\Data\\OrderComment;\n\nclass SalesOrderViewInfo\n{\n    /**\n     * @param \\Magento\\Sales\\Block\\Adminhtml\\Order\\View\\Info $subject\n     * @param string $result\n     * @return string\n     * @throws \\Magento\\Framework\\Exception\\LocalizedException\n     */\n    public function afterToHtml(\n        \\Magento\\Sales\\Block\\Adminhtml\\Order\\View\\Info $subject,\n        $result\n    ) {\n        $commentBlock = $subject->getLayout()->getBlock('boldcommerce_order_comments');\n        if ($commentBlock !== false && $subject->getNameInLayout() == 'order_info') {\n            $commentBlock->setOrderComment($subject->getOrder()->getData(OrderComment::COMMENT_FIELD_NAME));\n            $result = $result . $commentBlock->toHtml();\n        }\n        \n        return $result;\n    }\n}\n"
  },
  {
    "path": "Plugin/Model/Order/LoadOrderComment.php",
    "content": "<?php\nnamespace Bold\\OrderComment\\Plugin\\Model\\Order;\n\nuse Magento\\Sales\\Api\\OrderRepositoryInterface;\nuse Magento\\Sales\\Api\\Data\\OrderInterface;\nuse Magento\\Sales\\Model\\OrderFactory;\nuse Magento\\Sales\\Api\\Data\\OrderExtensionFactory;\n\nclass LoadOrderComment\n{\n    private $orderFactory;\n\n    private $orderExtensionFactory;\n\n    public function __construct(\n        OrderFactory $orderFactory,\n        OrderExtensionFactory $extensionFactory\n    ) {\n        $this->orderFactory = $orderFactory;\n        $this->orderExtensionFactory = $extensionFactory;\n    }\n\n    public function afterGet(\n        OrderRepositoryInterface $subject,\n        OrderInterface $resultOrder\n    ) {\n        $this->setOrderComment($resultOrder);\n        return $resultOrder;\n    }\n\n    public function afterGetList(\n        OrderRepositoryInterface $subject,\n        \\Magento\\Sales\\Api\\Data\\OrderSearchResultInterface $orderSearchResult\n    ) {\n        foreach ($orderSearchResult->getItems() as $order) {\n            $this->setOrderComment($order);\n        }\n        return $orderSearchResult;\n    }\n\n    public function setOrderComment(OrderInterface $order)\n    {\n        if ($order instanceof \\Magento\\Sales\\Model\\Order) {\n            $value = $order->getBoldOrderComment();\n        } else {\n            $temp = $this->getOrderFactory()->create();\n            $temp->load($order->getId());\n            $value = $temp->getBoldOrderComment();\n        }\n\n        $extensionAttributes = $order->getExtensionAttributes();\n        $orderExtension = $extensionAttributes ? $extensionAttributes : $this->getOrderExtensionFactory()->create();\n        $orderExtension->setBoldOrderComment($value);\n        $order->setExtensionAttributes($orderExtension);\n    }\n\n    public function getOrderFactory()\n    {\n        return $this->orderFactory;\n    }\n\n    public function getOrderExtensionFactory()\n    {\n        return $this->orderExtensionFactory;\n    }\n}\n"
  },
  {
    "path": "README.md",
    "content": "# Bold Commerce: Magento 2 Order Comments\n\n## Description\nThis extension allows customers to place a comment during the checkout.\nThe comment field is displayed in the billing step right above the place order button.\n\nAdditionally, there is also the option of showing the comment field on the cart page.\n\nStore owners can then see these comments in the backend on the order grid and on the order view page.\n\n### Checkout view\n![comment box closed](docs/checkout_comment_closed.png)\n\n\n![comment box opened](docs/checkout_comment_opened.png)\n\n### Admin panel\n![admin panel](docs/admin_panel.png)\n\n## Emails\n\nAdd the \"order comment\" to new order emails by referencing [the code here](https://github.com/boldcommerce/magento2-ordercomments/issues/6#issuecomment-328515806).\n\n## Configuration\n\nThere 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**.\n\n## Installation\n```\ncomposer require boldcommerce/magento2-ordercomments\nphp bin/magento module:enable Bold_OrderComment\nphp bin/magento setup:upgrade\n```\n\n## Changelog\n1.8.5\n=============\n* Third party contribution: PHP 8.1 bugfix `Deprecated Functionality: trim(): Passing null to parameter` when viewing\nan order in the my orders section that doesn't have an order comment [#72](https://github.com/boldcommerce/magento2-ordercomments/pull/72)\n\n1.8.4\n=============\n* Third party contribution: PHP 8.1 support [#71](https://github.com/boldcommerce/magento2-ordercomments/pull/71)\n* Third party contribution: Spanish translations [#70](https://github.com/boldcommerce/magento2-ordercomments/pull/70)\n* Third party contribution: Thai translations [#67](https://github.com/boldcommerce/magento2-ordercomments/pull/67)\n\n1.8.2\n=============\n* Third party contribution: Bengali translations [#65](https://github.com/boldcommerce/magento2-ordercomments/pull/65)\n\n1.8.1\n=============\n* fix bug introduced with 1.8.0 in checkout `Cannot read property 'length' of null`\n\n1.8.0\n=============\n* new feature: ability to show the comment field on the cart page based on a admin configuration setting.\n\n1.7.1\n=============\n* upgrade tests to phpunit 6\n\n1.7.0\n=============\n* Added website scope configuration setting to toggle visibility of comment field. [#59](https://github.com/boldcommerce/magento2-ordercomments/pull/59)\n\n1.6.5\n=============\n* Third party contribution: PHP 7.4 support added in composer [#55](https://github.com/boldcommerce/magento2-ordercomments/pull/55)\n* Third party contribution: Japanese translations added [#52](https://github.com/boldcommerce/magento2-ordercomments/pull/52)\n* Third party contribution: Fix typo in Italian translation [#51](https://github.com/boldcommerce/magento2-ordercomments/pull/51)\n* Third party contribution: Hungarian Translations added [#50](https://github.com/boldcommerce/magento2-ordercomments/pull/50)\n* Third party contribution: New sections added in readme [#48](https://github.com/boldcommerce/magento2-ordercomments/pull/48)\n\n1.6.4\n=============\n* Third party contribution: php 7.3 support in composer [#45](https://github.com/boldcommerce/magento2-ordercomments/pull/45)\n* Third party contribution: French translations [#43](https://github.com/boldcommerce/magento2-ordercomments/pull/43)\n* Third party contribution: Polish translations [#40](https://github.com/boldcommerce/magento2-ordercomments/pull/40)\n* Third party contribution: Czech translations [#39](https://github.com/boldcommerce/magento2-ordercomments/pull/39)\n\n1.6.3\n=============\n* 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)\n\n1.6.2\n=============\n* Third party contribution: fix duplicate comment field on admin sales invoice view [#31](https://github.com/boldcommerce/magento2-ordercomments/pull/31)\n* Third party contribution: fix typo and added some code improvements to the install script [#30](https://github.com/boldcommerce/magento2-ordercomments/pull/30)\n\n1.6.1\n=============\n* Third party contribution: Enabled PHP 7.2 support [#29](https://github.com/boldcommerce/magento2-ordercomments/pull/29)\n\n1.6.0\n=============\n* Third party contribution: Hebrew translations [#28](https://github.com/boldcommerce/magento2-ordercomments/pull/28)\n\n1.5.0\n=============\n* 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)\n\n1.4.1\n=============\n* Third party contribution: Fixed it_IT translation csv [#20](https://github.com/boldcommerce/magento2-ordercomments/pull/20)\n\n1.4.0\n=============\n* Third party contribution: Made the comment available in the order list web api `V1/orders` [#18](https://github.com/boldcommerce/magento2-ordercomments/pull/18)\n\n1.3.0\n=============\n* UX changes to the max comment length feature [#15](https://github.com/boldcommerce/magento2-ordercomments/issue/15)\n* Made the comment available in the order detail web api `V1/orders/{id}` [#15](https://github.com/boldcommerce/magento2-ordercomments/issue/15)\n\n1.2.0\n=============\n* added setting to change initial collapse state of comment field (closed/opened/no collapse) [#14](https://github.com/boldcommerce/magento2-ordercomments/issue/14)\n\n1.1.4\n=============\n* updated composer.json to allow PHP 7.1\n\n1.1.3\n=============\n* Third party contribution: Dutch translations [#10](https://github.com/boldcommerce/magento2-ordercomments/pull/10)\n* Third party contribution: Italian translations [#11](https://github.com/boldcommerce/magento2-ordercomments/pull/11)\n\n1.1.2\n=============\n* Fix for fatal error on admin order view page when used with some other extensions [#9](https://github.com/boldcommerce/magento2-ordercomments/issues/9)\n\n1.1.1\n=============\n* Third party contribution: Swedish translations and fixes in German translations [#5](https://github.com/boldcommerce/magento2-ordercomments/pull/5)\n\n1.1.0\n=============\n* Third party contribution: German translations [#2](https://github.com/boldcommerce/magento2-ordercomments/pull/2)\n* Third party contribution: Optional configuration for maximum comment length [#3](https://github.com/boldcommerce/magento2-ordercomments/pull/3)\n* Third party contribution: Show order comments in customer account [#4](https://github.com/boldcommerce/magento2-ordercomments/pull/4)\n\n1.0.0\n=============\ninitial version\n\n## Technical\nTo 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\nthe order comment in a separate request during the validation, before the order is placed. It should therefore work out of\nthe box.\n\n\n\n\n## Uninstall\nIf you installed this module through composer, then you can run `php bin/magento module:uninstall Bold_OrderComment` to automatically\nremove the code and drop the columns added by this extension.\n\n*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\nexit with `ctrl+c` and run \n```\ncomposer update\nphp bin/magento maintenance:disable\n```\nSee [github issue 3544](https://github.com/magento/magento2/issues/3544)\n\nAlternatively you can manually remove the extension and remove the column `bold_order_comment` from the tables\n* quote\n* sales_order\n* sales_order_grid\n\n## License\nMIT\n"
  },
  {
    "path": "Setup/InstallData.php",
    "content": "<?php\n\nnamespace Bold\\OrderComment\\Setup;\n\nuse Bold\\OrderComment\\Model\\Data\\OrderComment;\nuse Magento\\Framework\\Setup\\InstallDataInterface;\nuse Magento\\Framework\\Setup\\ModuleContextInterface;\nuse Magento\\Framework\\Setup\\ModuleDataSetupInterface;\n\nuse Magento\\Framework\\DB\\Ddl\\Table;\n\nclass InstallData implements InstallDataInterface\n{\n    /**\n     * @var \\Magento\\Sales\\Setup\\SalesSetupFactory\n     */\n    protected $salesSetupFactory;\n    \n    /**\n     * @var \\Magento\\Quote\\Setup\\QuoteSetupFactory\n     */\n    protected $quoteSetupFactory;\n\n    /**\n     * InstallData constructor.\n     * @param \\Magento\\Sales\\Setup\\SalesSetupFactory $salesSetupFactory\n     * @param \\Magento\\Quote\\Setup\\QuoteSetupFactory $quoteSetupFactory\n     */\n    public function __construct(\n        \\Magento\\Sales\\Setup\\SalesSetupFactory $salesSetupFactory,\n        \\Magento\\Quote\\Setup\\QuoteSetupFactory $quoteSetupFactory\n    ) {\n        $this->salesSetupFactory = $salesSetupFactory;\n        $this->quoteSetupFactory = $quoteSetupFactory;\n    }\n\n    /**\n     * @param ModuleDataSetupInterface $setup\n     * @param ModuleContextInterface $context\n     * @return void\n     */\n    public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context)\n    {\n        $setup->startSetup();\n\n        /** @var \\Magento\\Quote\\Setup\\QuoteSetup $quoteInstaller */\n        $quoteInstaller = $this->quoteSetupFactory->create(['resourceName' => 'quote_setup', 'setup' => $setup]);\n\n        /** @var \\Magento\\Sales\\Setup\\SalesSetup $salesInstaller */\n        $salesInstaller = $this->salesSetupFactory->create(['resourceName' => 'sales_setup', 'setup' => $setup]);\n        \n        $quoteInstaller->addAttribute(\n            'quote',\n            OrderComment::COMMENT_FIELD_NAME,\n            ['type' => Table::TYPE_TEXT, 'length' => '64k', 'nullable' => true]\n        );\n\n        $salesInstaller->addAttribute(\n            'order',\n            OrderComment::COMMENT_FIELD_NAME,\n            ['type' => Table::TYPE_TEXT, 'length' => '64k', 'nullable' => true, 'grid' => true]\n        );\n\n        $setup->endSetup();\n    }\n}\n"
  },
  {
    "path": "Setup/Uninstall.php",
    "content": "<?php\nnamespace Bold\\OrderComment\\Setup;\n\nuse Bold\\OrderComment\\Model\\Data\\OrderComment;\nuse Magento\\Framework\\Setup\\UninstallInterface;\nuse Magento\\Framework\\Setup\\SchemaSetupInterface;\nuse Magento\\Framework\\Setup\\ModuleContextInterface;\n\nclass Uninstall implements UninstallInterface\n{\n    public function uninstall(SchemaSetupInterface $setup, ModuleContextInterface $context)\n    {\n        $setup->startSetup();\n\n        $setup->getConnection()->dropColumn(\n            $setup->getTable('quote'),\n            OrderComment::COMMENT_FIELD_NAME\n        );\n\n        $setup->getConnection()->dropColumn(\n            $setup->getTable('sales_order'),\n            OrderComment::COMMENT_FIELD_NAME\n        );\n\n        $setup->getConnection()->dropColumn(\n            $setup->getTable('sales_order_grid'),\n            OrderComment::COMMENT_FIELD_NAME\n        );\n\n        $setup->endSetup();\n    }\n}\n"
  },
  {
    "path": "Test/Integration/Model/GuestOrderCommentManagementTest.php",
    "content": "<?php\nnamespace Bold\\OrderComment\\Test\\Integration\\Model;\n\nuse Bold\\OrderComment\\Model\\Data\\OrderComment;\nuse Magento\\TestFramework\\Helper\\Bootstrap;\nuse PHPUnit\\Framework\\TestCase;\n\n/**\n * Class GuestOrderCommentManagementTest\n * @package Bold\\OrderComment\\Test\\Integration\\Model\n *\n * @magentoDbIsolation enabled\n */\nclass GuestOrderCommentManagementTest extends TestCase\n{\n    /**\n     * @magentoDataFixture Magento/Sales/_files/quote_with_bundle.php\n     * @return void\n     */\n    public function testGuestSaveOrderComment()\n    {\n        $objectManager = Bootstrap::getObjectManager();\n\n        $comment = 'test comment guest';\n\n        /** @var \\Magento\\Quote\\Model\\Quote $quote */\n        $quote = $objectManager->create('\\Magento\\Quote\\Model\\Quote');\n        $quote->load('test01', 'reserved_order_id');\n\n        /** @var \\Magento\\Quote\\Model\\QuoteIdMask $quoteMask */\n        $quoteMask = $objectManager->create('\\Magento\\Quote\\Model\\QuoteIdMask');\n        $quoteMask->load($quote->getId(), 'quote_id');\n        \n        $model = $objectManager->create('\\Bold\\OrderComment\\Api\\GuestOrderCommentManagementInterface');\n\n        $data = $objectManager->create('\\Bold\\OrderComment\\Api\\Data\\OrderCommentInterface');\n\n        $data->setComment($comment);\n\n        $model->saveOrderComment($quoteMask->getMaskedId(), $data);\n\n        $quote->load('test01', 'reserved_order_id');\n\n        self::assertEquals($comment, $quote->getData(OrderComment::COMMENT_FIELD_NAME));\n    }\n}\n"
  },
  {
    "path": "Test/Integration/Model/OrderCommentManagementTest.php",
    "content": "<?php\nnamespace Bold\\OrderComment\\Test\\Integration\\Model;\n\nuse Bold\\OrderComment\\Model\\Data\\OrderComment;\nuse Magento\\TestFramework\\Helper\\Bootstrap;\nuse PHPUnit\\Framework\\TestCase;\n\n/**\n * Class OrderCommentManagementTest\n * @package Bold\\OrderComment\\Test\\Integration\\Model\n *\n * @magentoDbIsolation enabled\n */\nclass OrderCommentManagementTest extends TestCase\n{\n    /**\n     * @magentoDataFixture Magento/Sales/_files/quote_with_bundle.php\n     * @return void\n     */\n    public function testSaveOrderComment()\n    {\n        $objectManager = Bootstrap::getObjectManager();\n\n        $comment = 'test comment';\n\n        /** @var \\Magento\\Quote\\Model\\Quote $quote */\n        $quote = $objectManager->create('\\Magento\\Quote\\Model\\Quote');\n        $quote->load('test01', 'reserved_order_id');\n        \n        $model = $objectManager->create('\\Bold\\OrderComment\\Api\\OrderCommentManagementInterface');\n        $data = $objectManager->create('\\Bold\\OrderComment\\Api\\Data\\OrderCommentInterface');\n\n        $data->setComment($comment);\n        \n        $model->saveOrderComment($quote->getId(), $data);\n\n        $quote->load('test01', 'reserved_order_id');\n\n        self::assertEquals($comment, $quote->getData(OrderComment::COMMENT_FIELD_NAME));\n    }\n}\n"
  },
  {
    "path": "Test/Integration/Observer/AddOrderCommentToOrderTest.php",
    "content": "<?php\nnamespace Bold\\OrderComment\\Test\\Integration\\Observer;\n\nuse Bold\\OrderComment\\Model\\Data\\OrderComment;\nuse Magento\\TestFramework\\Helper\\Bootstrap;\nuse PHPUnit\\Framework\\TestCase;\n\n/**\n * Class AddOrderCommentToOrderTest\n * @package Bold\\OrderComment\\Test\\Integration\\Observer\n *\n * tests if the comment gets passed from the quote to the order during order creation.\n * @magentoDbIsolation enabled\n */\nclass AddOrderCommentToOrderTest extends TestCase\n{\n    /**\n     * Create order with product that has child items\n     *\n     * @magentoDataFixture Magento/Sales/_files/quote_with_bundle.php\n     * @return void\n     */\n    public function testExecute()\n    {\n        $comment = 'test comment';\n\n        $objectManager = Bootstrap::getObjectManager();\n\n        /** @var \\Magento\\Quote\\Model\\Quote $quote */\n        $quote = $objectManager->create('\\Magento\\Quote\\Model\\Quote');\n        $quote->load('test01', 'reserved_order_id');\n\n        $quote->setData(OrderComment::COMMENT_FIELD_NAME, $comment);\n        $quote->save();\n        \n        /** @var \\Magento\\Quote\\Api\\CartManagementInterface $model */\n        $model = $objectManager->create('\\Magento\\Quote\\Api\\CartManagementInterface');\n        /** @var \\Magento\\Sales\\Model\\Order $order */\n        $order = $model->submit($quote);\n        \n        self::assertEquals($comment, $order->getData(OrderComment::COMMENT_FIELD_NAME));\n    }\n}\n"
  },
  {
    "path": "Test/Unit/Model/GuestOrderCommentManagementTest.php",
    "content": "<?php\nnamespace Bold\\OrderComment\\Test\\Unit\\Model;\n\nuse Magento\\Quote\\Test\\Unit\\Model\\GuestCart\\GuestCartTestHelper;\nuse PHPUnit\\Framework\\TestCase;\n\nclass GuestOrderCommentManagementTest extends TestCase\n{\n    /**\n     * @var \\Bold\\OrderComment\\Model\\GuestOrderCommentManagement\n     */\n    protected $testObject;\n\n    /**\n     * @var \\PHPUnit_Framework_MockObject_MockObject\n     */\n    protected $quoteIdMaskFactoryMock;\n\n    /**\n     * @var \\PHPUnit_Framework_MockObject_MockObject\n     */\n    protected $quoteIdMaskMock;\n\n    /**\n     * @var \\PHPUnit_Framework_MockObject_MockObject\n     */\n    protected $orderCommentManagementMock;\n\n    /**\n     * @var string\n     */\n    protected $maskedCartId;\n\n    /**\n     * @var int\n     */\n    protected $cartId;\n\n    /**\n     * @var \\PHPUnit_Framework_MockObject_MockObject\n     */\n    protected $quoteRepositoryMock;\n\n    /**\n     * @var \\PHPUnit_Framework_MockObject_MockObject\n     */\n    protected $quoteMock;\n    \n    protected function setUp()\n    {\n        $objectManager = new \\Magento\\Framework\\TestFramework\\Unit\\Helper\\ObjectManager($this);\n        \n        $this->quoteRepositoryMock = $this->createMock('\\Magento\\Quote\\Api\\CartRepositoryInterface');\n\n        $this->quoteMock = $this->createMock(\n            '\\Magento\\Quote\\Model\\Quote',\n            [\n                'getItemsCount',\n                'save',\n                '__wakeup'\n            ],\n            [],\n            '',\n            false\n        );\n        \n        $this->orderCommentManagementMock = $this->createMock(\n            'Bold\\OrderComment\\Model\\OrderCommentManagement',\n            [],\n            [],\n            '',\n            false\n        );\n\n        $this->maskedCartId = 'f216207248d65c789b17be8545e0aa73';\n        $this->cartId = 123;\n\n        $guestCartTestHelper = new GuestCartTestHelper($this);\n        list($this->quoteIdMaskFactoryMock, $this->quoteIdMaskMock) = $guestCartTestHelper->mockQuoteIdMask(\n            $this->maskedCartId,\n            $this->cartId\n        );\n\n        $this->testObject = $objectManager->getObject(\n            'Bold\\OrderComment\\Model\\GuestOrderCommentManagement',\n            [\n                'orderCommentManagement' => $this->orderCommentManagementMock,\n                'quoteIdMaskFactory' => $this->quoteIdMaskFactoryMock\n            ]\n        );\n    }\n\n    public function testSaveComment()\n    {\n        $comment = 'test comment';\n\n        $orderCommentMock = $this->getMockBuilder('\\Bold\\OrderComment\\Model\\Data\\OrderComment')\n            ->disableOriginalConstructor()\n            ->getMock();\n        \n        $this->orderCommentManagementMock->expects($this->once())\n            ->method('saveOrderComment')\n            ->with($this->cartId, $orderCommentMock)\n            ->willReturn($comment);\n        $result = $this->testObject->saveOrderComment($this->maskedCartId, $orderCommentMock);\n        $this->assertEquals($comment, $result);\n    }\n}\n"
  },
  {
    "path": "Test/Unit/Model/OrderCommentManagementTest.php",
    "content": "<?php\nnamespace Bold\\OrderComment\\Test\\Unit\\Model;\n\nuse Bold\\OrderComment\\Api\\Data\\OrderCommentInterface;\nuse Bold\\OrderComment\\Model\\Data\\OrderComment;\nuse Bold\\OrderComment\\Model\\OrderCommentConfigProvider;\nuse Bold\\OrderComment\\Model\\OrderCommentManagement;\nuse Magento\\Framework\\App\\Config\\ScopeConfigInterface;\nuse Magento\\Quote\\Api\\CartRepositoryInterface;\nuse Magento\\Quote\\Model\\Quote;\nuse Magento\\Quote\\Model\\QuoteRepository;\nuse PHPUnit\\Framework\\TestCase;\n\nclass OrderCommentManagementTest extends TestCase\n{\n    /**\n     * @var \\PHPUnit_Framework_MockObject_MockObject|QuoteRepository\n     */\n    protected $quoteRepositoryMock;\n\n    /**\n     * @var \\PHPUnit_Framework_MockObject_MockObject|Quote\n     */\n    protected $quoteMock;\n\n    /**\n     * @var OrderCommentManagement\n     */\n    protected $testObject;\n\n    /**\n     * @var \\PHPUnit_Framework_MockObject_MockObject|ScopeConfigInterface\n     */\n    protected $configMock;\n\n    public function setUp()\n    {\n        $this->quoteRepositoryMock = $this->createMock(CartRepositoryInterface::class);\n\n        $this->quoteMock = $this->createPartialMock(\n            Quote::class,\n            [\n                'getItemsCount',\n                'save',\n                '__wakeup'\n            ]\n        );\n        $this->configMock = $this->getMockForAbstractClass(\n            ScopeConfigInterface::class\n        );\n\n        $this->testObject = new OrderCommentManagement($this->quoteRepositoryMock, $this->configMock);\n    }\n\n    /**\n     * @expectedException \\Magento\\Framework\\Exception\\NoSuchEntityException\n     * @expectedExceptionMessage Cart 123 doesn't contain products\n     */\n    public function testSaveCommentWithEmptyCart()\n    {\n        $this->setupQuoteRepositoryMockQueries(123, 0);\n        $this->testObject->saveOrderComment(123, $this->mockOrderComment());\n    }\n\n    /**\n     * @expectedException \\Magento\\Framework\\Exception\\CouldNotSaveException\n     * @expectedExceptionMessage The order comment could not be saved\n     */\n    public function testSaveCommentWhenCouldNotSaveQuote()\n    {\n        $cartId = 123;\n        $cartItemCount = 12;\n\n        $this->setupQuoteRepositoryMockQueries($cartId, $cartItemCount);\n        \n        $exceptionMessage = 'The order comment could not be saved';\n        $exception = new \\Magento\\Framework\\Exception\\CouldNotSaveException(__($exceptionMessage));\n        $this->quoteRepositoryMock->expects($this->once())\n            ->method('save')\n            ->with($this->quoteMock)\n            ->willThrowException($exception);\n\n        $this->testObject->saveOrderComment($cartId, $this->mockOrderComment());\n    }\n    \n    /**\n     * @expectedException \\Magento\\Framework\\Exception\\ValidatorException\n     * @expectedExceptionMessage Comment is too long\n     */\n    public function testSaveCommentThatIsTooLong()\n    {\n        $cartId = 123;\n        $cartItemCount = 12;\n        $comment = '123456789';\n        $this->configMock\n            ->method('getValue')\n            ->with(OrderCommentConfigProvider::CONFIG_MAX_LENGTH)\n            ->willReturn(8);\n\n        $this->setupQuoteRepositoryMockQueries($cartId, $cartItemCount);\n        $this->quoteRepositoryMock->expects($this->never())\n            ->method('save');\n\n        $this->testObject->saveOrderComment($cartId, $this->mockOrderComment($comment));\n    }\n\n    public function testSaveComment()\n    {\n        $cartId = 123;\n        $comment = 'test comment';\n        $cartItemCount = 12;\n\n        $this->setupQuoteRepositoryMockQueries($cartId, $cartItemCount);\n        $this->quoteRepositoryMock->expects($this->once())\n            ->method('save')\n            ->with($this->quoteMock)\n            ->will($this->returnSelf());\n\n        $this->testObject->saveOrderComment($cartId, $this->mockOrderComment($comment));\n\n        $this->assertEquals($comment, $this->quoteMock->getData(OrderComment::COMMENT_FIELD_NAME));\n    }\n\n    public function testSaveCommentWithTags()\n    {\n        $cartId = 123;\n        $cartItemCount = 12;\n        $comment = 'test comment<script>alert(\"abcd\");</script><?php die(\"qwerty\")?>';\n\n        $this->setupQuoteRepositoryMockQueries($cartId, $cartItemCount);\n        $this->quoteRepositoryMock->expects($this->once())\n            ->method('save')\n            ->with($this->quoteMock)\n            ->will($this->returnSelf());\n\n        $this->testObject->saveOrderComment($cartId, $this->mockOrderComment($comment));\n\n        $this->assertEquals(strip_tags($comment), $this->quoteMock->getData(OrderComment::COMMENT_FIELD_NAME));\n    }\n\n    private function setupQuoteRepositoryMockQueries(int $cartId, int $cartItemCount)\n    {\n        $this->quoteRepositoryMock->expects($this->once())\n            ->method('getActive')->with($cartId)->will($this->returnValue($this->quoteMock));\n        $this->quoteMock->expects($this->once())->method('getItemsCount')->will($this->returnValue($cartItemCount));\n    }\n\n    /**\n     * @return \\PHPUnit_Framework_MockObject_MockObject|OrderCommentInterface\n     */\n    private function mockOrderComment(string $comment = null): \\PHPUnit_Framework_MockObject_MockObject\n    {\n        $orderCommentMock = $this->getMockBuilder(OrderComment::class)\n            ->disableOriginalConstructor()\n            ->getMock();\n\n        if ($comment !== null) {\n            $orderCommentMock->expects($this->once())\n                ->method('getComment')\n                ->willReturn($comment);\n        }\n        return $orderCommentMock;\n    }\n}\n"
  },
  {
    "path": "Test/Unit/Observer/AddOrderCommentToOrderTest.php",
    "content": "<?php\nnamespace Bold\\OrderComment\\Test\\Unit\\Observer;\n\nuse Bold\\OrderComment\\Observer\\AddOrderCommentToOrder;\nuse Magento\\Framework\\TestFramework\\Unit\\Helper\\ObjectManager;\nuse Bold\\OrderComment\\Model\\Data\\OrderComment;\nuse PHPUnit\\Framework\\TestCase;\n\nclass AddOrderCommentToOrderTest extends TestCase\n{\n    protected $objectManager;\n\n    /**\n     * @var AddOrderCommentToOrder\n     */\n    protected $observer;\n    \n    public function setUp()\n    {\n        $this->objectManager = new ObjectManager($this);\n\n        $this->observer = new AddOrderCommentToOrder();\n    }\n    \n    public function testExecute()\n    {\n        $comment = 'test comment';\n\n        $observerMock = $this->createMock('Magento\\Framework\\Event\\Observer');\n        $eventMock = $this->createPartialMock('Magento\\Framework\\Event', ['getData']);\n\n        $quoteMock = $this->createPartialMock('Magento\\Quote\\Model\\Quote', ['getData']);\n        $orderMock = $this->createPartialMock('Magento\\Sales\\Model\\Order', []);\n\n        $map = [\n            ['quote', null, $quoteMock],\n            ['order', null, $orderMock]\n        ];\n        \n        $observerMock->expects($this->atLeast(2))\n            ->method('getEvent')\n            ->willReturn($eventMock);\n        $eventMock->expects($this->atLeast(2))\n            ->method('getData')\n            ->will($this->returnValueMap($map));\n\n        $quoteMock->expects($this->atLeastOnce())\n            ->method('getData')\n            ->with(OrderComment::COMMENT_FIELD_NAME)\n            ->willReturn($comment);\n        \n        $this->observer->execute($observerMock);\n\n        $this->assertEquals($comment, $orderMock->getData(OrderComment::COMMENT_FIELD_NAME));\n    }\n}\n"
  },
  {
    "path": "ViewModel/Comment.php",
    "content": "<?php\n/**\n * Comment\n *\n * @copyright Copyright © 2020 Bold Commerce BV. All rights reserved.\n * @author    dev@boldcommerce.nl\n */\ndeclare(strict_types=1);\n\nnamespace Bold\\OrderComment\\ViewModel;\n\nuse Bold\\OrderComment\\Model\\Config;\nuse Bold\\OrderComment\\Model\\Data\\OrderComment;\nuse Magento\\Checkout\\Model\\Session as CheckoutSession;\nuse Magento\\Framework\\View\\Element\\Block\\ArgumentInterface;\n\nclass Comment implements ArgumentInterface\n{\n\n    /**\n     * @var Config\n     */\n    private $config;\n\n    /**\n     * @var CheckoutSession\n     */\n    private $checkoutSession;\n\n    /**\n     * Comment constructor.\n     * @param Config $config\n     * @param CheckoutSession $checkoutSession\n     */\n    public function __construct(Config $config, CheckoutSession $checkoutSession)\n    {\n        $this->config = $config;\n        $this->checkoutSession = $checkoutSession;\n    }\n\n    /**\n     * Get the current comment from quote\n     *\n     * @return string|null\n     * @throws \\Magento\\Framework\\Exception\\LocalizedException\n     * @throws \\Magento\\Framework\\Exception\\NoSuchEntityException\n     */\n    public function getComment(): ?string\n    {\n        if ($this->checkoutSession->getQuoteId()) {\n            return $this->checkoutSession->getQuote()->getData(OrderComment::COMMENT_FIELD_NAME);\n        }\n        return null;\n    }\n\n    /**\n     * Get Max Length validation classes if character restriction is enabled\n     *\n     * @return string\n     */\n    public function getExtraClass(): string\n    {\n        $class = '';\n        if ($maxLength = $this->config->getMaximumCharacterLength()) {\n            $class .= 'validate-length maximum-length-' . $maxLength;\n        }\n        return $class;\n    }\n}\n"
  },
  {
    "path": "composer.json",
    "content": "{\n  \"name\": \"boldcommerce/magento2-ordercomments\",\n  \"description\": \"Magento 2 Module to add  a comment field above the place order button in the checkout\",\n  \"require\": {\n    \"php\": \"^7.0.0|^8.0.0\"\n  },\n  \"type\": \"magento2-module\",\n  \"keywords\": [\"magento2\"],\n  \"license\": \"MIT\",\n  \"authors\": [\n    {\n      \"name\": \"Bold Commerce BV\",\n      \"email\": \"info@boldcommerce.nl\"\n    }\n  ],\n  \"autoload\": {\n    \"files\": [\n      \"registration.php\"\n    ],\n    \"psr-4\": {\n      \"Bold\\\\OrderComment\\\\\": \"\"\n    }\n  }\n}\n"
  },
  {
    "path": "etc/adminhtml/di.xml",
    "content": "<?xml version=\"1.0\"?>\n<config xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"urn:magento:framework:ObjectManager/etc/config.xsd\">\n    <type name=\"Magento\\Sales\\Block\\Adminhtml\\Order\\View\\Info\">\n        <plugin name=\"bold_ordercomment-show_comment\" type=\"Bold\\OrderComment\\Plugin\\Block\\Adminhtml\\SalesOrderViewInfo\" sortOrder=\"99999\" />\n    </type>\n</config>"
  },
  {
    "path": "etc/adminhtml/system.xml",
    "content": "<?xml version=\"1.0\"?>\n<config xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"urn:magento:module:Magento_Config:etc/system_file.xsd\">\n    <system>\n        <section id=\"sales\">\n            <group id=\"ordercomments\" translate=\"label\" type=\"text\" sortOrder=\"110\" showInDefault=\"1\" showInWebsite=\"1\" showInStore=\"0\">\n                <label>Order Comment</label>\n                <field id=\"show_in_checkout\" sortOrder=\"10\" type=\"select\" showInDefault=\"1\" showInWebsite=\"1\" showInStore=\"0\">\n                    <label>Show in checkout</label>\n                    <source_model>Magento\\Config\\Model\\Config\\Source\\Yesno</source_model>\n                    <comment>Toggle visibility of the order comments section in the checkout</comment>\n                </field>\n                <field id =\"show_in_cart\" sortOrder=\"15\" type=\"select\" showInDefault=\"1\" showInWebsite=\"1\" showInStore=\"0\">\n                    <label>Show in cart</label>\n                    <source_model>Magento\\Config\\Model\\Config\\Source\\Yesno</source_model>\n                    <comment>Toggle visibility of order comments on the cart page</comment>\n                </field>\n                <field id=\"max_length\" translate=\"label\" type=\"text\" sortOrder=\"10\" showInDefault=\"1\" showInWebsite=\"1\" showInStore=\"0\" canRestore=\"1\">\n                    <label>Maximum length in characters</label>\n                    <comment>Leave empty for no limit</comment>\n                </field>\n                <field id=\"show_in_account\" translate=\"label\" type=\"select\" sortOrder=\"20\" showInDefault=\"1\" showInWebsite=\"1\" showInStore=\"0\" canRestore=\"1\">\n                    <label>Show comments in customer account</label>\n                    <source_model>Magento\\Config\\Model\\Config\\Source\\Yesno</source_model>\n                </field>\n                <field id=\"collapse_state\" translate=\"label\" type=\"select\" sortOrder=\"30\" showInDefault=\"1\" showInWebsite=\"1\" showInStore=\"0\" canRestore=\"1\">\n                    <label>Initial collapse state on checkout page</label>\n                    <source_model>Bold\\OrderComment\\Model\\Config\\Source\\Collapse</source_model>\n                </field>\n            </group>\n        </section>\n    </system>\n</config>\n"
  },
  {
    "path": "etc/config.xml",
    "content": "<?xml version=\"1.0\"?>\n<config xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"urn:magento:module:Magento_Store:etc/config.xsd\">\n    <default>\n        <sales>\n            <ordercomments>\n                <show_in_checkout>1</show_in_checkout>\n                <show_in_account>1</show_in_account>\n                <collapse_state>0</collapse_state>\n            </ordercomments>\n        </sales>\n    </default>\n</config>\n"
  },
  {
    "path": "etc/di.xml",
    "content": "<?xml version=\"1.0\"?>\n<config xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"urn:magento:framework:ObjectManager/etc/config.xsd\">\n    <preference for=\"Bold\\OrderComment\\Api\\Data\\OrderCommentInterface\" type=\"Bold\\OrderComment\\Model\\Data\\OrderComment\" />\n    <preference for=\"Bold\\OrderComment\\Api\\OrderCommentManagementInterface\" type=\"Bold\\OrderComment\\Model\\OrderCommentManagement\" />\n    <preference for=\"Bold\\OrderComment\\Api\\GuestOrderCommentManagementInterface\" type=\"Bold\\OrderComment\\Model\\GuestOrderCommentManagement\" />\n    \n    <virtualType name=\"Magento\\Sales\\Model\\ResourceModel\\Order\\Grid\">\n        <arguments>\n            <argument name=\"columns\" xsi:type=\"array\">\n                <item name=\"bold_order_comment\" xsi:type=\"string\">sales_order.bold_order_comment</item>\n            </argument>\n        </arguments>\n    </virtualType>\n\n    <type name=\"Magento\\Sales\\Api\\OrderRepositoryInterface\">\n        <plugin name=\"bold_load_ordercomment\" type=\"Bold\\OrderComment\\Plugin\\Model\\Order\\LoadOrderComment\"/>\n    </type>\n</config>"
  },
  {
    "path": "etc/events.xml",
    "content": "<?xml version=\"1.0\"?>\n<config xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"urn:magento:framework:Event/etc/events.xsd\">\n    <event name=\"sales_model_service_quote_submit_before\">\n        <observer name=\"bold_ordercomment-add_ordercomment_to_order\" instance=\"Bold\\OrderComment\\Observer\\AddOrderCommentToOrder\" />\n    </event>\n</config>"
  },
  {
    "path": "etc/extension_attributes.xml",
    "content": "<?xml version=\"1.0\"?>\n<!--\n/**\n * Copyright © 2013-2017 Magento, Inc. All rights reserved.\n * See COPYING.txt for license details.\n */\n-->\n\n<config xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"urn:magento:framework:Api/etc/extension_attributes.xsd\">\n    <extension_attributes for=\"Magento\\Sales\\Api\\Data\\OrderInterface\">\n        <attribute code=\"bold_order_comment\" type=\"string\" />\n    </extension_attributes>\n</config>"
  },
  {
    "path": "etc/frontend/di.xml",
    "content": "<?xml version=\"1.0\"?>\n<config xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"urn:magento:framework:ObjectManager/etc/config.xsd\">\n    <type name=\"Magento\\Checkout\\Model\\CompositeConfigProvider\">\n        <arguments>\n            <argument name=\"configProviders\" xsi:type=\"array\">\n                <item name=\"ordercomments_config_provider\" xsi:type=\"object\">Bold\\OrderComment\\Model\\OrderCommentConfigProvider</item>\n            </argument>\n        </arguments>\n    </type>\n</config>"
  },
  {
    "path": "etc/frontend/routes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n/**\n * routes.xml\n *\n * @copyright Copyright © 2020 Bold Commerce BV. All rights reserved.\n * @author    dev@boldcommerce.nl\n */\n-->\n<config xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"urn:magento:framework:App/etc/routes.xsd\">\n    <router id=\"standard\">\n        <route id=\"bold_ordercomment\" frontName=\"bold_ordercomment\">\n            <module name=\"Bold_OrderComment\" />\n        </route>\n    </router>\n</config>\n"
  },
  {
    "path": "etc/module.xml",
    "content": "<?xml version=\"1.0\"?>\n<config xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"urn:magento:framework:Module/etc/module.xsd\">\n    <module name=\"Bold_OrderComment\" setup_version=\"0.0.1\">\n        <sequence>\n            <module name=\"Magento_Sales\" />\n        </sequence>\n    </module>\n</config>\n"
  },
  {
    "path": "etc/webapi.xml",
    "content": "<?xml version=\"1.0\"?>\n<routes xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"urn:magento:module:Magento_Webapi:etc/webapi.xsd\">\n\n    <!-- Managing checkout comment -->\n    <route url=\"/V1/guest-carts/:cartId/set-order-comment\" method=\"PUT\">\n        <service class=\"Bold\\OrderComment\\Api\\GuestOrderCommentManagementInterface\" method=\"saveOrderComment\"/>\n        <resources>\n            <resource ref=\"anonymous\" />\n        </resources>\n    </route>\n\n    <!-- Managing checkout comment -->\n    <route url=\"/V1/carts/mine/set-order-comment\" method=\"PUT\">\n        <service class=\"Bold\\OrderComment\\Api\\OrderCommentManagementInterface\" method=\"saveOrderComment\"/>\n        <resources>\n            <resource ref=\"self\" />\n        </resources>\n        <data>\n            <parameter name=\"cartId\" force=\"true\">%cart_id%</parameter>\n        </data>\n    </route>\n</routes>"
  },
  {
    "path": "i18n/ar_SA.csv",
    "content": "\"Do you have any comments regarding the order?\",\"هل لديك اي تعليق بخصوص لطلبك؟\"\n\"Enter your comment...\",\"اكتب تعليقك\"\n\"Order Comment\",\"تعليق الطلب\"\n\"The order comment could not be saved\",\"تعليق الطلب لا يمكن حفظه\"\n\"Cart %1 doesn't contain products\",\"سلة المشتريات لا تحتوي على منتجات\""
  },
  {
    "path": "i18n/bn_BD.csv",
    "content": "\"Do you have any comments regarding the order?\",\"আপনার কি এই অর্ডার সংক্রান্ত কোন মন্তব্য আছে?\"\n\"Enter your comment...\",\"আপনার মন্তব্য লিখুন...\"\n\"Order Comment\",\"অর্ডার মন্তব্য\"\n\"The order comment could not be saved\",\"অর্ডার মন্তব্যটি সংরক্ষণ করা যায়নি\"\n\"Cart %1 doesn't contain products\",\"কার্ট %1 তে কোন পন্য নাই\"\n"
  },
  {
    "path": "i18n/cs_CZ.csv",
    "content": "\"Do you have any comments regarding the order?\",\"Máte k objednávce nějakou poznámku?\"\n\"Enter your comment...\",\"Vložte poznámku...\"\n\"Order Comment\",\"Poznámky k objednávce\"\n\"The order comment could not be saved\",\"Poznámka k objednávce nemohla být uložena\"\n\"Cart %1 doesn't contain products\",\"V košíku %1 nejsou žádné produkty\"\n"
  },
  {
    "path": "i18n/de_CH.csv",
    "content": "\"Do you have any comments regarding the order?\",\"Haben Sie Anmerkungen zur Bestellung?\"\n\"Enter your comment...\",\"Anmerkungen eingeben...\"\n\"Order Comment\",\"Anmerkungen\"\n\"The order comment could not be saved\",\"Die Anmerkungen zur Bestellung konnten nicht gespeichert werden\"\n\"Cart %1 doesn't contain products\",\"Warenkorb %1 enthält keine Produkte\"\n"
  },
  {
    "path": "i18n/de_DE.csv",
    "content": "\"Do you have any comments regarding the order?\",\"Haben Sie Anmerkungen zur Bestellung?\"\n\"Enter your comment...\",\"Anmerkungen eingeben...\"\n\"Order Comment\",\"Anmerkungen\"\n\"The order comment could not be saved\",\"Die Anmerkungen zur Bestellung konnten nicht gespeichert werden\"\n\"Cart %1 doesn't contain products\",\"Warenkorb %1 enthält keine Produkte\"\n"
  },
  {
    "path": "i18n/el_GR.csv",
    "content": "\"Do you have any comments regarding the order?\",\"Έχετε παρατηρήσεις σχετικά με την παραγγελία;\"\n\"Enter your comment...\",\"Γράψτε τις παρατηρήσεις σας ...\"\n\"Order Comment\",\"Παρατηρήσεις\"\n\"The order comment could not be saved\",\"Οι παρατηρήσεις σας δεν μπόρεσαν να αποθηκευτούν\"\n\"Cart %1 doesn't contain products\",\"Το καλάθι %1 δεν περιέχει προϊόντα\"\n"
  },
  {
    "path": "i18n/es_ES.csv",
    "content": "\"Your comment has been saved.\",\"Se ha guardado el comentario.\"\n\"There was an error when updating the quote.\",\"Ha ocurrido un error al actualizar el carrito.\"\n\"Starts with field closed\",\"Iniciar con el campo cerrado\"\n\"Starts with field opened\",\"Iniciar con el campo abierto\"\n\"Render field without collapse\",\"Renderizar campo sin que se pueda plegar\"\n\"Cart %1 doesn't contain products\",\"El carrito %1 no contiene productos\"\n\"The order comment could not be saved\",\"No se ha podido guardar el comentario\"\n\"Comment is too long\",\"El comentario es demasiado largo\"\n\"Order Comment\",\"Comentario de pedido\"\n\"Edit Order Comment\",\"Comentario de pedido\"\n\"Do you have any comments regarding the order?\",\"¿Tienes algún comentario sobre el pedido?\"\n\"Enter your comment...\",\"Introduce tu comentario...\"\n\"Save Comment\",\"Guardar comentario\"\n\"Enter comment\",\"Introducir comentario\"\n\"Remaining characters:\",\"Caracteres restantes:\"\n\"Maximum length in characters\",\"Carecteres máximos\"\n\"Show comments in customer account\",\"Mostrar comentario en cuenta de cliente\"\n\"Initial collapse state on checkout page\",\"Estado inicial del campo en el proceso de compra\""
  },
  {
    "path": "i18n/fr_FR.csv",
    "content": "\"Do you have any comments regarding the order?\",\"Avez-vous un commentaire à nous transmettre au sujet de cette commande ?\"\n\"Enter your comment...\",\"Saisissez votre commentaire ici...\"\n\"Order Comment\",\"Commentaire de commande\"\n\"The order comment could not be saved\",\"Le commentaire de cette commande ne peut pas être enregistré\"\n\"Cart %1 doesn't contain products\",\"Le panier %1 ne contient aucun article\"\n\"Remaining characters:\",\"Caractères restants :\"\n"
  },
  {
    "path": "i18n/he_IL.csv",
    "content": "\"Do you have any comments regarding the order?\",\"האם יש לכם הערה בנוגע להזמנה?\"\n\"Enter your comment...\",\"הוסיפו את ההערה שלכם...\"\n\"Order Comment\",\"הערת הזמנה\"\n\"The order comment could not be saved\",\"לא ניתן לשמור את ההערה\"\n\"Cart %1 doesn't contain products\",\"העגלה %1 לא מכילה מוצרים\"\n"
  },
  {
    "path": "i18n/hu_HU.csv",
    "content": "\"Do you have any comments regarding the order?\",\"Üzenet a futárnak:\"\n\"Enter your comment...\",\"Például kapucsengő, kapu, egyéb pontosítás...\"\n\"Order Comment\",\"Rendelés megjegyzés\"\n\"The order comment could not be saved\",\"A rendelés megjegyzést nem lehet elmenteni.\"\n\"Cart %1 doesn't contain products\",\"A kosárban nincs %1 termék\"\n\"Remaining characters:\",\"Üzenet hossz:\"\n"
  },
  {
    "path": "i18n/it_IT.csv",
    "content": "\"Do you have any comments regarding the order?\",\"Hai delle richieste in merito all'ordine?\"\n\"Enter your comment...\",\"Inserisci qui i commenti...\"\n\"Order Comment\",\"Commenti ordine\"\n\"The order comment could not be saved\",\"La tua richiesta al momento non puo' essere salvata\"\n\"Cart %1 doesn't contain products\",\"Il carrello %1 non contiene prodotti\"\n"
  },
  {
    "path": "i18n/ja_JP.csv",
    "content": "\"Do you have any comments regarding the order?\",\"注文に関して何かコメントはありますか？\"\n\"Enter your comment...\",\"コメントを入力してください...\"\n\"Order Comment\",\"注文コメント\"\n\"The order comment could not be saved\",\"注文コメントを保存できませんでした\"\n\"Cart %1 doesn't contain products\",\"カート%1には商品が含まれていません\"\n"
  },
  {
    "path": "i18n/nl_NL.csv",
    "content": "\"Do you have any comments regarding the order?\",\"Heb je nog opmerkingen over je bestelling?\"\n\"Enter your comment...\",\"Opmerkingen toevoegen...\"\n\"Order Comment\",\"Opmerkingen\"\n\"The order comment could not be saved\",\"De opmerkingen over je bestelling kunnen niet opgeslagen worden\"\n\"Cart %1 doesn't contain products\",\"Winkelwagen %1 bevat geen producten\"\n"
  },
  {
    "path": "i18n/pl_PL.csv",
    "content": "\"Do you have any comments regarding the order?\",\"Dodaj komentarz do zamówienia\"\n\"Enter your comment...\",\"Twój komentarz...\"\n\"Order Comment\",\"Komentarz do zamówienia\"\n\"The order comment could not be saved\",\"Twój komentarz nie został zapisany\"\n\"Cart %1 doesn't contain products\",\"Koszyk %1 nie zawiera produktów\"\n"
  },
  {
    "path": "i18n/sl_SI.csv",
    "content": "\"Do you have any comments regarding the order?\",\"Želite dodati komentar k vašemu naročilu?\"\n\"Enter your comment...\",\"Vnesite svoj komentar...\"\n\"Order Comment\",\"Komentar k naročilu\"\n\"The order comment could not be saved\",\"Komentarja naročila ni bilo mogoče shraniti\"\n\"Cart %1 doesn't contain products\",\"Košarica %1 je prazna\"\n"
  },
  {
    "path": "i18n/sv_SE.csv",
    "content": "\"Do you have any comments regarding the order?\",\"Vill du lämna en kommentar angående din order?\"\n\"Enter your comment...\",\"Lämna din kommentar...\"\n\"Order Comment\",\"Orderkommentar\"\n\"The order comment could not be saved\",\"Din kommentar kunde inte sparas\"\n\"Cart %1 doesn't contain products\",\"Varukorg %1 innehåller inga produkter\"\n"
  },
  {
    "path": "i18n/th_TH.csv",
    "content": "\"Do you have any comments regarding the order?\",\"คุณมีความคิดเห็นเกี่ยวกับคำสั่งซื้อหรือไม่?\"\n\"Enter your comment...\",\"ใส่ความคิดเห็นของคุณที่นี่ ...\"\n\"Order Comment\",\"ความเห็นของคำสั่งซื้อนี้\"\n\"The order comment could not be saved\",\"ไม่สามารถบันทึกความคิดเห็นของคำสั่งซื้อนี้\"\n\"Cart %1 doesn't contain products\",\"รถเข็น %1 ไม่มีสินค้า\"\n"
  },
  {
    "path": "registration.php",
    "content": "<?php\n    \\Magento\\Framework\\Component\\ComponentRegistrar::register(\n        \\Magento\\Framework\\Component\\ComponentRegistrar::MODULE,\n        'Bold_OrderComment',\n        __DIR__\n    );\n"
  },
  {
    "path": "view/adminhtml/layout/sales_order_view.xml",
    "content": "<?xml version=\"1.0\"?>\n<page xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"urn:magento:framework:View/Layout/etc/page_configuration.xsd\">\n    <body>\n        <block class=\"Magento\\Backend\\Block\\Template\" name=\"boldcommerce_order_comments\" template=\"Bold_OrderComment::order/view/comments.phtml\" />\n    </body>\n</page>\n"
  },
  {
    "path": "view/adminhtml/templates/order/view/comments.phtml",
    "content": "<?php if($comment = $block->getOrderComment()):?>\n    <section class=\"admin__page-section\">\n        <div class=\"admin__page-section-title\">\n            <span class=\"title\"><?php /* @escapeNotVerified */ echo __('Order Comment') ?></span>\n        </div>\n        <div class=\"admin__page-section-content\">\n            <?php echo nl2br($block->escapeHtml($comment));?>\n        </div>\n    </section>\n<?php endif; ?>\n"
  },
  {
    "path": "view/adminhtml/ui_component/sales_order_grid.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<listing xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"urn:magento:module:Magento_Ui:etc/ui_configuration.xsd\">\n    <columns name=\"sales_order_columns\">\n        <column name=\"bold_order_comment\">\n            <argument name=\"data\" xsi:type=\"array\">\n                <item name=\"js_config\" xsi:type=\"array\">\n                    <item name=\"component\" xsi:type=\"string\">Magento_Ui/js/grid/columns/column</item>\n                </item>\n                <item name=\"config\" xsi:type=\"array\">\n                    <item name=\"filter\" xsi:type=\"string\">text</item>\n                    <item name=\"visible\" xsi:type=\"boolean\">true</item>\n                    <item name=\"label\" xsi:type=\"string\" translate=\"true\">Order Comment</item>\n                </item>\n            </argument>\n        </column>\n    </columns>\n</listing>"
  },
  {
    "path": "view/frontend/layout/checkout_cart_index.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n/**\n * checkout_cart_index.xml\n *\n * @copyright Copyright © 2020 Bold Commerce BV. All rights reserved.\n * @author    dev@boldcommerce.nl\n */\n-->\n<page xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"urn:magento:framework:View/Layout/etc/page_configuration.xsd\">\n    <body>\n        <referenceContainer name=\"checkout.cart.container\">\n            <block name=\"sales.order.comment\" after=\"cart.discount\" template=\"Bold_OrderComment::cart/comment.phtml\" ifconfig=\"sales/ordercomments/show_in_cart\">\n                <arguments>\n                    <argument name=\"comment_view_model\" xsi:type=\"object\">Bold\\OrderComment\\ViewModel\\Comment</argument>\n                </arguments>\n            </block>\n        </referenceContainer>\n    </body>\n</page>\n"
  },
  {
    "path": "view/frontend/layout/checkout_index_index.xml",
    "content": "<?xml version=\"1.0\"?>\n<page xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"urn:magento:framework:View/Layout/etc/page_configuration.xsd\">\n    <body>\n        <referenceBlock name=\"checkout.root\">\n            <arguments>\n                <argument name=\"jsLayout\" xsi:type=\"array\">\n                    <item name=\"components\" xsi:type=\"array\">\n                        <item name=\"checkout\" xsi:type=\"array\">\n                            <item name=\"children\" xsi:type=\"array\">\n                                <item name=\"steps\" xsi:type=\"array\">\n                                    <item name=\"children\" xsi:type=\"array\">\n                                        <item name=\"billing-step\" xsi:type=\"array\">\n                                            <item name=\"children\" xsi:type=\"array\">\n                                                <item name=\"payment\" xsi:type=\"array\">\n                                                    <item name=\"children\" xsi:type=\"array\">\n                                                        <item name=\"additional-payment-validators\" xsi:type=\"array\">\n                                                            <item name=\"children\" xsi:type=\"array\">\n                                                                <item name=\"order-comment-validator\" xsi:type=\"array\">\n                                                                    <item name=\"component\" xsi:type=\"string\">Bold_OrderComment/js/view/checkout/order-comment-validator</item>\n                                                                </item>\n                                                            </item>\n                                                        </item>\n                                                        <item name=\"payments-list\" xsi:type=\"array\">\n                                                            <item name=\"children\" xsi:type=\"array\">\n                                                                <item name=\"before-place-order\" xsi:type=\"array\">\n                                                                    <item name=\"children\" xsi:type=\"array\">\n                                                                        <item name=\"comment\" xsi:type=\"array\">\n                                                                            <item name=\"component\" xsi:type=\"string\">Bold_OrderComment/js/view/checkout/order-comment-block</item>\n                                                                        </item>\n                                                                    </item>\n                                                                </item>\n                                                            </item>\n                                                        </item>\n                                                    </item>\n                                                </item>\n                                            </item>\n                                        </item>\n                                    </item>\n                                </item>\n                            </item>\n                        </item>\n                    </item>\n                </argument>\n            </arguments>\n        </referenceBlock>\n    </body>\n</page>\n"
  },
  {
    "path": "view/frontend/layout/sales_order_view.xml",
    "content": "<?xml version=\"1.0\"?>\n<page xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"urn:magento:framework:View/Layout/etc/page_configuration.xsd\">\n    <body>\n        <referenceContainer name=\"content\">\n            <block ifconfig=\"sales/ordercomments/show_in_account\" class=\"Bold\\OrderComment\\Block\\Order\\Comment\" as=\"ordercomment\" name=\"sales.order.comment\" after=\"sales.order.info\"/>\n        </referenceContainer>\n    </body>\n</page>\n"
  },
  {
    "path": "view/frontend/templates/cart/comment.phtml",
    "content": "<?php\n    /** @var \\Bold\\OrderComment\\ViewModel\\Comment $viewModel */\n    $viewModel = $block->getData('comment_view_model');\n    $oldComment = $viewModel ? $viewModel->getComment() : '';\n    $hasComment = !!$oldComment;\n    $extraValidationClasses = $viewModel ? $viewModel->getExtraClass() : '';\n?>\n\n<div class=\"cart-order-comment\">\n    <div class=\"block order-comment\"\n         id=\"block-order-comment\"\n         data-mage-init='{\"collapsible\":{\"active\": <?= $hasComment ? 'true' : 'false' ?>, \"openedState\": \"active\", \"saveState\": false}}'>\n        <div class=\"title\" data-role=\"title\">\n            <strong id=\"block-order-comment-heading\" role=\"heading\" aria-level=\"2\"><?= $block->escapeHtml(__('Edit Order Comment')) ?></strong>\n        </div>\n        <div class=\"content\" data-role=\"content\" aria-labelledby=\"block-order-comment-heading\">\n            <form id=\"order-comment-form\"\n                  action=\"<?= $block->escapeUrl($block->getUrl('bold_ordercomment/cart/updateComment')) ?>\"\n                  data-mage-init='{\"validation\": {}}'\n                  method=\"post\">\n                <div class=\"fieldset order-comment<?= $hasComment ? ' applied' : '' ?>\">\n                    <input type=\"hidden\" name=\"remove\" id=\"remove-comment\" value=\"0\" />\n                    <div class=\"field\">\n                        <label for=\"order_comment\" class=\"label\"><span><?= $block->escapeHtml(__('Do you have any comments regarding the order?')) ?></span></label>\n                        <div class=\"control\">\n                            <textarea class=\"input-text order-comment order-comment-input<?= $extraValidationClasses ? ' ' . $extraValidationClasses : ''?>\"\n                                      name=\"order_comment\"\n                                      rows=\"4\"\n                                      placeholder=\"<?= $block->escapeHtml(__('Enter your comment...')) ?>\"\n                            ><?= $oldComment ?></textarea>\n                        </div>\n                    </div>\n                    <div class=\"actions-toolbar\">\n                        <div class=\"primary\">\n                            <button class=\"action apply-comment primary\" type=\"submit\" value=\"<?= $block->escapeHtmlAttr(__('Save Comment')) ?>\">\n                                <span><?= $block->escapeHtml(__('Save Comment')) ?></span>\n                            </button>\n                        </div>\n                    </div>\n                </div>\n            </form>\n        </div>\n    </div>\n</div>\n"
  },
  {
    "path": "view/frontend/templates/order/view/comment.phtml",
    "content": "<?php\n/** @var \\Bold\\OrderComment\\Block\\Order\\Comment $block */\n?>\n<?php if($comment = $block->getOrderComment()):?>\n<div class=\"block block-order-details-view\">\n    <div class=\"block-content\">\n        <div class=\"box box-order-comment\">\n            <strong class=\"box-title\"><span><?php /* @escapeNotVerified */ echo __('Order Comment') ?></span></strong>\n            <div class=\"box-content\">\n               <?php echo $block->getOrderCommentHtml();?>\n            </div>\n        </div>\n    </div>\n</div>\n<?php endif; ?>"
  },
  {
    "path": "view/frontend/web/css/source/_module.less",
    "content": "._error {\n  .order-comment-input {\n    outline: none;\n    border: 2px solid @checkout-field-validation__border-color;\n    &:focus {\n      outline: none;\n      border: 2px solid @checkout-field-validation__border-color;\n      box-shadow: 0 0 3px @checkout-field-validation__border-color;\n    }\n  }\n}\n\n.checkout-payment-method {\n  .payment-option._collapsible.comment {\n    .payment-option-content {\n      display: block;\n    }\n  }\n}\n\n& when (@media-common = true) {\n    .cart-order-comment {\n        border-bottom: @border-width__base solid @border-color__base;\n        clear: left;\n        &:extend(.abs-discount-block all);\n        .fieldset.order-comment {\n            display: block;\n\n            &>div {\n                display: block;\n            }\n        }\n    }\n}\n\n//mobile\n.media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__m) {\n    .cart-order-comment {\n        border-bottom: @border-width__base solid @border-color__base;\n        .block>.title {\n            border: 0;\n        }\n    }\n}\n//desktop\n.media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__m) {\n    .cart-order-comment {\n        &:extend(.abs-discount-block-desktop all);\n        .lib-layout-column(2, 1, @layout-column-checkout__width-main);\n        border: 0;\n        box-sizing: border-box;\n        padding-right: 4%;\n\n        .block {\n            .title {\n                padding: 10px 0;\n                &:after {\n                    display: inline;\n                    margin-left: @indent__s;\n                    position: static;\n                }\n            }\n            &.order-comment {\n                width: 100%;\n                border-bottom: @border-width__base solid @border-color__base;\n                border-top: @border-width__base solid @border-color__base;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "view/frontend/web/js/model/checkout/order-comment-validator.js",
    "content": "define(\n    [\n        'jquery',\n        'Magento_Customer/js/model/customer',\n        'Magento_Checkout/js/model/quote',\n        'Magento_Checkout/js/model/url-builder',\n        'mage/url',\n        'Magento_Checkout/js/model/error-processor',\n        'Magento_Ui/js/model/messageList',\n        'mage/translate'\n    ],\n    function ($, customer, quote, urlBuilder, urlFormatter, errorProcessor, messageContainer, __) {\n        'use strict';\n\n        return {\n\n            /**\n             * Make an ajax PUT request to store the order comment in the quote.\n             *\n             * @returns {Boolean}\n             */\n            validate: function () {\n                var isCustomer = customer.isLoggedIn();\n                var form = this.getForm();\n\n                var comment = form.find('.input-text.order-comment').val();\n                if (this.hasMaxLength() && comment.length > this.getMaxLength()) {\n                    messageContainer.addErrorMessage({ message: __(\"Comment is too long\") });\n                    return false;\n                }\n\n                var quoteId = quote.getQuoteId();\n\n                var url;\n                if (isCustomer) {\n                    url = urlBuilder.createUrl('/carts/mine/set-order-comment', {})\n                } else {\n                    url = urlBuilder.createUrl('/guest-carts/:cartId/set-order-comment', {cartId: quoteId});\n                }\n\n                var payload = {\n                    cartId: quoteId,\n                    orderComment: {\n                        comment: comment\n                    }\n                };\n\n                if (!payload.orderComment.comment) {\n                    return true;\n                }\n\n                var result = true;\n\n                $.ajax({\n                    url: urlFormatter.build(url),\n                    data: JSON.stringify(payload),\n                    global: false,\n                    contentType: 'application/json',\n                    type: 'PUT',\n                    async: false\n                }).done(\n                    function (response) {\n                        result = true;\n                    }\n                ).fail(\n                    function (response) {\n                        result = false;\n                        errorProcessor.process(response);\n                    }\n                );\n\n                return result;\n            },\n            getForm: function () {\n                var form =  $('.payment-method input[name=\"payment[method]\"]:checked')\n                    .parents('.payment-method')\n                    .find('form.order-comment-form');\n\n                // Compatibility for Rubic_CleanCheckout\n                if (!form.length) {\n                    form = $('form.order-comment-form');\n                }\n                \n                return form;\n            },\n            hasMaxLength: function () {\n                return window.checkoutConfig.max_length > 0;\n            },\n            getMaxLength: function () {\n                return window.checkoutConfig.max_length;\n            }\n        };\n    }\n);\n"
  },
  {
    "path": "view/frontend/web/js/view/checkout/order-comment-block.js",
    "content": "define(\n    [\n        'jquery',\n        'uiComponent',\n        'knockout'\n    ],\n    function ($, Component, ko) {\n        'use strict';\n\n        ko.extenders.maxOrderCommentLength = function (target, maxLength) {\n            var timer;\n            var result = ko.computed({\n                read: target,\n                write: function (val) {\n                    if (maxLength > 0) {\n                        clearTimeout(timer);\n                        if (val.length > maxLength) {\n                            var limitedVal = val.substring(0, maxLength);\n                            if (target() === limitedVal) {\n                                target.notifySubscribers();\n                            } else {\n                                target(limitedVal);\n                            }\n                            result.css(\"_error\");\n                            timer = setTimeout(function () { result.css(\"\"); }, 800);\n                        } else {\n                            target(val);\n                            result.css(\"\");\n                        }\n                    } else {\n                        target(val);\n                    }\n                }\n            }).extend({ notify: 'always' });\n            result.css = ko.observable();\n            result(target());\n            return result;\n        };\n\n        function getExistingComment() {\n            return window.checkoutConfig.existing_comment;\n        }\n\n        return Component.extend({\n            defaults: {\n                template: 'Bold_OrderComment/checkout/order-comment-block'\n            },\n            initialize: function() {\n                this._super();\n                var self = this;\n                this.comment = ko.observable(getExistingComment()).extend({maxOrderCommentLength: this.getMaxLength()});\n\n                this.remainingCharacters = ko.computed(function(){\n                    return self.getMaxLength() - self.comment().length;\n                });\n            },\n            showInCheckout: function() {\n                return window.checkoutConfig.show_in_checkout;\n            },\n            hasMaxLength: function() {\n                return window.checkoutConfig.max_length > 0;\n            },\n            getMaxLength: function () {\n                return window.checkoutConfig.max_length;\n            },\n            getInitialCollapseState: function() {\n                return window.checkoutConfig.comment_initial_collapse_state;\n            },\n            isInitialStateOpened: function() {\n                return this.getInitialCollapseState() === 1\n            }\n        });\n    }\n);\n"
  },
  {
    "path": "view/frontend/web/js/view/checkout/order-comment-validator.js",
    "content": "define(\n    [\n        'uiComponent',\n        'Magento_Checkout/js/model/payment/additional-validators',\n        'Bold_OrderComment/js/model/checkout/order-comment-validator'\n    ],\n    function (Component, additionalValidators, commentValidator) {\n        'use strict';\n\n        additionalValidators.registerValidator(commentValidator);\n\n        return Component.extend({});\n    }\n);"
  },
  {
    "path": "view/frontend/web/template/checkout/form-content.html",
    "content": "<form class=\"form form-discount order-comment-form\" data-bind=\"mageInit: { 'validation':[]}\">\n    <div class=\"payment-option-inner\">\n        <div class=\"field\" data-bind=\"css: comment.css()\">\n            <label class=\"label\">\n                <span data-bind=\"i18n: 'Enter comment'\"></span>\n            </label>\n            <div class=\"control\">\n                <textarea class=\"input-text order-comment order-comment-input\" name=\"comment-code\" rows=\"4\" data-bind=\"value: comment,valueUpdate: 'afterkeydown',attr:{placeholder: $t('Enter your comment...')} \" ></textarea>\n                <p data-bind=\"if: hasMaxLength()\"><span data-bind=\"i18n: 'Remaining characters:'\"></span> <span class=\"order-comment-form__characters\" data-bind=\"text: remainingCharacters\"></span></p>\n            </div>\n        </div>\n    </div>\n</form>\n"
  },
  {
    "path": "view/frontend/web/template/checkout/order-comment-block.html",
    "content": "<!-- ko if: showInCheckout() -->\n\n<!-- ko if: getInitialCollapseState() == 2 -->\n<div class=\"payment-option opc-payment-additional comment last\">\n    <div class=\"payment-option-title field choice\">\n        <span data-bind=\"i18n: 'Do you have any comments regarding the order?'\"></span>\n    </div>\n    <div class=\"payment-option-content\">\n        <!-- ko template: 'Bold_OrderComment/checkout/form-content' --><!-- /ko -->\n    </div>\n</div>\n<!-- /ko -->\n\n<!-- ko if: getInitialCollapseState() != 2 -->\n<div class=\"payment-option _collapsible opc-payment-additional comment last\"\n     data-bind=\"mageInit: {'collapsible':{'openedState': '_active', 'active': isInitialStateOpened()}}\">\n\n    <div class=\"payment-option-title field choice\" data-role=\"title\">\n        <span class=\"action action-toggle\" role=\"heading\" aria-level=\"2\">\n            <!-- ko i18n: 'Do you have any comments regarding the order?'--><!-- /ko -->\n        </span>\n    </div>\n    <div class=\"payment-option-content\" data-role=\"content\">\n        <!-- ko template: 'Bold_OrderComment/checkout/form-content' --><!-- /ko -->\n    </div>\n</div>\n<!-- /ko -->\n\n<!-- /ko -->\n"
  }
]