From 0634d7b0f6f59f43043c728e5e5b09f58577eb67 Mon Sep 17 00:00:00 2001 From: Pete Eveleigh Date: Mon, 23 May 2022 10:15:23 +0100 Subject: [PATCH 01/27] Bump version and requirements --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index ce40c47..d9895f5 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "description": "A Craft CMS plugin for integrating Craft Commerce with ShipStation", "homepage": "https://github.com/fostercommerce/shipstation-connect", "type": "craft-plugin", - "version": "1.3.7", + "version": "1.3.8", "keywords": ["craft","plugin","shipstation"], "license": "proprietary", "support": { @@ -23,8 +23,8 @@ } }, "require": { - "craftcms/cms": "^3.0", - "craftcms/commerce": "^2.0|^3.0" + "craftcms/cms": "^4.0", + "craftcms/commerce": "^2.0|^3.0|^4.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.13" From 4137eab88897c40ad9c6be2324fc23c3a7183555 Mon Sep 17 00:00:00 2001 From: Pete Eveleigh Date: Mon, 23 May 2022 12:32:08 +0100 Subject: [PATCH 02/27] Plugn can now be installed in Craft 4 --- composer.json | 2 +- src/Plugin.php | 46 ++++++++++++++++------ src/web/twig/filters/IsFieldTypeFilter.php | 19 +++++---- 3 files changed, 45 insertions(+), 22 deletions(-) diff --git a/composer.json b/composer.json index d9895f5..09d9cf4 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "description": "A Craft CMS plugin for integrating Craft Commerce with ShipStation", "homepage": "https://github.com/fostercommerce/shipstation-connect", "type": "craft-plugin", - "version": "1.3.8", + "version": "2.0", "keywords": ["craft","plugin","shipstation"], "license": "proprietary", "support": { diff --git a/src/Plugin.php b/src/Plugin.php index 3792065..bd30cf9 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -6,17 +6,39 @@ use craft\web\UrlManager; use craft\services\UserPermissions; use craft\events\RegisterUserPermissionsEvent; +use craft\base\Model; use yii\base\Event; use yii\base\Exception; use fostercommerce\shipstationconnect\web\twig\filters\IsFieldTypeFilter; class Plugin extends \craft\base\Plugin { - public $hasCpSettings = true; - public $hasCpSection = true; - public $schemaVersion = '1.0.1'; - - public function init() + /** + * @var bool $hasCpSettings + */ + public bool $hasCpSettings = true; + + /** + * @var bool $hasCpSection + */ + public bool $hasCpSection = true; + + /** + * @var string $schemaVersion + */ + public string $schemaVersion = '1.0.1'; + + + /** + * init. + * + * @author Unknown + * @since v0.0.1 + * @version v1.0.0 Monday, May 23rd, 2022. + * @access public + * @return void + */ + public function init(): void { parent::init(); @@ -42,14 +64,14 @@ function (RegisterUrlRulesEvent $event) { }); } - protected function beforeInstall(): bool + protected function beforeInstall(): void { if (!Craft::$app->plugins->isPluginInstalled('commerce')) { Craft::error(Craft::t( 'shipstationconnect', 'Failed to install. Craft Commerce is required.' )); - return false; + // return false; } if (!Craft::$app->plugins->isPluginEnabled('commerce')) { @@ -57,13 +79,11 @@ protected function beforeInstall(): bool 'shipstationconnect', 'Failed to install. Craft Commerce is required.' )); - return false; + // return false; } - - return true; } - public function getCpNavItem() + public function getCpNavItem(): ?array { $item = parent::getCpNavItem(); @@ -75,12 +95,12 @@ public function getCpNavItem() return $item; } - protected function createSettingsModel() + protected function createSettingsModel(): ?Model { return new \fostercommerce\shipstationconnect\models\Settings(); } - public function settingsHtml() + public function settingsHtml(): ?string { return Craft::$app->getView()->renderTemplate('shipstationconnect/settings', [ 'settings' => $this->getSettings() diff --git a/src/web/twig/filters/IsFieldTypeFilter.php b/src/web/twig/filters/IsFieldTypeFilter.php index e6d28f7..eac3d1f 100644 --- a/src/web/twig/filters/IsFieldTypeFilter.php +++ b/src/web/twig/filters/IsFieldTypeFilter.php @@ -5,27 +5,30 @@ use craft\base\Field; use craft\fields\Matrix; use craft\fields\Dropdown; +use Twig\Extension\AbstractExtension; +use Twig\TwigFilter; +use Twig\TwigFunction; -class IsFieldTypeFilter extends \Twig_Extension +class IsFieldTypeFilter extends AbstractExtension { - public function getName() + public function getName(): string { return 'IsMatrixFilter'; } - public function getFilters() + public function getFilters(): array { return [ - new \Twig_SimpleFilter('is_matrix', [$this, 'is_matrix']), - new \Twig_SimpleFilter('is_dropdown', [$this, 'is_dropdown']), + new TwigFilter('is_matrix', [$this, 'is_matrix']), + new TwigFilter('is_dropdown', [$this, 'is_dropdown']), ]; } - public function getFunctions() + public function getFunctions(): array { return [ - new \Twig_SimpleFunction('is_matrix', [$this, 'is_matrix']), - new \Twig_SimpleFunction('is_dropdown', [$this, 'is_dropdown']), + new TwigFunction('is_matrix', [$this, 'is_matrix']), + new TwigFunction('is_dropdown', [$this, 'is_dropdown']), ]; } From fa560c43f3331ff0255ad4028ae066bdc12af17e Mon Sep 17 00:00:00 2001 From: Pete Eveleigh Date: Mon, 23 May 2022 16:12:49 +0100 Subject: [PATCH 03/27] typing --- src/controllers/OrdersController.php | 28 +++++++++++++------------- src/controllers/SettingsController.php | 6 ++++-- src/services/Xml.php | 24 +++++++++++----------- 3 files changed, 30 insertions(+), 28 deletions(-) diff --git a/src/controllers/OrdersController.php b/src/controllers/OrdersController.php index c6ddd17..524c1e4 100644 --- a/src/controllers/OrdersController.php +++ b/src/controllers/OrdersController.php @@ -21,9 +21,9 @@ class OrdersController extends Controller const FIND_ORDER_EVENT = 'findOrderEvent'; // Disable CSRF validation for the entire controller - public $enableCsrfValidation = false; + public bool $enableCsrfValidation = false; - protected $allowAnonymous = true; + protected bool $allowAnonymous = true; /** * @inheritDoc @@ -50,7 +50,7 @@ public function init() * @param array $variables, containing key 'fulfillmentService' * @throws HttpException for malformed requests */ - public function actionProcess($store = null, $action = null) + public function actionProcess($store = null, $action = null): void { $request = Craft::$app->request; try { @@ -84,7 +84,7 @@ public function actionProcess($store = null, $action = null) } } - private function logException($msg, $params = [], $e = null) + private function logException($msg, $params = [], $e = null): void { Craft::error( Craft::t('shipstationconnect', $msg, $params), @@ -101,7 +101,7 @@ private function logException($msg, $params = [], $e = null) * * @return bool, true if successfully authenticated or false otherwise */ - protected function authenticate() + protected function authenticate(): bool { $plugin = Plugin::getInstance(); if ($plugin->isAuthHandledByCraft()) { @@ -122,7 +122,7 @@ protected function authenticate() * * @return SimpleXMLElement Orders XML */ - protected function getOrders($store = null) + protected function getOrders($store = null): SimpleXMLElement { $query = Order::find(); @@ -160,7 +160,7 @@ protected function getOrders($store = null) * @param ElementCriteriaModel, a REFERENCE to the criteria instance * @return Int total number of pages */ - protected function paginateOrders(&$query) + protected function paginateOrders(&$query): int { $pageSize = Plugin::getInstance()->settings->ordersPageSize; if (!is_numeric($pageSize) || $pageSize < 1) { @@ -185,7 +185,7 @@ protected function paginateOrders(&$query) * @param String $field_name, the name of the field in GET params * @return String|null the formatted date string */ - protected function parseDate($field_name) + protected function parseDate($field_name): ?string { $request = Craft::$app->getRequest(); if ($date_raw = $request->getParam($field_name)) { @@ -201,7 +201,7 @@ protected function parseDate($field_name) return null; } - private function getBlockTypeByHandle($fieldId, $handle) + private function getBlockTypeByHandle($fieldId, $handle): ?MatrixBlockType { $result = (new Query()) ->select([ @@ -236,7 +236,7 @@ private function getBlockTypeByHandle($fieldId, $handle) * * @throws ErrorException if the order fails to save */ - protected function postShipment() + protected function postShipment(): ?string { $order = $this->orderFromParams(); @@ -296,7 +296,7 @@ protected function postShipment() } } - private function validateShippingInformation($info) + private function validateShippingInformation($info): bool { // Requires at least one value foreach ($info as $key => $value) { @@ -317,7 +317,7 @@ private function validateShippingInformation($info) * * @return array */ - protected function getShippingInformationFromParams() + protected function getShippingInformationFromParams(): array { $request = Craft::$app->getRequest(); return [ @@ -336,7 +336,7 @@ protected function getShippingInformationFromParams() * @throws HttpException, 404 if not found, 406 if order number is invalid * @return Commerce_Order */ - protected function orderFromParams() + protected function orderFromParams(): Commerce_Order { $request = Craft::$app->getRequest(); if ($orderNumber = $request->getParam('order_number')) { @@ -365,7 +365,7 @@ protected function orderFromParams() * @param SimpleXMLElement $xml * @return null */ - protected function returnXML(\SimpleXMLElement $xml) + protected function returnXML(\SimpleXMLElement $xml): void { header("Content-type: text/xml"); // Output it into a buffer, in case TasksService wants to close the connection prematurely diff --git a/src/controllers/SettingsController.php b/src/controllers/SettingsController.php index c0779c7..11c72e9 100644 --- a/src/controllers/SettingsController.php +++ b/src/controllers/SettingsController.php @@ -7,10 +7,12 @@ use fostercommerce\shipstationconnect\Plugin; use fostercommerce\shipstationconnect\models\Settings; +use yii\web\Response; + class SettingsController extends Controller { - public function actionIndex() + public function actionIndex(): Response { $plugin = Plugin::getInstance(); return $this->renderTemplate("shipstationconnect/settings/index", [ @@ -19,7 +21,7 @@ public function actionIndex() ]); } - public function actionSave() + public function actionSave(): Response { $this->requirePostRequest(); $postData = Craft::$app->getRequest()->getBodyParam('settings'); diff --git a/src/services/Xml.php b/src/services/Xml.php index 7887ba4..45daf13 100644 --- a/src/services/Xml.php +++ b/src/services/Xml.php @@ -16,7 +16,7 @@ class Xml extends Component const LINEITEM_OPTION_LIMIT = 10; const ORDER_FIELD_EVENT = 'orderFieldEvent'; - public function shouldInclude($order) + public function shouldInclude($order): bool { $settings = Plugin::getInstance()->settings; $billingSameAsShipping = $settings->billingSameAsShipping; @@ -34,7 +34,7 @@ public function shouldInclude($order) * @param String $name the name of the child node, default 'Orders' * @return SimpleXMLElement */ - public function orders(\SimpleXMLElement $xml, $orders, $name='Orders') + public function orders(\SimpleXMLElement $xml, $orders, $name='Orders'): \SimpleXMLElement { $orders_xml = $xml->getName() == $name ? $xml : $xml->addChild($name); foreach ($orders as $order) { @@ -54,7 +54,7 @@ public function orders(\SimpleXMLElement $xml, $orders, $name='Orders') * @param String $name the name of the child node, default 'Order' * @return SimpleXMLElement */ - public function order(\SimpleXMLElement $xml, Order $order, $name='Order') + public function order(\SimpleXMLElement $xml, Order $order, $name='Order'): \SimpleXMLElement { $order_xml = $xml->getName() == $name ? $xml : $xml->addChild($name); @@ -140,7 +140,7 @@ public function order(\SimpleXMLElement $xml, Order $order, $name='Order') * @param [Order] $order * @return null */ - public function shippingMethod(\SimpleXMLElement $order_xml, $order) + public function shippingMethod(\SimpleXMLElement $order_xml, $order): void { $orderFieldEvent = new OrderFieldEvent([ 'field' => OrderFieldEvent::FIELD_SHIPPING_METHOD, @@ -163,7 +163,7 @@ public function shippingMethod(\SimpleXMLElement $order_xml, $order) * @param String $name the name of the child node, default 'Items' * @return SimpleXMLElement */ - public function items(\SimpleXMLElement $xml, $items, $name='Items') + public function items(\SimpleXMLElement $xml, $items, $name='Items'): \SimpleXMLElement { $items_xml = $xml->getName() == $name ? $xml : $xml->addChild($name); foreach ($items as $item) { @@ -181,7 +181,7 @@ public function items(\SimpleXMLElement $xml, $items, $name='Items') * @param String $name the name of the child node, default 'Item' * @return SimpleXMLElement */ - public function item(\SimpleXMLElement $xml, LineItem $item, $name='Item') + public function item(\SimpleXMLElement $xml, LineItem $item, $name='Item'): \SimpleXMLElement { $item_xml = $xml->getName() == $name ? $xml : $xml->addChild($name); @@ -248,7 +248,7 @@ public function item(\SimpleXMLElement $xml, LineItem $item, $name='Item') * @param string $name [description] * @return [type] [description] */ - public function discount(\SimpleXMLElement $xml, Order $order, $name='Item') + public function discount(\SimpleXMLElement $xml, Order $order, $name='Item'): ?SimpleXMLElement { // If no discount was applied, skip this if ($order->getTotalDiscount() >= 0) { @@ -297,7 +297,7 @@ public function discount(\SimpleXMLElement $xml, Order $order, $name='Item') * @param String $name the name of the child node, default 'Options' * @return SimpleXMLElement */ - public function options(\SimpleXMLElement $xml, $options, $name='Options') + public function options(\SimpleXMLElement $xml, $options, $name='Options'): \SimpleXMLElement { $options_xml = $xml->getName() == $name ? $xml : $xml->addChild($name); @@ -331,7 +331,7 @@ public function options(\SimpleXMLElement $xml, $options, $name='Options') * @param String $name the name of the child node, default 'Customer' * @return SimpleXMLElement */ - public function customer(\SimpleXMLElement $xml, Customer $customer, $name='Customer') + public function customer(\SimpleXMLElement $xml, Customer $customer, $name='Customer'): \SimpleXMLElement { $customer_xml = $xml->getName() == $name ? $xml : $xml->addChild($name); @@ -341,7 +341,7 @@ public function customer(\SimpleXMLElement $xml, Customer $customer, $name='Cust return $customer_xml; } - private function generateName($firstName, $lastName) + private function generateName($firstName, $lastName): ?string { if (!$firstName && !$lastName) { return false; @@ -366,7 +366,7 @@ function($name) { * @param Customer $customer * @return SimpleXMLElement, or null if no address exists */ - public function billTo(\SimpleXMLElement $customer_xml, Order $order, Customer $customer) + public function billTo(\SimpleXMLElement $customer_xml, Order $order, Customer $customer): ?SimpleXMLElement { $billingAddress = $order->getBillingAddress(); if (!$billingAddress) { @@ -403,7 +403,7 @@ public function billTo(\SimpleXMLElement $customer_xml, Order $order, Customer $ * @param Customer $customer * @return SimpleXMLElement, or null if no address exists */ - public function shipTo(\SimpleXMLElement $customer_xml, Order $order, Customer $customer) + public function shipTo(\SimpleXMLElement $customer_xml, Order $order, Customer $customer): ?SimpleXMLElement { $shippingAddress = $order->getShippingAddress(); $shipTo_xml = $this->address($customer_xml, $shippingAddress, 'ShipTo'); From d54780a696d19e49cc525a4ac621576abaafbad8 Mon Sep 17 00:00:00 2001 From: Pete Eveleigh Date: Wed, 8 Jun 2022 11:42:40 +0100 Subject: [PATCH 04/27] remove commerce 2 and 3 from requirements --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 09d9cf4..5683075 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ }, "require": { "craftcms/cms": "^4.0", - "craftcms/commerce": "^2.0|^3.0|^4.0" + "craftcms/commerce": "^4.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.13" From dff5981b596779f31d80ec54290b61c76facf946 Mon Sep 17 00:00:00 2001 From: Pete Eveleigh Date: Wed, 8 Jun 2022 12:37:59 +0100 Subject: [PATCH 05/27] update typing for process action --- src/controllers/OrdersController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/OrdersController.php b/src/controllers/OrdersController.php index 524c1e4..9fa4086 100644 --- a/src/controllers/OrdersController.php +++ b/src/controllers/OrdersController.php @@ -50,7 +50,7 @@ public function init() * @param array $variables, containing key 'fulfillmentService' * @throws HttpException for malformed requests */ - public function actionProcess($store = null, $action = null): void + public function actionProcess($store = null, $action = null): mixed { $request = Craft::$app->request; try { From 545adc77861a7693163cc4961c99cc196ae9cbf3 Mon Sep 17 00:00:00 2001 From: Pete Eveleigh Date: Wed, 8 Jun 2022 15:20:54 +0100 Subject: [PATCH 06/27] fixes for Craft 4 compatibility --- src/controllers/OrdersController.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/controllers/OrdersController.php b/src/controllers/OrdersController.php index 9fa4086..57842c1 100644 --- a/src/controllers/OrdersController.php +++ b/src/controllers/OrdersController.php @@ -21,14 +21,14 @@ class OrdersController extends Controller const FIND_ORDER_EVENT = 'findOrderEvent'; // Disable CSRF validation for the entire controller - public bool $enableCsrfValidation = false; + public $enableCsrfValidation = false; - protected bool $allowAnonymous = true; + protected array|int|bool $allowAnonymous = true; /** * @inheritDoc */ - public function init() + public function init(): void { // Allow anonymous access only when this plugin is handling basic // authentication, otherwise require auth so that Craft doesn't let @@ -38,8 +38,10 @@ public function init() if ($isUsingCraftAuth) { $this->requirePermission('shipstationconnect-processOrders'); } - - return parent::init(); + + parent::init(); + + //return parent::init(); } /** From bc89266ac0ebdf130574427836ff88b991aa5c79 Mon Sep 17 00:00:00 2001 From: Pete Eveleigh Date: Wed, 8 Jun 2022 15:45:10 +0100 Subject: [PATCH 07/27] fix deprecation --- src/controllers/OrdersController.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/controllers/OrdersController.php b/src/controllers/OrdersController.php index 57842c1..f34983e 100644 --- a/src/controllers/OrdersController.php +++ b/src/controllers/OrdersController.php @@ -9,6 +9,7 @@ use craft\commerce\elements\Order; use craft\db\Query; use craft\db\Table; +use craft\helpers\App; use craft\models\MatrixBlockType; use yii\web\HttpException; use yii\base\ErrorException; @@ -111,8 +112,8 @@ protected function authenticate(): bool } $settings = $plugin->settings; - $expectedUsername = Craft::parseEnv($settings->shipstationUsername); - $expectedPassword = Craft::parseEnv($settings->shipstationPassword); + $expectedUsername = App::parseEnv($settings->shipstationUsername); + $expectedPassword = App::parseEnv($settings->shipstationPassword); list($username, $password) = Craft::$app->getRequest()->getAuthCredentials(); From 3f038ba6e29373cd3a66bbfed45d451dcf64c300 Mon Sep 17 00:00:00 2001 From: Pete Eveleigh Date: Wed, 8 Jun 2022 17:49:26 +0100 Subject: [PATCH 08/27] fixes permissions page --- src/Plugin.php | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/Plugin.php b/src/Plugin.php index bd30cf9..de78d9f 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -57,11 +57,20 @@ function (RegisterUrlRulesEvent $event) { } ); - Event::on(UserPermissions::class, UserPermissions::EVENT_REGISTER_PERMISSIONS, function(RegisterUserPermissionsEvent $event) { - $event->permissions['ShipStation Connect'] = [ - 'shipstationconnect-processOrders' => ['label' => 'Process Orders'], - ]; - }); + Event::on( + UserPermissions::class, + UserPermissions::EVENT_REGISTER_PERMISSIONS, + function(RegisterUserPermissionsEvent $event) { + $event->permissions[] = [ + 'heading' => 'ShipStation Connect', + 'permissions' => [ + 'shipstationconnect-processOrders' => [ + 'label' => 'Process Orders' + ], + ] + ]; + } + ); } protected function beforeInstall(): void From 2fd59b45d8d991fc6f7731973c52fd3c1966fc68 Mon Sep 17 00:00:00 2001 From: Pete Eveleigh Date: Fri, 10 Jun 2022 12:41:59 +0100 Subject: [PATCH 09/27] change name of variable from 'action' to 'do' as I think action might be reserved --- src/controllers/OrdersController.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/controllers/OrdersController.php b/src/controllers/OrdersController.php index f34983e..1ba1b17 100644 --- a/src/controllers/OrdersController.php +++ b/src/controllers/OrdersController.php @@ -53,7 +53,7 @@ public function init(): void * @param array $variables, containing key 'fulfillmentService' * @throws HttpException for malformed requests */ - public function actionProcess($store = null, $action = null): mixed + public function actionProcess($store = null, $do = null): mixed { $request = Craft::$app->request; try { @@ -61,7 +61,7 @@ public function actionProcess($store = null, $action = null): mixed throw new HttpException(401, 'Invalid ShipStation username or password.'); } - switch ($action) { + switch ($do) { case 'export': return $this->getOrders($store); case 'shipnotify': @@ -70,10 +70,10 @@ public function actionProcess($store = null, $action = null): mixed throw new HttpException(400, 'No action set. Set the ?action= parameter as `export` or `shipnotify`.'); } } catch (ErrorException $e) { - $this->logException('Error processing action {action}', ['action' => $action], $e); + $this->logException('Error processing action {action}', ['action' => $do], $e); return $this->asErrorJson($e->getMessage())->setStatusCode(500); } catch (HttpException $e) { - $action = $action; + $action = $do; if ($action) { $this->logException('Error processing action {action}', ['action' => $action], $e); } else { From e22cf119b6a2976e7c07741a790c0fe700346b5e Mon Sep 17 00:00:00 2001 From: Pete Eveleigh Date: Fri, 10 Jun 2022 12:54:06 +0100 Subject: [PATCH 10/27] fix return value for xml service --- src/services/Xml.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/Xml.php b/src/services/Xml.php index 45daf13..a0ced1e 100644 --- a/src/services/Xml.php +++ b/src/services/Xml.php @@ -248,11 +248,11 @@ public function item(\SimpleXMLElement $xml, LineItem $item, $name='Item'): \Sim * @param string $name [description] * @return [type] [description] */ - public function discount(\SimpleXMLElement $xml, Order $order, $name='Item'): ?SimpleXMLElement + public function discount(\SimpleXMLElement $xml, Order $order, $name='Item'): mixed { // If no discount was applied, skip this if ($order->getTotalDiscount() >= 0) { - return; + return null; } $discount_xml = $xml->getName() == $name ? $xml : $xml->addChild($name); From 2c942e59eea5665940dc079e718bc5255749a521 Mon Sep 17 00:00:00 2001 From: Pete Eveleigh Date: Mon, 13 Jun 2022 16:45:10 +0100 Subject: [PATCH 11/27] remove commented out code --- src/controllers/OrdersController.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/controllers/OrdersController.php b/src/controllers/OrdersController.php index 1ba1b17..115a8f0 100644 --- a/src/controllers/OrdersController.php +++ b/src/controllers/OrdersController.php @@ -42,7 +42,6 @@ public function init(): void parent::init(); - //return parent::init(); } /** From d1f14360a08335f8e339a1f33f03f025b4a89cb7 Mon Sep 17 00:00:00 2001 From: Pete Eveleigh Date: Tue, 14 Jun 2022 13:15:14 +0100 Subject: [PATCH 12/27] attempt to fix issue with action param --- src/Plugin.php | 25 +++++++++++++++++++++++++ src/controllers/OrdersController.php | 14 +++++++------- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/Plugin.php b/src/Plugin.php index de78d9f..e64856a 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -4,6 +4,7 @@ use Craft; use craft\events\RegisterUrlRulesEvent; use craft\web\UrlManager; +use craft\web\Application; use craft\services\UserPermissions; use craft\events\RegisterUserPermissionsEvent; use craft\base\Model; @@ -47,6 +48,20 @@ public function init(): void ]); Craft::$app->view->registerTwigExtension(new IsFieldTypeFilter()); + + Craft::$app->on(Application::EVENT_INIT, function() { + $request = Craft::$app->request; + if(in_array('actions', $request->getSegments()) && in_array('shipstationconnect', $request->getSegments())) { + if(array_key_exists('action', $request->getQueryParams())) { + // rename array key to match the action name + $params = $request->getQueryParams(); + $params['ssaction'] = $params['action']; + unset($params['action']); + $request->setQueryParams($params); + } + }; + + }); Event::on( UrlManager::class, @@ -56,6 +71,16 @@ function (RegisterUrlRulesEvent $event) { $event->rules['shipstationconnect/settings/save'] = 'shipstationconnect/settings/save'; } ); + + + Event::on( + UrlManager::class, + UrlManager::EVENT_REGISTER_SITE_URL_RULES, + function (RegisterUrlRulesEvent $event) { + $event->rules['export'] = 'shipstationconnect/orders/export'; + } + ); + Event::on( UserPermissions::class, diff --git a/src/controllers/OrdersController.php b/src/controllers/OrdersController.php index 115a8f0..5d19da4 100644 --- a/src/controllers/OrdersController.php +++ b/src/controllers/OrdersController.php @@ -52,7 +52,7 @@ public function init(): void * @param array $variables, containing key 'fulfillmentService' * @throws HttpException for malformed requests */ - public function actionProcess($store = null, $do = null): mixed + public function actionProcess($store = null, $ssaction = null): mixed { $request = Craft::$app->request; try { @@ -60,7 +60,7 @@ public function actionProcess($store = null, $do = null): mixed throw new HttpException(401, 'Invalid ShipStation username or password.'); } - switch ($do) { + switch ($ssaction) { case 'export': return $this->getOrders($store); case 'shipnotify': @@ -69,19 +69,19 @@ public function actionProcess($store = null, $do = null): mixed throw new HttpException(400, 'No action set. Set the ?action= parameter as `export` or `shipnotify`.'); } } catch (ErrorException $e) { - $this->logException('Error processing action {action}', ['action' => $do], $e); + $this->logException('Error processing action {action}', ['action' => $ssaction], $e); return $this->asErrorJson($e->getMessage())->setStatusCode(500); } catch (HttpException $e) { - $action = $do; - if ($action) { - $this->logException('Error processing action {action}', ['action' => $action], $e); + + if ($ssaction) { + $this->logException('Error processing action {action}', ['action' => $ssaction], $e); } else { $this->logException('An action is required. Supported actions: export, shipnotify.'); } return $this->asErrorJson($e->getMessage())->setStatusCode($e->statusCode); } catch (\Exception $e) { - $this->logException('Error processing action {action}', ['action' => $action], $e); + $this->logException('Error processing action {action}', ['action' => $ssaction], $e); return $this->asErrorJson($e->getMessage())->setStatusCode(500); } } From ae291943f80ec19b8d4baf3c4684fdd8d886802d Mon Sep 17 00:00:00 2001 From: Pete Eveleigh Date: Tue, 14 Jun 2022 14:09:20 +0100 Subject: [PATCH 13/27] add a comment --- src/Plugin.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Plugin.php b/src/Plugin.php index e64856a..fd4a40b 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -49,6 +49,9 @@ public function init(): void Craft::$app->view->registerTwigExtension(new IsFieldTypeFilter()); + // Because Shipstation uses a querystring parameter of 'action' in their requests. + // This interferes with Craft's routing system. + // So we intercept the request and rename that parameter to ssaction IF the request is for one of this plugin's controllers Craft::$app->on(Application::EVENT_INIT, function() { $request = Craft::$app->request; if(in_array('actions', $request->getSegments()) && in_array('shipstationconnect', $request->getSegments())) { From 76b578887d93973d8f6766310813f9e299dc0e07 Mon Sep 17 00:00:00 2001 From: Pete Eveleigh Date: Tue, 14 Jun 2022 14:26:23 +0100 Subject: [PATCH 14/27] =?UTF-8?q?Don't=20do=20request=20tests=20if=20it's?= =?UTF-8?q?=20a=20console=20request=20-=20because=20we=20then=20can't=20in?= =?UTF-8?q?stall=20the=20plugin=20for=20some=20reason=20=C2=AF\=5F(?= =?UTF-8?q?=E3=83=84)=5F/=C2=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Plugin.php | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/Plugin.php b/src/Plugin.php index fd4a40b..3b5ff6f 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -54,15 +54,18 @@ public function init(): void // So we intercept the request and rename that parameter to ssaction IF the request is for one of this plugin's controllers Craft::$app->on(Application::EVENT_INIT, function() { $request = Craft::$app->request; - if(in_array('actions', $request->getSegments()) && in_array('shipstationconnect', $request->getSegments())) { - if(array_key_exists('action', $request->getQueryParams())) { - // rename array key to match the action name - $params = $request->getQueryParams(); - $params['ssaction'] = $params['action']; - unset($params['action']); - $request->setQueryParams($params); - } - }; + if(!$request->isConsoleRequest){ + if(in_array('actions', $request->getSegments()) && in_array('shipstationconnect', $request->getSegments())) { + if(array_key_exists('action', $request->getQueryParams())) { + // rename array key to match the action name + $params = $request->getQueryParams(); + $params['ssaction'] = $params['action']; + unset($params['action']); + $request->setQueryParams($params); + } + }; + } + }); From 1737386bf0b2e60158b2822108a7f544d54dfd56 Mon Sep 17 00:00:00 2001 From: Pete Eveleigh Date: Tue, 14 Jun 2022 14:32:49 +0100 Subject: [PATCH 15/27] bump version --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 5683075..f1811b3 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "description": "A Craft CMS plugin for integrating Craft Commerce with ShipStation", "homepage": "https://github.com/fostercommerce/shipstation-connect", "type": "craft-plugin", - "version": "2.0", + "version": "2.0.1", "keywords": ["craft","plugin","shipstation"], "license": "proprietary", "support": { From 7da93f6643bf0db5865a87f655872865bb5e3f02 Mon Sep 17 00:00:00 2001 From: Pete Eveleigh Date: Tue, 14 Jun 2022 14:37:50 +0100 Subject: [PATCH 16/27] indentation --- src/Plugin.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Plugin.php b/src/Plugin.php index 3b5ff6f..530b0a7 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -57,11 +57,11 @@ public function init(): void if(!$request->isConsoleRequest){ if(in_array('actions', $request->getSegments()) && in_array('shipstationconnect', $request->getSegments())) { if(array_key_exists('action', $request->getQueryParams())) { - // rename array key to match the action name - $params = $request->getQueryParams(); - $params['ssaction'] = $params['action']; - unset($params['action']); - $request->setQueryParams($params); + // rename array key to match the action name + $params = $request->getQueryParams(); + $params['ssaction'] = $params['action']; + unset($params['action']); + $request->setQueryParams($params); } }; } From e357bcd2c867f6224627bf5d5eab4e5b67fcca8b Mon Sep 17 00:00:00 2001 From: Pete Eveleigh <827065+fantasticmachine@users.noreply.github.com> Date: Tue, 14 Jun 2022 15:44:43 +0100 Subject: [PATCH 17/27] Update OrdersController.php revert back to using $action --- src/controllers/OrdersController.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/controllers/OrdersController.php b/src/controllers/OrdersController.php index 5d19da4..58696a1 100644 --- a/src/controllers/OrdersController.php +++ b/src/controllers/OrdersController.php @@ -52,7 +52,7 @@ public function init(): void * @param array $variables, containing key 'fulfillmentService' * @throws HttpException for malformed requests */ - public function actionProcess($store = null, $ssaction = null): mixed + public function actionProcess($store = null, $action = null): mixed { $request = Craft::$app->request; try { @@ -60,7 +60,7 @@ public function actionProcess($store = null, $ssaction = null): mixed throw new HttpException(401, 'Invalid ShipStation username or password.'); } - switch ($ssaction) { + switch ($action) { case 'export': return $this->getOrders($store); case 'shipnotify': @@ -69,11 +69,11 @@ public function actionProcess($store = null, $ssaction = null): mixed throw new HttpException(400, 'No action set. Set the ?action= parameter as `export` or `shipnotify`.'); } } catch (ErrorException $e) { - $this->logException('Error processing action {action}', ['action' => $ssaction], $e); + $this->logException('Error processing action {action}', ['action' => $action], $e); return $this->asErrorJson($e->getMessage())->setStatusCode(500); } catch (HttpException $e) { - if ($ssaction) { + if ($action) { $this->logException('Error processing action {action}', ['action' => $ssaction], $e); } else { $this->logException('An action is required. Supported actions: export, shipnotify.'); @@ -81,7 +81,7 @@ public function actionProcess($store = null, $ssaction = null): mixed return $this->asErrorJson($e->getMessage())->setStatusCode($e->statusCode); } catch (\Exception $e) { - $this->logException('Error processing action {action}', ['action' => $ssaction], $e); + $this->logException('Error processing action {action}', ['action' => $action], $e); return $this->asErrorJson($e->getMessage())->setStatusCode(500); } } From d13a36980263b43b62c2fa46cec0b7f71385b41a Mon Sep 17 00:00:00 2001 From: Pete Eveleigh Date: Wed, 15 Jun 2022 16:17:04 +0100 Subject: [PATCH 18/27] fix variable name --- src/controllers/OrdersController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/OrdersController.php b/src/controllers/OrdersController.php index 58696a1..2e3f6b4 100644 --- a/src/controllers/OrdersController.php +++ b/src/controllers/OrdersController.php @@ -74,7 +74,7 @@ public function actionProcess($store = null, $action = null): mixed } catch (HttpException $e) { if ($action) { - $this->logException('Error processing action {action}', ['action' => $ssaction], $e); + $this->logException('Error processing action {action}', ['action' => $action], $e); } else { $this->logException('An action is required. Supported actions: export, shipnotify.'); } From aa04c0560262ca1a19f897953b04130b5cc8e166 Mon Sep 17 00:00:00 2001 From: Pete Eveleigh Date: Fri, 1 Jul 2022 11:55:48 +0100 Subject: [PATCH 19/27] remove workaround for action parameter bug --- src/Plugin.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Plugin.php b/src/Plugin.php index 530b0a7..b8a4f49 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -52,6 +52,7 @@ public function init(): void // Because Shipstation uses a querystring parameter of 'action' in their requests. // This interferes with Craft's routing system. // So we intercept the request and rename that parameter to ssaction IF the request is for one of this plugin's controllers + /* Craft::$app->on(Application::EVENT_INIT, function() { $request = Craft::$app->request; if(!$request->isConsoleRequest){ @@ -68,6 +69,7 @@ public function init(): void }); + */ Event::on( UrlManager::class, From 0be6667cbdfd94388916740f37355d846ffc8f67 Mon Sep 17 00:00:00 2001 From: Pete Eveleigh Date: Fri, 1 Jul 2022 12:14:24 +0100 Subject: [PATCH 20/27] change to element type --- src/services/Xml.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/services/Xml.php b/src/services/Xml.php index a0ced1e..79edc08 100644 --- a/src/services/Xml.php +++ b/src/services/Xml.php @@ -8,6 +8,7 @@ use craft\commerce\models\LineItem; use craft\commerce\models\Customer; use craft\commerce\models\Address; +use craft\models\User; use yii\base\Component; use yii\base\Event; @@ -331,7 +332,7 @@ public function options(\SimpleXMLElement $xml, $options, $name='Options'): \Sim * @param String $name the name of the child node, default 'Customer' * @return SimpleXMLElement */ - public function customer(\SimpleXMLElement $xml, Customer $customer, $name='Customer'): \SimpleXMLElement + public function customer(\SimpleXMLElement $xml, User $customer, $name='Customer'): \SimpleXMLElement { $customer_xml = $xml->getName() == $name ? $xml : $xml->addChild($name); From 80358175af662c7cb212bf058822af4ffec29af9 Mon Sep 17 00:00:00 2001 From: Pete Eveleigh Date: Fri, 1 Jul 2022 12:19:32 +0100 Subject: [PATCH 21/27] Use proper element --- src/services/Xml.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/Xml.php b/src/services/Xml.php index 79edc08..a8270dd 100644 --- a/src/services/Xml.php +++ b/src/services/Xml.php @@ -8,7 +8,7 @@ use craft\commerce\models\LineItem; use craft\commerce\models\Customer; use craft\commerce\models\Address; -use craft\models\User; +use craft\elements\User; use yii\base\Component; use yii\base\Event; From 465f61b523c061aba0797347fc24598c7dc9c402 Mon Sep 17 00:00:00 2001 From: Pete Eveleigh Date: Fri, 1 Jul 2022 12:21:59 +0100 Subject: [PATCH 22/27] revert previous changes --- src/services/Xml.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/services/Xml.php b/src/services/Xml.php index a8270dd..a0ced1e 100644 --- a/src/services/Xml.php +++ b/src/services/Xml.php @@ -8,7 +8,6 @@ use craft\commerce\models\LineItem; use craft\commerce\models\Customer; use craft\commerce\models\Address; -use craft\elements\User; use yii\base\Component; use yii\base\Event; @@ -332,7 +331,7 @@ public function options(\SimpleXMLElement $xml, $options, $name='Options'): \Sim * @param String $name the name of the child node, default 'Customer' * @return SimpleXMLElement */ - public function customer(\SimpleXMLElement $xml, User $customer, $name='Customer'): \SimpleXMLElement + public function customer(\SimpleXMLElement $xml, Customer $customer, $name='Customer'): \SimpleXMLElement { $customer_xml = $xml->getName() == $name ? $xml : $xml->addChild($name); From 09772ca559c25ddc54fd8a8a45433930e154c2fa Mon Sep 17 00:00:00 2001 From: Pete Eveleigh Date: Fri, 1 Jul 2022 15:27:21 +0100 Subject: [PATCH 23/27] make changes to how user/customer/addresses are handled due to changes in how they work in Craft 4/Commerce 4 --- src/services/Xml.php | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/src/services/Xml.php b/src/services/Xml.php index a0ced1e..578a40f 100644 --- a/src/services/Xml.php +++ b/src/services/Xml.php @@ -6,8 +6,10 @@ use craft\commerce\Plugin as CommercePlugin; use craft\commerce\elements\Order; use craft\commerce\models\LineItem; -use craft\commerce\models\Customer; -use craft\commerce\models\Address; +//use craft\commerce\models\Customer; +//use craft\commerce\models\Address; +use craft\elements\User; +use craft\elements\Address; use yii\base\Component; use yii\base\Event; @@ -331,7 +333,7 @@ public function options(\SimpleXMLElement $xml, $options, $name='Options'): \Sim * @param String $name the name of the child node, default 'Customer' * @return SimpleXMLElement */ - public function customer(\SimpleXMLElement $xml, Customer $customer, $name='Customer'): \SimpleXMLElement + public function customer(\SimpleXMLElement $xml, User $customer, $name='Customer'): \SimpleXMLElement { $customer_xml = $xml->getName() == $name ? $xml : $xml->addChild($name); @@ -366,7 +368,7 @@ function($name) { * @param Customer $customer * @return SimpleXMLElement, or null if no address exists */ - public function billTo(\SimpleXMLElement $customer_xml, Order $order, Customer $customer): ?SimpleXMLElement + public function billTo(\SimpleXMLElement $customer_xml, Order $order, User $customer): ?\SimpleXMLElement { $billingAddress = $order->getBillingAddress(); if (!$billingAddress) { @@ -380,7 +382,8 @@ public function billTo(\SimpleXMLElement $customer_xml, Order $order, Customer $ if ($billingAddress) { $billTo_xml = $this->address($customer_xml, $billingAddress, 'BillTo'); if (!$name = $this->generateName($billingAddress->firstName, $billingAddress->lastName)) { - $user = $customer->getUser(); + //$user = $customer->getUser(); + $user = $customer; if ($user) { $name = $this->generateName($user->firstName, $user->lastName) ?: 'Unknown'; } else { @@ -403,12 +406,13 @@ public function billTo(\SimpleXMLElement $customer_xml, Order $order, Customer $ * @param Customer $customer * @return SimpleXMLElement, or null if no address exists */ - public function shipTo(\SimpleXMLElement $customer_xml, Order $order, Customer $customer): ?SimpleXMLElement + public function shipTo(\SimpleXMLElement $customer_xml, Order $order, User $customer): ?\SimpleXMLElement { $shippingAddress = $order->getShippingAddress(); $shipTo_xml = $this->address($customer_xml, $shippingAddress, 'ShipTo'); if (!$name = $this->generateName($shippingAddress->firstName, $shippingAddress->lastName)) { - $user = $customer->getUser(); + //$user = $customer->getUser(); + $user = $customer; if ($user) { $name = $this->generateName($user->firstName, $user->lastName) ?: 'Unknown'; } else { @@ -434,19 +438,21 @@ public function address(\SimpleXMLElement $xml, Address $address=null, $name='Ad if (!is_null($address)) { $address_mapping = [ - 'Company' => 'businessName', - 'Phone' => 'phone', - 'Address1' => 'address1', - 'Address2' => 'address2', - 'City' => 'city', - 'State' => 'stateText', - 'PostalCode' => 'zipCode', - 'Country' => [ + 'Company' => 'organization', + //'Phone' => 'phone', + 'Address1' => 'addressLine1', + 'Address2' => 'addressLine2', + 'City' => 'locality', + 'State' => 'administrativeArea', + 'PostalCode' => 'postalCode', + /*'Country' => [ 'callback' => function ($address) { return $address->countryId ? $address->getCountry()->iso : null; }, 'cdata' => false, ] + */ + 'Country' => 'countryCode' ]; $this->mapCraftModel($address_xml, $address_mapping, $address); } From 47a8c0c617d4360072d5e8c32c521c48e7478340 Mon Sep 17 00:00:00 2001 From: Pete Eveleigh Date: Tue, 12 Jul 2022 16:03:57 +0100 Subject: [PATCH 24/27] fix return type --- src/controllers/OrdersController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/OrdersController.php b/src/controllers/OrdersController.php index 2e3f6b4..d664013 100644 --- a/src/controllers/OrdersController.php +++ b/src/controllers/OrdersController.php @@ -336,9 +336,9 @@ protected function getShippingInformationFromParams(): array * return to ShipStation as part of the getOrders() method above. * * @throws HttpException, 404 if not found, 406 if order number is invalid - * @return Commerce_Order + * @return Order */ - protected function orderFromParams(): Commerce_Order + protected function orderFromParams(): Order { $request = Craft::$app->getRequest(); if ($orderNumber = $request->getParam('order_number')) { From 10ec645a10fdf4dfa2605a4c21ca6eb7c611395e Mon Sep 17 00:00:00 2001 From: Pete Eveleigh Date: Tue, 12 Jul 2022 16:24:28 +0100 Subject: [PATCH 25/27] fix return values --- src/controllers/OrdersController.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/controllers/OrdersController.php b/src/controllers/OrdersController.php index d664013..3ad43fb 100644 --- a/src/controllers/OrdersController.php +++ b/src/controllers/OrdersController.php @@ -10,6 +10,7 @@ use craft\db\Query; use craft\db\Table; use craft\helpers\App; +use craft\web\Response; use craft\models\MatrixBlockType; use yii\web\HttpException; use yii\base\ErrorException; @@ -238,7 +239,7 @@ private function getBlockTypeByHandle($fieldId, $handle): ?MatrixBlockType * * @throws ErrorException if the order fails to save */ - protected function postShipment(): ?string + protected function postShipment(): null|string|Response { $order = $this->orderFromParams(); From ebe7b2001cb7d3d29ee47cc04f87be1138700bfc Mon Sep 17 00:00:00 2001 From: Pete Eveleigh Date: Tue, 12 Jul 2022 18:02:51 +0100 Subject: [PATCH 26/27] removed mixed return type to retain php 7 compatability --- src/controllers/OrdersController.php | 3 ++- src/services/Xml.php | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/controllers/OrdersController.php b/src/controllers/OrdersController.php index 3ad43fb..6b71d45 100644 --- a/src/controllers/OrdersController.php +++ b/src/controllers/OrdersController.php @@ -15,6 +15,7 @@ use yii\web\HttpException; use yii\base\ErrorException; use yii\base\Event; +use yii\web\Response as YiiResponse; use fostercommerce\shipstationconnect\Plugin; use fostercommerce\shipstationconnect\events\FindOrderEvent; @@ -53,7 +54,7 @@ public function init(): void * @param array $variables, containing key 'fulfillmentService' * @throws HttpException for malformed requests */ - public function actionProcess($store = null, $action = null): mixed + public function actionProcess($store = null, $action = null): null|string|Response|YiiResponse|SimpleXMLElement { $request = Craft::$app->request; try { diff --git a/src/services/Xml.php b/src/services/Xml.php index 578a40f..0e9fdfd 100644 --- a/src/services/Xml.php +++ b/src/services/Xml.php @@ -250,7 +250,7 @@ public function item(\SimpleXMLElement $xml, LineItem $item, $name='Item'): \Sim * @param string $name [description] * @return [type] [description] */ - public function discount(\SimpleXMLElement $xml, Order $order, $name='Item'): mixed + public function discount(\SimpleXMLElement $xml, Order $order, $name='Item'): null|\SimpleXMLElement { // If no discount was applied, skip this if ($order->getTotalDiscount() >= 0) { From e296efaedba387573359c49e02461c8071d7e1af Mon Sep 17 00:00:00 2001 From: Pete Eveleigh Date: Tue, 19 Jul 2022 12:26:46 +0100 Subject: [PATCH 27/27] Update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bd73c2..42aed15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 2.0.1 - 2022-07-19 + +### Added + +- Support for Craft 4/Commerce 4. Note that development for Craft 3 has stopped at 1.3.7. + ## 1.3.7 - 2021-12-01 ### Fixed