From 3b7a0258afbf7474ca86c1fb943fcf5ad4018f82 Mon Sep 17 00:00:00 2001 From: Marcin Materzok Date: Mon, 5 Sep 2022 17:13:04 +0200 Subject: [PATCH] Version 1.0.0 --- Api/Json/BaseInterface.php | 33 ++ Api/Json/OrderInterface.php | 33 ++ Api/Json/ProductInterface.php | 19 ++ Api/Json/ShipmentInterface.php | 30 ++ Api/OrderStatusInterface.php | 21 ++ Block/Adminhtml/Form/Field/DeliveryColumn.php | 77 +++++ .../Adminhtml/Form/Field/DeliveryOptions.php | 73 +++++ .../Form/Field/OrderMagentoColumn.php | 78 +++++ .../Form/Field/OrderMappingOptions.php | 89 +++++ .../Form/Field/OrderSchemaColumn.php | 76 +++++ .../Adminhtml/Form/Field/TrackingOptions.php | 73 +++++ LICENSE_MTRZK | 21 ++ Model/Config.php | 309 ++++++++++++++++++ Model/Config/Backend/Serialized.php | 91 ++++++ Model/Config/Source/EmailMx.php | 52 +++ Model/Config/Source/SchemaOrderTypes.php | 54 +++ Model/Config/Source/UrlType.php | 50 +++ Model/EmailLookup.php | 90 +++++ Model/Processor/AbstractProcessor.php | 168 ++++++++++ Model/Processor/OrderProcessor.php | 137 ++++++++ Model/Processor/ShipmentProcessor.php | 166 ++++++++++ Model/Renderer/DeliveryName.php | 57 ++++ Model/Renderer/OrderStatus.php | 48 +++ Model/Renderer/TrackingNumber.php | 78 +++++ Observer/OrderEmailObserver.php | 66 ++++ Observer/ShipmentEmailObserver.php | 70 ++++ README.md | 45 +++ composer.json | 29 ++ etc/adminhtml/system.xml | 118 +++++++ etc/config.xml | 27 ++ etc/di.xml | 6 + etc/events.xml | 9 + etc/module.xml | 8 + registration.php | 13 + 34 files changed, 2314 insertions(+) create mode 100644 Api/Json/BaseInterface.php create mode 100644 Api/Json/OrderInterface.php create mode 100644 Api/Json/ProductInterface.php create mode 100644 Api/Json/ShipmentInterface.php create mode 100644 Api/OrderStatusInterface.php create mode 100644 Block/Adminhtml/Form/Field/DeliveryColumn.php create mode 100644 Block/Adminhtml/Form/Field/DeliveryOptions.php create mode 100644 Block/Adminhtml/Form/Field/OrderMagentoColumn.php create mode 100644 Block/Adminhtml/Form/Field/OrderMappingOptions.php create mode 100644 Block/Adminhtml/Form/Field/OrderSchemaColumn.php create mode 100644 Block/Adminhtml/Form/Field/TrackingOptions.php create mode 100644 LICENSE_MTRZK create mode 100644 Model/Config.php create mode 100644 Model/Config/Backend/Serialized.php create mode 100644 Model/Config/Source/EmailMx.php create mode 100644 Model/Config/Source/SchemaOrderTypes.php create mode 100644 Model/Config/Source/UrlType.php create mode 100644 Model/EmailLookup.php create mode 100644 Model/Processor/AbstractProcessor.php create mode 100644 Model/Processor/OrderProcessor.php create mode 100644 Model/Processor/ShipmentProcessor.php create mode 100644 Model/Renderer/DeliveryName.php create mode 100644 Model/Renderer/OrderStatus.php create mode 100644 Model/Renderer/TrackingNumber.php create mode 100644 Observer/OrderEmailObserver.php create mode 100644 Observer/ShipmentEmailObserver.php create mode 100644 README.md create mode 100644 composer.json create mode 100644 etc/adminhtml/system.xml create mode 100644 etc/config.xml create mode 100644 etc/di.xml create mode 100644 etc/events.xml create mode 100644 etc/module.xml create mode 100644 registration.php diff --git a/Api/Json/BaseInterface.php b/Api/Json/BaseInterface.php new file mode 100644 index 0000000..d8badeb --- /dev/null +++ b/Api/Json/BaseInterface.php @@ -0,0 +1,33 @@ +allmethods = $allmethods; + } + + /** + * @param string $value + * + * @return $this + */ + public function setInputName($value) + { + /* @phpstan-ignore-next-line */ + return $this->setName($value); + } + + /** + * @param mixed $value + * + * @return $this + */ + public function setInputId($value) + { + return $this->setId($value); + } + + /** + * @return string + */ + public function _toHtml(): string + { + if (!$this->getOptions()) { + $this->setOptions($this->getSourceOptions()); + } + + return parent::_toHtml(); + } + + /** + * @return array + */ + private function getSourceOptions(): array + { + return $this->allmethods->toOptionArray(); + } +} diff --git a/Block/Adminhtml/Form/Field/DeliveryOptions.php b/Block/Adminhtml/Form/Field/DeliveryOptions.php new file mode 100644 index 0000000..56fab7c --- /dev/null +++ b/Block/Adminhtml/Form/Field/DeliveryOptions.php @@ -0,0 +1,73 @@ +addColumn('delivery_method', [ + 'label' => __('Delivery Method'), + 'renderer' => $this->getDeliveryMethodRenderer() + ]); + + $this->addColumn('delivery_name', [ + 'label' => __('Delivery JSON Name'), + 'class' => 'required-entry' + ]); + + $this->_addAfter = false; + $this->_addButtonLabel = __('Add')->render(); + } + + /** + * @return Types|BlockInterface + * @throws LocalizedException + */ + private function getDeliveryMethodRenderer() + { + $this->typeRenderer = $this->getLayout()->createBlock( + DeliveryColumn::class, + '', + ['data' => ['is_render_to_js_template' => true]] + ); + + return $this->typeRenderer; + } + + /** + * @param DataObject $row + * + * @throws LocalizedException + */ + protected function _prepareArrayRow(DataObject $row): void + { + $options = []; + $type = $row->getDeliveryMethod(); + + if ($type !== null) { + /* @phpstan-ignore-next-line */ + $options['option_' . $this->getDeliveryMethodRenderer()->calcOptionHash($type)] = 'selected="selected"'; + } + + $row->setData('option_extra_attrs', $options); + } +} diff --git a/Block/Adminhtml/Form/Field/OrderMagentoColumn.php b/Block/Adminhtml/Form/Field/OrderMagentoColumn.php new file mode 100644 index 0000000..5e502dc --- /dev/null +++ b/Block/Adminhtml/Form/Field/OrderMagentoColumn.php @@ -0,0 +1,78 @@ +orderStatusCollection = $orderStatusCollection; + } + + /** + * @param string $value + * + * @return $this + */ + public function setInputName($value) + { + /* @phpstan-ignore-next-line */ + return $this->setName($value); + } + + /** + * @param mixed $value + * + * @return $this + */ + public function setInputId($value) + { + return $this->setId($value); + } + + /** + * @return string + */ + public function _toHtml(): string + { + if (!$this->getOptions()) { + $this->setOptions($this->getSourceOptions()); + } + + return parent::_toHtml(); + } + + /** + * @return array + */ + private function getSourceOptions(): array + { + return $this->orderStatusCollection->toOptionArray(); + } +} diff --git a/Block/Adminhtml/Form/Field/OrderMappingOptions.php b/Block/Adminhtml/Form/Field/OrderMappingOptions.php new file mode 100644 index 0000000..6f48d8e --- /dev/null +++ b/Block/Adminhtml/Form/Field/OrderMappingOptions.php @@ -0,0 +1,89 @@ +addColumn('order_status', [ + 'label' => __('Magento 2 Order Status'), + 'renderer' => $this->getOrderStatusRenderer() + ]); + $this->addColumn('schema_status', [ + 'label' => __('Schema Order Status'), + 'renderer' => $this->getSchemaStatusRenderer() + ]); + $this->_addAfter = false; + $this->_addButtonLabel = __('Add')->render(); + } + + /** + * @return BlockInterface + * @throws LocalizedException + */ + private function getOrderStatusRenderer() + { + $this->orderStatusRenderer = $this->getLayout()->createBlock( + OrderMagentoColumn::class, + '', + ['data' => ['is_render_to_js_template' => true]] + ); + + return $this->orderStatusRenderer; + } + + /** + * @return BlockInterface + * @throws LocalizedException + */ + private function getSchemaStatusRenderer() + { + $this->schemaStatusRenderer = $this->getLayout()->createBlock( + OrderSchemaColumn::class, + '', + ['data' => ['is_render_to_js_template' => true]] + ); + + return $this->schemaStatusRenderer; + } + + /** + * @param DataObject $row + * + * @throws LocalizedException + */ + protected function _prepareArrayRow(DataObject $row): void + { + $options = []; + $type = $row->getSchemaStatus(); + + if ($type !== null) { + /* @phpstan-ignore-next-line */ + $options['option_' . $this->getSchemaStatusRenderer()->calcOptionHash($type)] = 'selected="selected"'; + } + + $row->setData('option_extra_attrs', $options); + } +} diff --git a/Block/Adminhtml/Form/Field/OrderSchemaColumn.php b/Block/Adminhtml/Form/Field/OrderSchemaColumn.php new file mode 100644 index 0000000..596b674 --- /dev/null +++ b/Block/Adminhtml/Form/Field/OrderSchemaColumn.php @@ -0,0 +1,76 @@ +schemaOrderTypes = $schemaOrderTypes; + } + + /** + * @param string $value + * + * @return $this + */ + public function setInputName($value) + { + /* @phpstan-ignore-next-line */ + return $this->setName($value); + } + + /** + * @param mixed $value + * + * @return $this + */ + public function setInputId($value) + { + return $this->setId($value); + } + + /** + * @return string + */ + public function _toHtml(): string + { + if (!$this->getOptions()) { + $this->setOptions($this->getSourceOptions()); + } + + return parent::_toHtml(); + } + + /** + * @return array + */ + private function getSourceOptions(): array + { + return $this->schemaOrderTypes->toOptionArray(); + } +} diff --git a/Block/Adminhtml/Form/Field/TrackingOptions.php b/Block/Adminhtml/Form/Field/TrackingOptions.php new file mode 100644 index 0000000..236a6f9 --- /dev/null +++ b/Block/Adminhtml/Form/Field/TrackingOptions.php @@ -0,0 +1,73 @@ +addColumn('delivery_method', [ + 'label' => __('Delivery Method'), + 'renderer' => $this->getDeliveryMethodRenderer() + ]); + + $this->addColumn('tracking_url', [ + 'label' => __('Tracking URL'), + 'class' => 'required-entry' + ]); + + $this->_addAfter = false; + $this->_addButtonLabel = __('Add')->render(); + } + + /** + * @return BlockInterface + * @throws LocalizedException + */ + private function getDeliveryMethodRenderer() + { + $this->deliveryMethodRenderer = $this->getLayout()->createBlock( + DeliveryColumn::class, + '', + ['data' => ['is_render_to_js_template' => true]] + ); + + return $this->deliveryMethodRenderer; + } + + /** + * @param DataObject $row + * + * @throws LocalizedException + */ + protected function _prepareArrayRow(DataObject $row): void + { + $options = []; + $type = $row->getDeliveryMethod(); + + if ($type !== null) { + /* @phpstan-ignore-next-line */ + $options['option_' . $this->getDeliveryMethodRenderer()->calcOptionHash($type)] = 'selected="selected"'; + } + + $row->setData('option_extra_attrs', $options); + } +} diff --git a/LICENSE_MTRZK b/LICENSE_MTRZK new file mode 100644 index 0000000..d06666c --- /dev/null +++ b/LICENSE_MTRZK @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Marcin Materzok - MTRZK Sp. z o .o. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Model/Config.php b/Model/Config.php new file mode 100644 index 0000000..072843b --- /dev/null +++ b/Model/Config.php @@ -0,0 +1,309 @@ +scopeConfig = $scopeConfig; + $this->serializer = $serializer; + } + + /** + * @param int $storeId + * + * @return bool + */ + public function isEnabled(int $storeId = 0): bool + { + return $this->scopeConfig->isSetFlag( + self::XML_GENERAL_IS_ENABLED, + ScopeInterface::SCOPE_STORE, + $storeId + ); + } + + /** + * @param int $storeId + * + * @return bool + */ + public function isOrderEnabled(int $storeId = 0): bool + { + return $this->scopeConfig->isSetFlag( + self::XML_GENERAL_IS_ORDER_ENABLED, + ScopeInterface::SCOPE_STORE, + $storeId + ); + } + + /** + * @param int $storeId + * + * @return bool + */ + public function isShipmentEnabled(int $storeId = 0): bool + { + return $this->scopeConfig->isSetFlag( + self::XML_GENERAL_IS_SHIPMENT_ENABLED, + ScopeInterface::SCOPE_STORE, + $storeId + ); + } + + /** + * @param int|null $storeId + * + * @return string + */ + public function getMerchantName(int $storeId = 0): string + { + return (string) $this->scopeConfig->getValue( + self::XML_GENERAL_MERCHANT_NAME, + ScopeInterface::SCOPE_STORE, + $storeId + ); + } + + /** + * @param int $storeId + * + * @return bool + */ + public function isOrderAddBillingData(int $storeId = 0): bool + { + return $this->scopeConfig->isSetFlag( + self::XML_ORDER_ADD_BILLING_DATA, + ScopeInterface::SCOPE_STORE, + $storeId + ); + } + + /** + * @param int|null $storeId + * + * @return string + */ + public function getOrderStatusMapping(int $storeId = 0): string + { + return (string) $this->scopeConfig->getValue( + self::XML_ORDER_ORDER_STATUS_MAPPING, + ScopeInterface::SCOPE_STORE, + $storeId + ); + } + + /** + * @param int|null $storeId + * + * @return array + */ + public function getOrderStatusMappingArray(int $storeId = 0): array + { + try { + return (array) $this->serializer->unserialize( + $this->getOrderStatusMapping($storeId) + ); + } catch (InvalidArgumentException $e) { + return []; + } + } + + /** + * @param int $storeId + * + * @return bool + */ + public function isOrderAddViewActon(int $storeId = 0): bool + { + return $this->scopeConfig->isSetFlag( + self::XML_ORDER_ADD_ACTION_FOR_CUSTOMER, + ScopeInterface::SCOPE_STORE, + $storeId + ); + } + + /** + * @param int|null $storeId + * + * @return string + */ + public function getOrderViewActionLabel(int $storeId = 0): string + { + return (string) $this->scopeConfig->getValue( + self::XML_ORDER_CUSTOMER_VIEW_ACTION_LABEL, + ScopeInterface::SCOPE_STORE, + $storeId + ); + } + + /** + * @param int|null $storeId + * + * @return string + */ + public function getOrderViewActionDescription(int $storeId = 0): string + { + return (string) $this->scopeConfig->getValue( + self::XML_ORDER_CUSTOMER_VIEW_ACTION_DESCRIPTION, + ScopeInterface::SCOPE_STORE, + $storeId + ); + } + + /** + * @param int|null $storeId + * + * @return string + */ + public function getOrderViewActionType(int $storeId = 0): string + { + return (string) $this->scopeConfig->getValue( + self::XML_ORDER_CUSTOMER_VIEW_ACTION_TYPE, + ScopeInterface::SCOPE_STORE, + $storeId + ); + } + + /** + * @param int|null $storeId + * + * @return string + */ + public function getOrderViewActionCustomUrl(int $storeId = 0): string + { + return (string) $this->scopeConfig->getValue( + self::XML_ORDER_CUSTOMER_VIEW_ACTION_CUSTOM_URL, + ScopeInterface::SCOPE_STORE, + $storeId + ); + } + + /** + * @param int $storeId + * + * @return bool + */ + public function isShipmentAddViewActon(int $storeId = 0): bool + { + return $this->scopeConfig->isSetFlag( + self::XML_SHIPMENT_ADD_TRACK_ACTION, + ScopeInterface::SCOPE_STORE, + $storeId + ); + } + + /** + * @param int|null $storeId + * + * @return string + */ + public function getShipmentDeliveryMapping(int $storeId = 0): string + { + return (string) $this->scopeConfig->getValue( + self::XML_SHIPMENT_SHIPMENT_DELIVERY_MAPPING, + ScopeInterface::SCOPE_STORE, + $storeId + ); + } + + /** + * @param int|null $storeId + * + * @return array + */ + public function getShipmentDeliveryMappingArray(int $storeId = 0): array + { + try { + return (array) $this->serializer->unserialize( + $this->getShipmentDeliveryMapping($storeId) + ); + } catch (InvalidArgumentException $e) { + return []; + } + } + + /** + * @param int|null $storeId + * + * @return string + */ + public function getShipmentTrackingMapping(int $storeId = 0): string + { + return (string) $this->scopeConfig->getValue( + self::XML_SHIPMENT_SHIPMENT_TRACKING_MAPPING, + ScopeInterface::SCOPE_STORE, + $storeId + ); + } + + /** + * @param int|null $storeId + * + * @return array + */ + public function getShipmentTrackingMappingArray(int $storeId = 0): array + { + try { + return (array) $this->serializer->unserialize( + $this->getShipmentTrackingMapping($storeId) + ); + } catch (InvalidArgumentException $e) { + return []; + } + } + + /** + * @param int|null $storeId + * + * @return string + */ + public function getAddMarkupEmailSettings(int $storeId = 0): string + { + return (string) $this->scopeConfig->getValue( + self::XML_ADVANCED_ADD_MARKUP_TO_EMAILS, + ScopeInterface::SCOPE_STORE, + $storeId + ); + } +} diff --git a/Model/Config/Backend/Serialized.php b/Model/Config/Backend/Serialized.php new file mode 100644 index 0000000..37cd630 --- /dev/null +++ b/Model/Config/Backend/Serialized.php @@ -0,0 +1,91 @@ +serializer = $serializer; + } + + /** + * @return Serialized | Value + */ + public function beforeSave() + { + $value = $this->getValue(); + + if (is_array($value)) { + unset($value['__empty']); + } + + $this->setValue($value); + + if (is_array($this->getValue())) { + $this->setValue($this->serializer->serialize($this->getValue())); + } + + parent::beforeSave(); + + return $this; + } + + /** + * @return Serialized + */ + protected function _afterLoad() + { + $value = $this->getValue(); + if (!is_array($value)) { + $this->setValue(empty($value) ? false : $this->serializer->unserialize($value)); + } + + return $this; + } +} diff --git a/Model/Config/Source/EmailMx.php b/Model/Config/Source/EmailMx.php new file mode 100644 index 0000000..1d0a66a --- /dev/null +++ b/Model/Config/Source/EmailMx.php @@ -0,0 +1,52 @@ +toArray() as $value => $label) { + $optionArray[] = [ + 'value' => $value, + 'label' => $label + ]; + } + + return $optionArray; + } + + /** + * Get options in "key-value" format + * + * @return array + */ + public function toArray() + { + return [ + self::DEFAULT_TYPE => __('Add to all emails'), + self::GMAIL_TYPE => __('Add only for @gmail.com'), + self::GMAIL_MX_TYPE => __('Add only for @gmail.com and emails with Google Workspace MX records') + ]; + } +} + diff --git a/Model/Config/Source/SchemaOrderTypes.php b/Model/Config/Source/SchemaOrderTypes.php new file mode 100644 index 0000000..06b6f81 --- /dev/null +++ b/Model/Config/Source/SchemaOrderTypes.php @@ -0,0 +1,54 @@ +toArray() as $value => $label) { + $optionArray[] = [ + 'value' => $value, + 'label' => $label + ]; + } + + return $optionArray; + } + + /** + * Get options in "key-value" format + * + * @return array + */ + public function toArray(): array + { + return [ + OrderStatusInterface::CANCELLED => OrderStatusInterface::CANCELLED, + OrderStatusInterface::DELIVERED => OrderStatusInterface::DELIVERED, + OrderStatusInterface::IN_TRANSIT => OrderStatusInterface::IN_TRANSIT, + OrderStatusInterface::PAYMENT_DUE => OrderStatusInterface::PAYMENT_DUE, + OrderStatusInterface::PICKUP_AVAILABLE => OrderStatusInterface::PICKUP_AVAILABLE, + OrderStatusInterface::PROBLEM => OrderStatusInterface::PROBLEM, + OrderStatusInterface::PROCESSING => OrderStatusInterface::PROCESSING, + OrderStatusInterface::RETURNED => OrderStatusInterface::RETURNED + ]; + } +} + diff --git a/Model/Config/Source/UrlType.php b/Model/Config/Source/UrlType.php new file mode 100644 index 0000000..763e5cb --- /dev/null +++ b/Model/Config/Source/UrlType.php @@ -0,0 +1,50 @@ +toArray() as $value => $label) { + $optionArray[] = [ + 'value' => $value, + 'label' => $label + ]; + } + + return $optionArray; + } + + /** + * Get options in "key-value" format + * + * @return array + */ + public function toArray() + { + return [ + self::CUSTOM_TYPE => __('Custom Url'), + self::DEFAULT_TYPE => __('Default Magento Url') + ]; + } +} + diff --git a/Model/EmailLookup.php b/Model/EmailLookup.php new file mode 100644 index 0000000..44d713b --- /dev/null +++ b/Model/EmailLookup.php @@ -0,0 +1,90 @@ +config = $config; + } + + /** + * @param string $email + * @param int $storeId + * + * @return bool + */ + public function checkEmail(string $email, int $storeId): bool + { + if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { + return false; + } + + $markupSettings = $this->config->getAddMarkupEmailSettings($storeId); + + if ($markupSettings === EmailMx::GMAIL_TYPE) { + return $this->checkGmailAccount($email); + } + + if ($markupSettings === EmailMx::GMAIL_MX_TYPE) { + return $this->checkMxRecords($email); + } + + return true; + } + + /** + * @param string $email + * + * @return bool + */ + private function checkMxRecords(string $email): bool + { + if ($this->checkGmailAccount($email)) { + return true; + } + + getmxrr(substr($email, strrpos($email, '@') + 1), $hosts); + + if (!$hosts) { + return false; + } + + return !empty(array_intersect(self::GMAIL_MX_SERVERS, $hosts)); + } + + /** + * @param string $email + * + * @return bool + */ + private function checkGmailAccount(string $email): bool + { + return stripos($email, self::GMAIL_SURFIX) !== false; + } +} diff --git a/Model/Processor/AbstractProcessor.php b/Model/Processor/AbstractProcessor.php new file mode 100644 index 0000000..f59767e --- /dev/null +++ b/Model/Processor/AbstractProcessor.php @@ -0,0 +1,168 @@ +config = $config; + $this->emailLookup = $emailLookup; + $this->serializer = $serializer; + $this->url = $url; + $this->orderStatus = $orderStatus; + $this->logger = $logger; + $this->storeManager = $storeManager; + } + + /** + * @param string $email + * @param int $storeId + * + * @return bool + */ + protected function isEmailEnabledToSend(string $email, int $storeId): bool + { + return $this->emailLookup->checkEmail($email, $storeId); + } + + /** + * @param array $array + * + * @return string + */ + private function toJson(array $array): string + { + + + try { + return (string) $this->serializer->serialize($array); + } catch (InvalidArgumentException $e) { + return ""; + } + } + + /** + * @param array $array + * + * @return string + */ + protected function generateScript(array $array): string + { + $html = ''; + + return $html; + } + + /** + * @param string $schema + * + * @return string + */ + protected function getSchemaUrl(string $schema): string + { + return self::SCHEMA_URL . $schema; + } + + /** + * @param OrderInterface $order + * + * @return string + */ + protected function getOrderViewUrl(OrderInterface $order): string + { + $storeId = (int) $order->getStoreId(); + + if ($this->config->getOrderViewActionType($storeId) === UrlType::CUSTOM_TYPE) { + return str_replace( + [ + "{{order_id}}", + "{{increment_id}}" + ], + [ + $order->getEntityId(), + $order->getIncrementId() + ], + $this->config->getOrderViewActionCustomUrl($storeId) + ); + } + + return $this->localhostFixUrl( + $this->url->getUrl('sales/order/view', [ + '_scope' => $storeId, + 'id' => $order->getEntityId(), + '_nosid' => true + ]) + ); + } + + /** + * @param int $storeId + * + * @return string + * @throws NoSuchEntityException + */ + protected function getMediaUrl(int $storeId): string + { + return $this->localhostFixUrl($this->storeManager->getStore($storeId)->getBaseUrl(UrlInterface::URL_TYPE_MEDIA)); + } + + /** + * Google if url is localhost show error, function change http://localhost to http://example.com/ + * + * @param string|null $url + * + * @return string + */ + protected function localhostFixUrl(?string $url): ?string + { + return str_replace("localhost", "example.com", $url); + } +} diff --git a/Model/Processor/OrderProcessor.php b/Model/Processor/OrderProcessor.php new file mode 100644 index 0000000..7830fc7 --- /dev/null +++ b/Model/Processor/OrderProcessor.php @@ -0,0 +1,137 @@ +getCustomerEmail(); + $storeId = (int) $order->getStoreId(); + + if (!$this->isEmailEnabledToSend($email, $storeId)) { + return ""; + } + + $currencyCode = $order->getOrderCurrencyCode(); + + $arrayJson = [ + BaseInterface::CONTEXT => BaseInterface::SCHEMA_HTTP, + BaseInterface::TYPE => BaseInterface::TYPE_ORDER, + BaseInterface::MERCHANT => [ + BaseInterface::TYPE => BaseInterface::TYPE_ORGANIZATION, + BaseInterface::NAME => $this->config->getMerchantName($storeId) + ], + JsonOrderInterface::ORDER_NUMBER => $order->getIncrementId(), + JsonOrderInterface::PRICE_CURRENCY => $currencyCode, + JsonOrderInterface::PRICE => number_format((float) $order->getGrandTotal(), 2), + JsonOrderInterface::ORDER_DATE => $order->getCreatedAt(), + JsonOrderInterface::ORDER_STATUS => $this->orderStatus->getSchemaOrder($order->getState(), $storeId), + ]; + + if ($this->config->isOrderAddViewActon($storeId) && !$order->getCustomerIsGuest()) { + $url = $this->getOrderViewUrl($order); + + $arrayJson[BaseInterface::POTENTIAL_ACTION] = [ + BaseInterface::TYPE => BaseInterface::TYPE_VIEW_ACTION, + JsonOrderInterface::URL => $url, + JsonOrderInterface::NAME => $this->config->getOrderViewActionLabel($storeId) + ]; + + $arrayJson[BaseInterface::URL] = $url; + } + + $productsJson = []; + + foreach ($order->getAllVisibleItems() as $item) { + $productImage = $this->getMediaUrl($storeId) . $item->getProduct()->getImage(); + $itemJson = [ + BaseInterface::TYPE => BaseInterface::TYPE_OFFER, + JsonOrderInterface::ITEM_OFFERED => [ + BaseInterface::TYPE => BaseInterface::TYPE_PRODUCT, + ProductInterface::NAME => $item->getName(), + ProductInterface::SKU => $item->getProduct()->getSku(), + ProductInterface::URL => $this->localhostFixUrl($item->getProduct()->getProductUrl()), + ProductInterface::IMAGE => $productImage, + ], + JsonOrderInterface::PRICE => number_format((float) $item->getPriceInclTax(), 2), + JsonOrderInterface::PRICE_CURRENCY => $currencyCode, + JsonOrderInterface::ELIGIBLE_QUANTITY => [ + BaseInterface::TYPE => BaseInterface::TYPE_QUANTITATIVE_VALUE, + JsonOrderInterface::VALUE => (int) $item->getQtyOrdered() + ] + ]; + + $productsJson[] = $itemJson; + } + + if ($order->getDiscountAmount() > 0) { + $arrayJson[JsonOrderInterface::DISCOUNT] = $order->getDiscountAmount(); + $arrayJson[JsonOrderInterface::DISCOUNT_CURRENCY] = $currencyCode; + } + + $arrayJson[JsonOrderInterface::ACCEPTED_OFFER] = $productsJson; + + if ($this->config->isOrderAddBillingData($storeId)) { + $billingAddress = $order->getBillingAddress(); + $companyName = $billingAddress->getCompany(); + + if (!empty($companyName)) { + $customerData = [ + BaseInterface::TYPE => BaseInterface::TYPE_ORGANIZATION, + BaseInterface::NAME => $companyName + ]; + } else { + $customerName = $billingAddress->getFirstname() . " " . $billingAddress->getLastname(); + $customerData = [ + BaseInterface::TYPE => BaseInterface::TYPE_PERSON, + BaseInterface::NAME => $customerName + ]; + $companyName = $customerName; + } + + $billingData = [ + JsonOrderInterface::CUSTOMER => $customerData, + JsonOrderInterface::BILLING_ADDRESS => [ + BaseInterface::TYPE => BaseInterface::TYPE_POSTAL_ADDRESS, + BaseInterface::NAME => $companyName, + JsonOrderInterface::STREET_ADDRESS => implode(" ", $billingAddress->getStreet()), + JsonOrderInterface::ADDRESS_REGION => $billingAddress->getRegion(), + JsonOrderInterface::ADDRESS_LOCALITY => $billingAddress->getCity(), + JsonOrderInterface::ADDRESS_COUNTRY => $billingAddress->getCountryId(), + ] + ]; + + $arrayJson = array_merge($arrayJson, $billingData); + } + + return $this->generateScript($arrayJson); + + } catch (Exception $e) { + $this->logger->error("OrderProcessor error on Increment ID ".$order->getIncrementId().": ".$e->getMessage()); + + return ""; + } + } +} diff --git a/Model/Processor/ShipmentProcessor.php b/Model/Processor/ShipmentProcessor.php new file mode 100644 index 0000000..a95e48e --- /dev/null +++ b/Model/Processor/ShipmentProcessor.php @@ -0,0 +1,166 @@ +trackingNumber = $trackingNumber; + $this->deliveryName = $deliveryName; + } + + /** + * @see https://developers.google.com/gmail/markup/reference/parcel-delivery + * + * @param ShipmentInterface $shipment + * @param OrderInterface $order + * + * @return string + */ + public function processShipment(ShipmentInterface $shipment, OrderInterface $order): string + { + try { + $email = $order->getCustomerEmail(); + $storeId = (int) $order->getStoreId(); + + if (!$this->isEmailEnabledToSend($email, $storeId)) { + return ""; + } + + $deliveryMethod = $order->getShippingMethod(true)->getData(); + $deliveryMethodName = $deliveryMethod['carrier_code']."_".$deliveryMethod['method']; + $shippingAddress = $order->getShippingAddress(); + $shippingName = $shippingAddress->getCompany(); + + if (empty($shippingName)) { + $shippingName = $shippingAddress->getFirstname() . " " . $shippingAddress->getLastname(); + } + + $arrayJson = [ + BaseInterface::CONTEXT => BaseInterface::SCHEMA_HTTP, + BaseInterface::TYPE => BaseInterface::TYPE_PARCEL_DELIVERY, + JsonShipmentInterface::CARRIER => [ + BaseInterface::TYPE => BaseInterface::TYPE_ORGANIZATION, + BaseInterface::NAME => $this->deliveryName->getDeliveryName($deliveryMethodName, $storeId) + ], + JsonShipmentInterface::DELIVERY_ADDRESS => [ + BaseInterface::TYPE => BaseInterface::TYPE_POSTAL_ADDRESS, + BaseInterface::NAME => $shippingName, + JsonShipmentInterface::STREET_ADDRESS => implode(" ", $shippingAddress->getStreet()), + JsonShipmentInterface::ADDRESS_REGION => $shippingAddress->getRegion(), + JsonShipmentInterface::ADDRESS_LOCALITY => $shippingAddress->getCity(), + JsonShipmentInterface::ADDRESS_COUNTRY => $shippingAddress->getCountryId(), + JsonShipmentInterface::POSTAL_CODE => $shippingAddress->getPostcode() + ], + JsonShipmentInterface::PART_OF_ORDER => [ + BaseInterface::TYPE => BaseInterface::TYPE_ORDER, + JsonOrderInterface::ORDER_NUMBER => $order->getIncrementId(), + BaseInterface::MERCHANT => [ + BaseInterface::TYPE => BaseInterface::TYPE_ORGANIZATION, + BaseInterface::NAME => $this->config->getMerchantName($storeId) + ], + ], + JsonShipmentInterface::TRACKING_NUMBER => $this->trackingNumber->getTrackingNumber($shipment), + JsonShipmentInterface::ORDER_STATUS => $this->orderStatus->getSchemaOrder($order->getState(), $storeId), + ]; + + if ($this->config->isShipmentAddViewActon($storeId)) { + $trackingUrl = $this->trackingNumber->getTrackingUrl($shipment, $order); + + if (!$trackingUrl) { + $trackingUrl = $this->getOrderViewUrl($order); + } + + $arrayJson[BaseInterface::POTENTIAL_ACTION] = [ + BaseInterface::TYPE => BaseInterface::TYPE_TRACK_ACTION, + JsonShipmentInterface::URL => $trackingUrl + ]; + + $arrayJson[JsonShipmentInterface::TRACKING_URL] = $trackingUrl; + } + + $productsJson = []; + + foreach ($order->getAllVisibleItems() as $item) { + $productImage = $this->getMediaUrl($storeId) . $item->getProduct()->getImage(); + + $itemJson = [ + BaseInterface::TYPE => BaseInterface::TYPE_PRODUCT, + ProductInterface::NAME => $item->getName(), + ProductInterface::SKU => $item->getProduct()->getSku(), + ProductInterface::URL => $this->localhostFixUrl($item->getProduct()->getProductUrl()), + ProductInterface::IMAGE => $productImage + ]; + + $productsJson[] = $itemJson; + } + + $arrayJson[JsonShipmentInterface::ITEM_SHIPPED] = $productsJson; + + $arrayJson[JsonShipmentInterface::EXPECTED_ARRIVAL_UNTIL] = Date('y:m:d', strtotime('+3 days')); + + return $this->generateScript($arrayJson); + } catch (Exception $e) { + $this->logger->error("OrderProcessor error on Increment ID " . $order->getIncrementId() . ": " . $e->getMessage()); + + return ""; + } + } +} diff --git a/Model/Renderer/DeliveryName.php b/Model/Renderer/DeliveryName.php new file mode 100644 index 0000000..4c16738 --- /dev/null +++ b/Model/Renderer/DeliveryName.php @@ -0,0 +1,57 @@ +config = $config; + $this->allmethods = $allmethods; + } + + /** + * @param string $deliveryCode + * @param int $storeId + * + * @return string + */ + public function getDeliveryName(string $deliveryCode, int $storeId): string + { + $mapping = $this->config->getShipmentDeliveryMappingArray($storeId); + + if (count($mapping) > 0) { + foreach ($mapping as $map) { + if ($map['delivery_method'] === $deliveryCode) { + return $map['delivery_name']; + } + } + } + + foreach ($this->allmethods->toOptionArray() as $map) { + if ($map['value'] === $deliveryCode) { + return $map['label']; + } + } + + return ''; + } +} diff --git a/Model/Renderer/OrderStatus.php b/Model/Renderer/OrderStatus.php new file mode 100644 index 0000000..982d6c0 --- /dev/null +++ b/Model/Renderer/OrderStatus.php @@ -0,0 +1,48 @@ +config = $config; + } + + /** + * @param string $orderStatus + * @param int $storeId + * + * @return string + */ + public function getSchemaOrder(string $orderStatus, int $storeId): string + { + $mapping = $this->config->getOrderStatusMappingArray($storeId); + + if (count($mapping) > 0) { + foreach ($mapping as $map) { + if ($map['order_status'] === $orderStatus) { + return $map['schema_status']; + } + } + } + + return OrderStatusInterface::PROCESSING; + } +} diff --git a/Model/Renderer/TrackingNumber.php b/Model/Renderer/TrackingNumber.php new file mode 100644 index 0000000..c458c71 --- /dev/null +++ b/Model/Renderer/TrackingNumber.php @@ -0,0 +1,78 @@ +config = $config; + $this->allmethods = $allmethods; + } + + /** + * Here is class with function to make preference if you have some custom tracking Url method + * + * @param ShipmentInterface $shipment + * @param OrderInterface $order + * + * @return null|string + */ + public function getTrackingUrl(ShipmentInterface $shipment, OrderInterface $order): ?string + { + $deliveryMethod = $order->getShippingMethod(true)->getData(); + $deliveryMethodName = $deliveryMethod['carrier_code'] . "_" . $deliveryMethod['method']; + $storeId = (int) $order->getStoreId(); + $mapping = $this->config->getShipmentTrackingMappingArray($storeId); + + if (count($mapping) > 0) { + foreach ($mapping as $map) { + if ($map['delivery_method'] === $deliveryMethodName) { + return str_replace( + [ + "{{shipment_id}}", + "{{tracking_number}}" + ], + [ + $shipment->getEntityId(), + $this->getTrackingNumber($shipment) + ], + $map['tracking_url'] + ); + } + } + } + + return null; + } + + /** + * @param ShipmentInterface $shipment + * + * @return string + */ + public function getTrackingNumber(ShipmentInterface $shipment): string + { + return current($shipment->getTracks())->getTrackNumber(); + } +} diff --git a/Observer/OrderEmailObserver.php b/Observer/OrderEmailObserver.php new file mode 100644 index 0000000..a710281 --- /dev/null +++ b/Observer/OrderEmailObserver.php @@ -0,0 +1,66 @@ +orderProcessor = $orderProcessor; + $this->config = $config; + } + + /** {@inheritDoc} */ + public function execute(Observer $observer): void + { + $sender = $observer->getSender(); + + if (!$sender instanceof OrderSender) { + return; + } + + /** @var DataObject $transport */ + $transport = $observer->getData('transportObject'); + + /** @var OrderInterface $order */ + $order = $transport->getOrder(); + + if (!$order) { + return; + } + + $storeId = (int) $order->getStoreId(); + + if (!$this->config->isEnabled($storeId) && !$this->config->isOrderEnabled($storeId)) { + return; + } + + $jsonData = $this->orderProcessor->processOrder($order); + + $transport->setData('gmailMarkup', $jsonData); + } +} diff --git a/Observer/ShipmentEmailObserver.php b/Observer/ShipmentEmailObserver.php new file mode 100644 index 0000000..1d333f3 --- /dev/null +++ b/Observer/ShipmentEmailObserver.php @@ -0,0 +1,70 @@ +shipmentProcessor = $shipmentProcessor; + $this->config = $config; + } + + /** {@inheritDoc} */ + public function execute(Observer $observer): void + { + $sender = $observer->getSender(); + + if (!$sender instanceof EmailSender && !$sender instanceof ShipmentSender) { + return; + } + + /** @var DataObject $transport */ + $transport = $observer->getData('transportObject'); + + /** @var OrderInterface $order */ + $order = $transport->getOrder(); + + /** @var ShipmentInterface $shipment */ + $shipment = $transport->getShipment(); + + if (!$order || !$shipment) { + return; + } + + $storeId = (int) $order->getStoreId(); + + if (!$this->config->isEnabled($storeId) && !$this->config->isShipmentEnabled($storeId)) { + return; + } + + $jsonData = $this->shipmentProcessor->processShipment($shipment, $order); + + $transport->setData('gmailMarkup', $jsonData); + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..549d620 --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +# Magento 2 Gmail Markup extenstion + +## 1. Documentation + +- [Contribute on Github](https://github.com/marcinmaterzok/magento2-email-gmail-markup) +- [Releases](https://github.com/marcinmaterzok/magento2-email-gmail-markup/releases) +- [Google Registration Guidelines](https://developers.google.com/gmail/markup/registering-with-google) +- [What is Gmail Markup?](https://developers.google.com/gmail/markup) + + +## 2. How to install + +### Install via composer (recommend) +**1. Run the following command in Magento 2 root folder:** +``` +composer require mtrzk/magento2-gmail-markup +php bin/magento setup:upgrade +php bin/magento setup:static-content:deploy +``` +**2. Configure module in Magneto 2 Admin panel** + +**3. IMPORTANT! You need to manually add variable in you email templates (in header in head tag).** +``` +{{var gmailMarkup|raw}} +``` + +## 3. How to register in Google + +1. If you want to use this module you need to check "Email Sender Quality guidelines" section on +https://developers.google.com/gmail/markup/registering-with-google +2. Enable module, and check order and shipment email via https://www.mail-tester.com/ +3. Register on this form: +https://docs.google.com/forms/d/e/1FAIpQLSfT5F1VJXtBjGw2mLxY2aX557ctPTsCrJpURiKJjYeVrugHBQ/viewform?pli=1 +4. If you also want to use ViewOrder and TrackAction action you need to check "Actions / Schema Guidelines" section + + +## 4. CHANGELOG +Version 1.0.0 + +``` +- First commit +- Added support for Order emails +- Added support for Shipment emails +- Advanced configuration per store +``` diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..89ab098 --- /dev/null +++ b/composer.json @@ -0,0 +1,29 @@ +{ + "name": "mtrzk/magento2-gmail-markup", + "description": "Magento 2 for Email Gmail Markup functionality", + "version": "1.0.0", + "require": { + "php": ">=7.4.0", + "magento/module-sales": "*", + "magento/module-email": "*", + "magento/module-backend": "*" + }, + "license": [ + "proprietary" + ], + "authors": [ + { + "name": "Marcin Materzok", + "email": "marcin@mtrzk.com" + } + ], + "type": "magento2-module", + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Mtrzk\\GmailMarkup\\": "" + } + } +} diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml new file mode 100644 index 0000000..1350858 --- /dev/null +++ b/etc/adminhtml/system.xml @@ -0,0 +1,118 @@ + + + + + + +
+ + mtrzk_modules + Mtrzk_GmailMarkup::faqpage + + + + + Magento\Config\Model\Config\Source\Yesno + + + + Magento\Config\Model\Config\Source\Yesno + + 1 + + + + + Magento\Config\Model\Config\Source\Yesno + + 1 + + + + + + 1 + + + + + + + + + Magento\Config\Model\Config\Source\Yesno + + + + Mtrzk\GmailMarkup\Model\Config\Backend\Serialized + Mtrzk\GmailMarkup\Block\Adminhtml\Form\Field\OrderMappingOptions + This field is for mapping Magento 2 Order status with JSON Schema status + + + + Magento\Config\Model\Config\Source\Yesno + + + + + 1 + + + + + + 1 + + + + + Mtrzk\GmailMarkup\Model\Config\Source\UrlType + + + + + custom + + This field is for custom order view URL (for example in PWA). Replace variable is: order_id or increment_id + + + 1 + + + + + + + + Magento\Config\Model\Config\Source\Yesno + + + + Mtrzk\GmailMarkup\Model\Config\Backend\Serialized + Mtrzk\GmailMarkup\Block\Adminhtml\Form\Field\DeliveryOptions + This field is for mapping Magento 2 Delivery methods with Courier names + + + + Mtrzk\GmailMarkup\Model\Config\Backend\Serialized + Mtrzk\GmailMarkup\Block\Adminhtml\Form\Field\TrackingOptions + This field is for mapping Magento 2 Delivery with tracking URL. Replace variable is: tracking_number or shipment_id + + + 1 + + + + + + + + Mtrzk\GmailMarkup\Model\Config\Source\EmailMx + + + 1 + + +
+
+
diff --git a/etc/config.xml b/etc/config.xml new file mode 100644 index 0000000..969f066 --- /dev/null +++ b/etc/config.xml @@ -0,0 +1,27 @@ + + + + + + 1 + 1 + 1 + Your Store Name + + + 0 + View Order + Show your order on store site + default + http://yourstore.com/order/view/id/{{order_id}}/ + {"_1662154108769_769":{"order_status":"pending","schema_status":"http:\/\/schema.org\/OrderProcessing"},"_1662154115688_688":{"order_status":"canceled","schema_status":"http:\/\/schema.org\/OrderCancelled"},"_1662154123960_960":{"order_status":"payment_review","schema_status":"http:\/\/schema.org\/OrderPaymentDue"},"_1662154132124_124":{"order_status":"pending_payment","schema_status":"http:\/\/schema.org\/OrderProcessing"},"_1662154142564_564":{"order_status":"complete","schema_status":"http:\/\/schema.org\/OrderDelivered"},"_1662154147793_793":{"order_status":"holded","schema_status":"http:\/\/schema.org\/OrderProblem"},"_1662154161029_29":{"order_status":"paypal_reversed","schema_status":"http:\/\/schema.org\/OrderProblem"},"_1662154179561_561":{"order_status":"fraud","schema_status":"http:\/\/schema.org\/OrderProblem"}} + + + 0 + + + default + + + + diff --git a/etc/di.xml b/etc/di.xml new file mode 100644 index 0000000..f348c04 --- /dev/null +++ b/etc/di.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/etc/events.xml b/etc/events.xml new file mode 100644 index 0000000..f73451e --- /dev/null +++ b/etc/events.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/etc/module.xml b/etc/module.xml new file mode 100644 index 0000000..4676b5f --- /dev/null +++ b/etc/module.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/registration.php b/registration.php new file mode 100644 index 0000000..9ed449b --- /dev/null +++ b/registration.php @@ -0,0 +1,13 @@ +