From 58377bb34e8403bbf87b54a60eca388beb1987ee Mon Sep 17 00:00:00 2001 From: Nathaniel Hammond Date: Thu, 27 Nov 2025 11:03:52 +0000 Subject: [PATCH 01/10] Bump shopify dep --- composer.json | 2 +- composer.lock | 214 +++++++++++++++--------------- src/templates/webhooks/index.twig | 59 -------- 3 files changed, 108 insertions(+), 167 deletions(-) delete mode 100644 src/templates/webhooks/index.twig diff --git a/composer.json b/composer.json index 4d77183..fcd8e26 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "php": "^8.2", "carnage/php-graphql-client": "^1.14", "craftcms/cms": "^5.0.0-beta.10||^4.15.0", - "shopify/shopify-api": "^5.11.0" + "shopify/shopify-api": "^6.0.0" }, "require-dev": { "craftcms/feed-me": "^6.6.1||^5.9.0", diff --git a/composer.lock b/composer.lock index 4b01964..deedefa 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "24939c1fcdda7ece25f15e8e71719862", + "content-hash": "fffa4c7b5d520b39f150fc69fa3aa850", "packages": [ { "name": "bacon/bacon-qr-code", @@ -390,16 +390,16 @@ }, { "name": "craftcms/cms", - "version": "5.8.18", + "version": "5.8.19", "source": { "type": "git", "url": "https://github.com/craftcms/cms.git", - "reference": "ff394ccb9e70c83f16f47ea159ab2331b5e05084" + "reference": "2fdb2031cdf41875121c3f4b0094551ae95a2c38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/craftcms/cms/zipball/ff394ccb9e70c83f16f47ea159ab2331b5e05084", - "reference": "ff394ccb9e70c83f16f47ea159ab2331b5e05084", + "url": "https://api.github.com/repos/craftcms/cms/zipball/2fdb2031cdf41875121c3f4b0094551ae95a2c38", + "reference": "2fdb2031cdf41875121c3f4b0094551ae95a2c38", "shasum": "" }, "require": { @@ -513,7 +513,7 @@ "rss": "https://github.com/craftcms/cms/releases.atom", "source": "https://github.com/craftcms/cms" }, - "time": "2025-10-06T18:25:26+00:00" + "time": "2025-10-28T19:21:54+00:00" }, { "name": "craftcms/plugin-installer", @@ -773,16 +773,16 @@ }, { "name": "doctrine/collections", - "version": "2.3.0", + "version": "2.4.0", "source": { "type": "git", "url": "https://github.com/doctrine/collections.git", - "reference": "2eb07e5953eed811ce1b309a7478a3b236f2273d" + "reference": "9acfeea2e8666536edff3d77c531261c63680160" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/collections/zipball/2eb07e5953eed811ce1b309a7478a3b236f2273d", - "reference": "2eb07e5953eed811ce1b309a7478a3b236f2273d", + "url": "https://api.github.com/repos/doctrine/collections/zipball/9acfeea2e8666536edff3d77c531261c63680160", + "reference": "9acfeea2e8666536edff3d77c531261c63680160", "shasum": "" }, "require": { @@ -791,11 +791,11 @@ "symfony/polyfill-php84": "^1.30" }, "require-dev": { - "doctrine/coding-standard": "^12", + "doctrine/coding-standard": "^14", "ext-json": "*", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^10.5" + "phpstan/phpstan": "^2.1.30", + "phpstan/phpstan-phpunit": "^2.0.7", + "phpunit/phpunit": "^10.5.58 || ^11.5.42 || ^12.4" }, "type": "library", "autoload": { @@ -839,7 +839,7 @@ ], "support": { "issues": "https://github.com/doctrine/collections/issues", - "source": "https://github.com/doctrine/collections/tree/2.3.0" + "source": "https://github.com/doctrine/collections/tree/2.4.0" }, "funding": [ { @@ -855,7 +855,7 @@ "type": "tidelift" } ], - "time": "2025-03-22T10:17:19+00:00" + "time": "2025-10-25T09:18:13+00:00" }, { "name": "doctrine/deprecations", @@ -1229,20 +1229,20 @@ }, { "name": "ezyang/htmlpurifier", - "version": "v4.18.0", + "version": "v4.19.0", "source": { "type": "git", "url": "https://github.com/ezyang/htmlpurifier.git", - "reference": "cb56001e54359df7ae76dc522d08845dc741621b" + "reference": "b287d2a16aceffbf6e0295559b39662612b77fcf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/cb56001e54359df7ae76dc522d08845dc741621b", - "reference": "cb56001e54359df7ae76dc522d08845dc741621b", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/b287d2a16aceffbf6e0295559b39662612b77fcf", + "reference": "b287d2a16aceffbf6e0295559b39662612b77fcf", "shasum": "" }, "require": { - "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" + "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" }, "require-dev": { "cerdic/css-tidy": "^1.7 || ^2.0", @@ -1284,9 +1284,9 @@ ], "support": { "issues": "https://github.com/ezyang/htmlpurifier/issues", - "source": "https://github.com/ezyang/htmlpurifier/tree/v4.18.0" + "source": "https://github.com/ezyang/htmlpurifier/tree/v4.19.0" }, - "time": "2024-11-01T03:51:45+00:00" + "time": "2025-10-17T16:34:55+00:00" }, { "name": "firebase/php-jwt", @@ -2224,23 +2224,23 @@ }, { "name": "moneyphp/money", - "version": "v4.7.1", + "version": "v4.8.0", "source": { "type": "git", "url": "https://github.com/moneyphp/money.git", - "reference": "1a23f0e1b22e2c59ed5ed70cfbe4cbe696be9348" + "reference": "b358727ea5a5cd2d7475e59c31dfc352440ae7ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/moneyphp/money/zipball/1a23f0e1b22e2c59ed5ed70cfbe4cbe696be9348", - "reference": "1a23f0e1b22e2c59ed5ed70cfbe4cbe696be9348", + "url": "https://api.github.com/repos/moneyphp/money/zipball/b358727ea5a5cd2d7475e59c31dfc352440ae7ec", + "reference": "b358727ea5a5cd2d7475e59c31dfc352440ae7ec", "shasum": "" }, "require": { "ext-bcmath": "*", "ext-filter": "*", "ext-json": "*", - "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" + "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" }, "require-dev": { "cache/taggable-cache": "^1.1.0", @@ -2308,9 +2308,9 @@ ], "support": { "issues": "https://github.com/moneyphp/money/issues", - "source": "https://github.com/moneyphp/money/tree/v4.7.1" + "source": "https://github.com/moneyphp/money/tree/v4.8.0" }, - "time": "2025-06-06T07:12:38+00:00" + "time": "2025-10-23T07:55:09+00:00" }, { "name": "monolog/monolog", @@ -3733,16 +3733,16 @@ }, { "name": "shopify/shopify-api", - "version": "v5.11.0", + "version": "v6.0.0", "source": { "type": "git", "url": "https://github.com/Shopify/shopify-api-php.git", - "reference": "ed1c9cd01b68a0beb89ad770123ebc926bb7a98c" + "reference": "f4d177e8ce062aa302aef10e29bc274ac26ccd19" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Shopify/shopify-api-php/zipball/ed1c9cd01b68a0beb89ad770123ebc926bb7a98c", - "reference": "ed1c9cd01b68a0beb89ad770123ebc926bb7a98c", + "url": "https://api.github.com/repos/Shopify/shopify-api-php/zipball/f4d177e8ce062aa302aef10e29bc274ac26ccd19", + "reference": "f4d177e8ce062aa302aef10e29bc274ac26ccd19", "shasum": "" }, "require": { @@ -3801,9 +3801,9 @@ ], "support": { "issues": "https://github.com/Shopify/shopify-api-php/issues", - "source": "https://github.com/Shopify/shopify-api-php/tree/v5.11.0" + "source": "https://github.com/Shopify/shopify-api-php/tree/v6.0.0" }, - "time": "2025-07-11T14:07:16+00:00" + "time": "2025-10-28T19:19:42+00:00" }, { "name": "spomky-labs/cbor-php", @@ -3890,20 +3890,20 @@ }, { "name": "spomky-labs/pki-framework", - "version": "1.3.0", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/Spomky-Labs/pki-framework.git", - "reference": "eced5b5ce70518b983ff2be486e902bbd15135ae" + "reference": "bf6f55a9d9eb25b7781640221cb54f5c727850d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/eced5b5ce70518b983ff2be486e902bbd15135ae", - "reference": "eced5b5ce70518b983ff2be486e902bbd15135ae", + "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/bf6f55a9d9eb25b7781640221cb54f5c727850d7", + "reference": "bf6f55a9d9eb25b7781640221cb54f5c727850d7", "shasum": "" }, "require": { - "brick/math": "^0.10|^0.11|^0.12|^0.13", + "brick/math": "^0.10|^0.11|^0.12|^0.13|^0.14", "ext-mbstring": "*", "php": ">=8.1" }, @@ -3911,7 +3911,7 @@ "ekino/phpstan-banned-code": "^1.0|^2.0|^3.0", "ext-gmp": "*", "ext-openssl": "*", - "infection/infection": "^0.28|^0.29", + "infection/infection": "^0.28|^0.29|^0.31", "php-parallel-lint/php-parallel-lint": "^1.3", "phpstan/extension-installer": "^1.3|^2.0", "phpstan/phpstan": "^1.8|^2.0", @@ -3921,8 +3921,8 @@ "phpunit/phpunit": "^10.1|^11.0|^12.0", "rector/rector": "^1.0|^2.0", "roave/security-advisories": "dev-latest", - "symfony/string": "^6.4|^7.0", - "symfony/var-dumper": "^6.4|^7.0", + "symfony/string": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0", "symplify/easy-coding-standard": "^12.0" }, "suggest": { @@ -3983,7 +3983,7 @@ ], "support": { "issues": "https://github.com/Spomky-Labs/pki-framework/issues", - "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.3.0" + "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.4.0" }, "funding": [ { @@ -3995,7 +3995,7 @@ "type": "patreon" } ], - "time": "2025-06-13T08:35:04+00:00" + "time": "2025-10-22T08:24:34+00:00" }, { "name": "symfony/css-selector", @@ -4610,16 +4610,16 @@ }, { "name": "symfony/mailer", - "version": "v7.3.4", + "version": "v7.3.5", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "ab97ef2f7acf0216955f5845484235113047a31d" + "reference": "fd497c45ba9c10c37864e19466b090dcb60a50ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/ab97ef2f7acf0216955f5845484235113047a31d", - "reference": "ab97ef2f7acf0216955f5845484235113047a31d", + "url": "https://api.github.com/repos/symfony/mailer/zipball/fd497c45ba9c10c37864e19466b090dcb60a50ba", + "reference": "fd497c45ba9c10c37864e19466b090dcb60a50ba", "shasum": "" }, "require": { @@ -4670,7 +4670,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.3.4" + "source": "https://github.com/symfony/mailer/tree/v7.3.5" }, "funding": [ { @@ -4690,7 +4690,7 @@ "type": "tidelift" } ], - "time": "2025-09-17T05:51:54+00:00" + "time": "2025-10-24T14:27:20+00:00" }, { "name": "symfony/mime", @@ -5821,23 +5821,23 @@ }, { "name": "symfony/property-info", - "version": "v7.3.4", + "version": "v7.3.5", "source": { "type": "git", "url": "https://github.com/symfony/property-info.git", - "reference": "7b6db23f23d13ada41e1cb484748a8ec028fbace" + "reference": "0b346ed259dc5da43535caf243005fe7d4b0f051" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-info/zipball/7b6db23f23d13ada41e1cb484748a8ec028fbace", - "reference": "7b6db23f23d13ada41e1cb484748a8ec028fbace", + "url": "https://api.github.com/repos/symfony/property-info/zipball/0b346ed259dc5da43535caf243005fe7d4b0f051", + "reference": "0b346ed259dc5da43535caf243005fe7d4b0f051", "shasum": "" }, "require": { "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/string": "^6.4|^7.0", - "symfony/type-info": "~7.2.8|^7.3.1" + "symfony/type-info": "^7.3.5" }, "conflict": { "phpdocumentor/reflection-docblock": "<5.2", @@ -5887,7 +5887,7 @@ "validator" ], "support": { - "source": "https://github.com/symfony/property-info/tree/v7.3.4" + "source": "https://github.com/symfony/property-info/tree/v7.3.5" }, "funding": [ { @@ -5907,20 +5907,20 @@ "type": "tidelift" } ], - "time": "2025-09-15T13:55:54+00:00" + "time": "2025-10-05T22:12:41+00:00" }, { "name": "symfony/serializer", - "version": "v6.4.26", + "version": "v6.4.27", "source": { "type": "git", "url": "https://github.com/symfony/serializer.git", - "reference": "48d0477483614d615aa1d5e5d90a45e4c7bfa2c9" + "reference": "28779bbdb398cac3421d0e51f7ca669e4a27c5ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/48d0477483614d615aa1d5e5d90a45e4c7bfa2c9", - "reference": "48d0477483614d615aa1d5e5d90a45e4c7bfa2c9", + "url": "https://api.github.com/repos/symfony/serializer/zipball/28779bbdb398cac3421d0e51f7ca669e4a27c5ac", + "reference": "28779bbdb398cac3421d0e51f7ca669e4a27c5ac", "shasum": "" }, "require": { @@ -5989,7 +5989,7 @@ "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/serializer/tree/v6.4.26" + "source": "https://github.com/symfony/serializer/tree/v6.4.27" }, "funding": [ { @@ -6009,7 +6009,7 @@ "type": "tidelift" } ], - "time": "2025-09-15T13:37:27+00:00" + "time": "2025-10-08T04:24:22+00:00" }, { "name": "symfony/service-contracts", @@ -6186,16 +6186,16 @@ }, { "name": "symfony/type-info", - "version": "v7.3.4", + "version": "v7.3.5", "source": { "type": "git", "url": "https://github.com/symfony/type-info.git", - "reference": "d34eaeb57f39c8a9c97eb72a977c423207dfa35b" + "reference": "8b36f41421160db56914f897b57eaa6a830758b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/type-info/zipball/d34eaeb57f39c8a9c97eb72a977c423207dfa35b", - "reference": "d34eaeb57f39c8a9c97eb72a977c423207dfa35b", + "url": "https://api.github.com/repos/symfony/type-info/zipball/8b36f41421160db56914f897b57eaa6a830758b3", + "reference": "8b36f41421160db56914f897b57eaa6a830758b3", "shasum": "" }, "require": { @@ -6245,7 +6245,7 @@ "type" ], "support": { - "source": "https://github.com/symfony/type-info/tree/v7.3.4" + "source": "https://github.com/symfony/type-info/tree/v7.3.5" }, "funding": [ { @@ -6265,7 +6265,7 @@ "type": "tidelift" } ], - "time": "2025-09-11T15:33:27+00:00" + "time": "2025-10-16T12:30:12+00:00" }, { "name": "symfony/uid", @@ -6343,16 +6343,16 @@ }, { "name": "symfony/var-dumper", - "version": "v7.3.4", + "version": "v7.3.5", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb" + "reference": "476c4ae17f43a9a36650c69879dcf5b1e6ae724d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb", - "reference": "b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/476c4ae17f43a9a36650c69879dcf5b1e6ae724d", + "reference": "476c4ae17f43a9a36650c69879dcf5b1e6ae724d", "shasum": "" }, "require": { @@ -6406,7 +6406,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.3.4" + "source": "https://github.com/symfony/var-dumper/tree/v7.3.5" }, "funding": [ { @@ -6426,20 +6426,20 @@ "type": "tidelift" } ], - "time": "2025-09-11T10:12:26+00:00" + "time": "2025-09-27T09:00:46+00:00" }, { "name": "symfony/yaml", - "version": "v7.3.3", + "version": "v7.3.5", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "d4f4a66866fe2451f61296924767280ab5732d9d" + "reference": "90208e2fc6f68f613eae7ca25a2458a931b1bacc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/d4f4a66866fe2451f61296924767280ab5732d9d", - "reference": "d4f4a66866fe2451f61296924767280ab5732d9d", + "url": "https://api.github.com/repos/symfony/yaml/zipball/90208e2fc6f68f613eae7ca25a2458a931b1bacc", + "reference": "90208e2fc6f68f613eae7ca25a2458a931b1bacc", "shasum": "" }, "require": { @@ -6482,7 +6482,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.3.3" + "source": "https://github.com/symfony/yaml/tree/v7.3.5" }, "funding": [ { @@ -6502,7 +6502,7 @@ "type": "tidelift" } ], - "time": "2025-08-27T11:34:33+00:00" + "time": "2025-09-27T09:00:46+00:00" }, { "name": "theiconic/name-parser", @@ -7448,28 +7448,28 @@ }, { "name": "webmozart/assert", - "version": "1.11.0", + "version": "1.12.1", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + "reference": "9be6926d8b485f55b9229203f962b51ed377ba68" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/9be6926d8b485f55b9229203f962b51ed377ba68", + "reference": "9be6926d8b485f55b9229203f962b51ed377ba68", "shasum": "" }, "require": { "ext-ctype": "*", + "ext-date": "*", + "ext-filter": "*", "php": "^7.2 || ^8.0" }, - "conflict": { - "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<4.6.1 || 4.6.2" - }, - "require-dev": { - "phpunit/phpunit": "^8.5.13" + "suggest": { + "ext-intl": "", + "ext-simplexml": "", + "ext-spl": "" }, "type": "library", "extra": { @@ -7500,9 +7500,9 @@ ], "support": { "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.11.0" + "source": "https://github.com/webmozarts/assert/tree/1.12.1" }, - "time": "2022-06-03T18:03:27+00:00" + "time": "2025-10-29T15:56:20+00:00" }, { "name": "webonyx/graphql-php", @@ -8063,7 +8063,7 @@ "packages-dev": [ { "name": "cakephp/core", - "version": "5.2.8", + "version": "5.2.9", "source": { "type": "git", "url": "https://github.com/cakephp/core.git", @@ -8130,7 +8130,7 @@ }, { "name": "cakephp/utility", - "version": "5.2.8", + "version": "5.2.9", "source": { "type": "git", "url": "https://github.com/cakephp/utility.git", @@ -8296,16 +8296,16 @@ }, { "name": "craftcms/feed-me", - "version": "6.10.1", + "version": "6.11.0", "source": { "type": "git", "url": "https://github.com/craftcms/feed-me.git", - "reference": "8818ee101baf758225884828b7d11688f373b4d8" + "reference": "46cbf5aeb3c6ddabb9ab248c0e6742444533f1bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/craftcms/feed-me/zipball/8818ee101baf758225884828b7d11688f373b4d8", - "reference": "8818ee101baf758225884828b7d11688f373b4d8", + "url": "https://api.github.com/repos/craftcms/feed-me/zipball/46cbf5aeb3c6ddabb9ab248c0e6742444533f1bb", + "reference": "46cbf5aeb3c6ddabb9ab248c0e6742444533f1bb", "shasum": "" }, "require": { @@ -8362,7 +8362,7 @@ "rss": "https://github.com/craftcms/feed-me/commits/master.atom", "source": "https://github.com/craftcms/feed-me" }, - "time": "2025-08-27T17:11:59+00:00" + "time": "2025-10-31T00:53:51+00:00" }, { "name": "craftcms/html-field", @@ -8666,16 +8666,16 @@ }, { "name": "league/csv", - "version": "9.26.0", + "version": "9.27.1", "source": { "type": "git", "url": "https://github.com/thephpleague/csv.git", - "reference": "7fce732754d043f3938899e5183e2d0f3d31b571" + "reference": "26de738b8fccf785397d05ee2fc07b6cd8749797" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/csv/zipball/7fce732754d043f3938899e5183e2d0f3d31b571", - "reference": "7fce732754d043f3938899e5183e2d0f3d31b571", + "url": "https://api.github.com/repos/thephpleague/csv/zipball/26de738b8fccf785397d05ee2fc07b6cd8749797", + "reference": "26de738b8fccf785397d05ee2fc07b6cd8749797", "shasum": "" }, "require": { @@ -8753,7 +8753,7 @@ "type": "github" } ], - "time": "2025-10-01T11:24:54+00:00" + "time": "2025-10-25T08:35:20+00:00" }, { "name": "league/html-to-markdown", diff --git a/src/templates/webhooks/index.twig b/src/templates/webhooks/index.twig deleted file mode 100644 index 00be7e4..0000000 --- a/src/templates/webhooks/index.twig +++ /dev/null @@ -1,59 +0,0 @@ -{# @var craft \craft\web\twig\variables\CraftVariable #} -{% extends "_layouts/cp" %} -{% set selectedSubnavItem = 'webhooks' %} - -{% set title = "Webhooks"|t('shopify') %} - -{% set navItems = {} %} - -{% block content %} - -

{{ "Webhook Management"|t('shopify') }}

- -
-

{{ "Create the webhooks for the current environment."|t('shopify') }}

-
- {{ actionInput('shopify/webhooks/create') }} - {{ redirectInput('shopify/webhooks') }} - {{ csrfInput() }} - -
-
- - {# Divs needed for the Admin Table js below #} -
-
-
- - {% set tableData = [] %} - {% for webhook in webhooks %} - {% set tableData = tableData|merge([{ - id: webhook.id, - title: webhook.topic, - callbackUrl: webhook.endpoint.callbackUrl - }]) %} - {% endfor %} - - {% js %} - var columns = [ - { name: '__slot:title', title: '{{ 'Topic'|t('shopify') }}' }, - { name: 'callbackUrl', title: '{{ 'URL'|t('shopify') }}' } - ]; - - new Craft.VueAdminTable({ - fullPane: false, - columns: columns, - container: '#webhooks-container', - deleteAction: 'shopify/webhooks/delete', - deleteConfirmationMessage: Craft.t('shopify', "Are you sure you want to delete this webhook?"), - deleteFailMessage: Craft.t('shopify', "Webhook could not be deleted"), - deleteSuccessMessage: Craft.t('shopify', "Webhook deleted"), - emptyMessage: Craft.t('shopify', 'No webhooks exist yet.'), - tableData: {{ tableData|json_encode|raw }}, - deleteCallback: function(){ - window.location.reload(); // We need to reload to get the create button showing again - } - }); - {% endjs %} -{% endblock %} - From c1d848b3b3fea94b6b0b885db3e37f984e34225c Mon Sep 17 00:00:00 2001 From: Nathaniel Hammond Date: Thu, 27 Nov 2025 11:04:55 +0000 Subject: [PATCH 02/10] Revert "Bump shopify dep" This reverts commit 58377bb34e8403bbf87b54a60eca388beb1987ee. --- composer.json | 2 +- composer.lock | 214 +++++++++++++++--------------- src/templates/webhooks/index.twig | 59 ++++++++ 3 files changed, 167 insertions(+), 108 deletions(-) create mode 100644 src/templates/webhooks/index.twig diff --git a/composer.json b/composer.json index fcd8e26..4d77183 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "php": "^8.2", "carnage/php-graphql-client": "^1.14", "craftcms/cms": "^5.0.0-beta.10||^4.15.0", - "shopify/shopify-api": "^6.0.0" + "shopify/shopify-api": "^5.11.0" }, "require-dev": { "craftcms/feed-me": "^6.6.1||^5.9.0", diff --git a/composer.lock b/composer.lock index deedefa..4b01964 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "fffa4c7b5d520b39f150fc69fa3aa850", + "content-hash": "24939c1fcdda7ece25f15e8e71719862", "packages": [ { "name": "bacon/bacon-qr-code", @@ -390,16 +390,16 @@ }, { "name": "craftcms/cms", - "version": "5.8.19", + "version": "5.8.18", "source": { "type": "git", "url": "https://github.com/craftcms/cms.git", - "reference": "2fdb2031cdf41875121c3f4b0094551ae95a2c38" + "reference": "ff394ccb9e70c83f16f47ea159ab2331b5e05084" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/craftcms/cms/zipball/2fdb2031cdf41875121c3f4b0094551ae95a2c38", - "reference": "2fdb2031cdf41875121c3f4b0094551ae95a2c38", + "url": "https://api.github.com/repos/craftcms/cms/zipball/ff394ccb9e70c83f16f47ea159ab2331b5e05084", + "reference": "ff394ccb9e70c83f16f47ea159ab2331b5e05084", "shasum": "" }, "require": { @@ -513,7 +513,7 @@ "rss": "https://github.com/craftcms/cms/releases.atom", "source": "https://github.com/craftcms/cms" }, - "time": "2025-10-28T19:21:54+00:00" + "time": "2025-10-06T18:25:26+00:00" }, { "name": "craftcms/plugin-installer", @@ -773,16 +773,16 @@ }, { "name": "doctrine/collections", - "version": "2.4.0", + "version": "2.3.0", "source": { "type": "git", "url": "https://github.com/doctrine/collections.git", - "reference": "9acfeea2e8666536edff3d77c531261c63680160" + "reference": "2eb07e5953eed811ce1b309a7478a3b236f2273d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/collections/zipball/9acfeea2e8666536edff3d77c531261c63680160", - "reference": "9acfeea2e8666536edff3d77c531261c63680160", + "url": "https://api.github.com/repos/doctrine/collections/zipball/2eb07e5953eed811ce1b309a7478a3b236f2273d", + "reference": "2eb07e5953eed811ce1b309a7478a3b236f2273d", "shasum": "" }, "require": { @@ -791,11 +791,11 @@ "symfony/polyfill-php84": "^1.30" }, "require-dev": { - "doctrine/coding-standard": "^14", + "doctrine/coding-standard": "^12", "ext-json": "*", - "phpstan/phpstan": "^2.1.30", - "phpstan/phpstan-phpunit": "^2.0.7", - "phpunit/phpunit": "^10.5.58 || ^11.5.42 || ^12.4" + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^10.5" }, "type": "library", "autoload": { @@ -839,7 +839,7 @@ ], "support": { "issues": "https://github.com/doctrine/collections/issues", - "source": "https://github.com/doctrine/collections/tree/2.4.0" + "source": "https://github.com/doctrine/collections/tree/2.3.0" }, "funding": [ { @@ -855,7 +855,7 @@ "type": "tidelift" } ], - "time": "2025-10-25T09:18:13+00:00" + "time": "2025-03-22T10:17:19+00:00" }, { "name": "doctrine/deprecations", @@ -1229,20 +1229,20 @@ }, { "name": "ezyang/htmlpurifier", - "version": "v4.19.0", + "version": "v4.18.0", "source": { "type": "git", "url": "https://github.com/ezyang/htmlpurifier.git", - "reference": "b287d2a16aceffbf6e0295559b39662612b77fcf" + "reference": "cb56001e54359df7ae76dc522d08845dc741621b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/b287d2a16aceffbf6e0295559b39662612b77fcf", - "reference": "b287d2a16aceffbf6e0295559b39662612b77fcf", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/cb56001e54359df7ae76dc522d08845dc741621b", + "reference": "cb56001e54359df7ae76dc522d08845dc741621b", "shasum": "" }, "require": { - "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" + "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" }, "require-dev": { "cerdic/css-tidy": "^1.7 || ^2.0", @@ -1284,9 +1284,9 @@ ], "support": { "issues": "https://github.com/ezyang/htmlpurifier/issues", - "source": "https://github.com/ezyang/htmlpurifier/tree/v4.19.0" + "source": "https://github.com/ezyang/htmlpurifier/tree/v4.18.0" }, - "time": "2025-10-17T16:34:55+00:00" + "time": "2024-11-01T03:51:45+00:00" }, { "name": "firebase/php-jwt", @@ -2224,23 +2224,23 @@ }, { "name": "moneyphp/money", - "version": "v4.8.0", + "version": "v4.7.1", "source": { "type": "git", "url": "https://github.com/moneyphp/money.git", - "reference": "b358727ea5a5cd2d7475e59c31dfc352440ae7ec" + "reference": "1a23f0e1b22e2c59ed5ed70cfbe4cbe696be9348" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/moneyphp/money/zipball/b358727ea5a5cd2d7475e59c31dfc352440ae7ec", - "reference": "b358727ea5a5cd2d7475e59c31dfc352440ae7ec", + "url": "https://api.github.com/repos/moneyphp/money/zipball/1a23f0e1b22e2c59ed5ed70cfbe4cbe696be9348", + "reference": "1a23f0e1b22e2c59ed5ed70cfbe4cbe696be9348", "shasum": "" }, "require": { "ext-bcmath": "*", "ext-filter": "*", "ext-json": "*", - "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" + "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" }, "require-dev": { "cache/taggable-cache": "^1.1.0", @@ -2308,9 +2308,9 @@ ], "support": { "issues": "https://github.com/moneyphp/money/issues", - "source": "https://github.com/moneyphp/money/tree/v4.8.0" + "source": "https://github.com/moneyphp/money/tree/v4.7.1" }, - "time": "2025-10-23T07:55:09+00:00" + "time": "2025-06-06T07:12:38+00:00" }, { "name": "monolog/monolog", @@ -3733,16 +3733,16 @@ }, { "name": "shopify/shopify-api", - "version": "v6.0.0", + "version": "v5.11.0", "source": { "type": "git", "url": "https://github.com/Shopify/shopify-api-php.git", - "reference": "f4d177e8ce062aa302aef10e29bc274ac26ccd19" + "reference": "ed1c9cd01b68a0beb89ad770123ebc926bb7a98c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Shopify/shopify-api-php/zipball/f4d177e8ce062aa302aef10e29bc274ac26ccd19", - "reference": "f4d177e8ce062aa302aef10e29bc274ac26ccd19", + "url": "https://api.github.com/repos/Shopify/shopify-api-php/zipball/ed1c9cd01b68a0beb89ad770123ebc926bb7a98c", + "reference": "ed1c9cd01b68a0beb89ad770123ebc926bb7a98c", "shasum": "" }, "require": { @@ -3801,9 +3801,9 @@ ], "support": { "issues": "https://github.com/Shopify/shopify-api-php/issues", - "source": "https://github.com/Shopify/shopify-api-php/tree/v6.0.0" + "source": "https://github.com/Shopify/shopify-api-php/tree/v5.11.0" }, - "time": "2025-10-28T19:19:42+00:00" + "time": "2025-07-11T14:07:16+00:00" }, { "name": "spomky-labs/cbor-php", @@ -3890,20 +3890,20 @@ }, { "name": "spomky-labs/pki-framework", - "version": "1.4.0", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/Spomky-Labs/pki-framework.git", - "reference": "bf6f55a9d9eb25b7781640221cb54f5c727850d7" + "reference": "eced5b5ce70518b983ff2be486e902bbd15135ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/bf6f55a9d9eb25b7781640221cb54f5c727850d7", - "reference": "bf6f55a9d9eb25b7781640221cb54f5c727850d7", + "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/eced5b5ce70518b983ff2be486e902bbd15135ae", + "reference": "eced5b5ce70518b983ff2be486e902bbd15135ae", "shasum": "" }, "require": { - "brick/math": "^0.10|^0.11|^0.12|^0.13|^0.14", + "brick/math": "^0.10|^0.11|^0.12|^0.13", "ext-mbstring": "*", "php": ">=8.1" }, @@ -3911,7 +3911,7 @@ "ekino/phpstan-banned-code": "^1.0|^2.0|^3.0", "ext-gmp": "*", "ext-openssl": "*", - "infection/infection": "^0.28|^0.29|^0.31", + "infection/infection": "^0.28|^0.29", "php-parallel-lint/php-parallel-lint": "^1.3", "phpstan/extension-installer": "^1.3|^2.0", "phpstan/phpstan": "^1.8|^2.0", @@ -3921,8 +3921,8 @@ "phpunit/phpunit": "^10.1|^11.0|^12.0", "rector/rector": "^1.0|^2.0", "roave/security-advisories": "dev-latest", - "symfony/string": "^6.4|^7.0|^8.0", - "symfony/var-dumper": "^6.4|^7.0|^8.0", + "symfony/string": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0", "symplify/easy-coding-standard": "^12.0" }, "suggest": { @@ -3983,7 +3983,7 @@ ], "support": { "issues": "https://github.com/Spomky-Labs/pki-framework/issues", - "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.4.0" + "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.3.0" }, "funding": [ { @@ -3995,7 +3995,7 @@ "type": "patreon" } ], - "time": "2025-10-22T08:24:34+00:00" + "time": "2025-06-13T08:35:04+00:00" }, { "name": "symfony/css-selector", @@ -4610,16 +4610,16 @@ }, { "name": "symfony/mailer", - "version": "v7.3.5", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "fd497c45ba9c10c37864e19466b090dcb60a50ba" + "reference": "ab97ef2f7acf0216955f5845484235113047a31d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/fd497c45ba9c10c37864e19466b090dcb60a50ba", - "reference": "fd497c45ba9c10c37864e19466b090dcb60a50ba", + "url": "https://api.github.com/repos/symfony/mailer/zipball/ab97ef2f7acf0216955f5845484235113047a31d", + "reference": "ab97ef2f7acf0216955f5845484235113047a31d", "shasum": "" }, "require": { @@ -4670,7 +4670,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.3.5" + "source": "https://github.com/symfony/mailer/tree/v7.3.4" }, "funding": [ { @@ -4690,7 +4690,7 @@ "type": "tidelift" } ], - "time": "2025-10-24T14:27:20+00:00" + "time": "2025-09-17T05:51:54+00:00" }, { "name": "symfony/mime", @@ -5821,23 +5821,23 @@ }, { "name": "symfony/property-info", - "version": "v7.3.5", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/property-info.git", - "reference": "0b346ed259dc5da43535caf243005fe7d4b0f051" + "reference": "7b6db23f23d13ada41e1cb484748a8ec028fbace" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-info/zipball/0b346ed259dc5da43535caf243005fe7d4b0f051", - "reference": "0b346ed259dc5da43535caf243005fe7d4b0f051", + "url": "https://api.github.com/repos/symfony/property-info/zipball/7b6db23f23d13ada41e1cb484748a8ec028fbace", + "reference": "7b6db23f23d13ada41e1cb484748a8ec028fbace", "shasum": "" }, "require": { "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/string": "^6.4|^7.0", - "symfony/type-info": "^7.3.5" + "symfony/type-info": "~7.2.8|^7.3.1" }, "conflict": { "phpdocumentor/reflection-docblock": "<5.2", @@ -5887,7 +5887,7 @@ "validator" ], "support": { - "source": "https://github.com/symfony/property-info/tree/v7.3.5" + "source": "https://github.com/symfony/property-info/tree/v7.3.4" }, "funding": [ { @@ -5907,20 +5907,20 @@ "type": "tidelift" } ], - "time": "2025-10-05T22:12:41+00:00" + "time": "2025-09-15T13:55:54+00:00" }, { "name": "symfony/serializer", - "version": "v6.4.27", + "version": "v6.4.26", "source": { "type": "git", "url": "https://github.com/symfony/serializer.git", - "reference": "28779bbdb398cac3421d0e51f7ca669e4a27c5ac" + "reference": "48d0477483614d615aa1d5e5d90a45e4c7bfa2c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/28779bbdb398cac3421d0e51f7ca669e4a27c5ac", - "reference": "28779bbdb398cac3421d0e51f7ca669e4a27c5ac", + "url": "https://api.github.com/repos/symfony/serializer/zipball/48d0477483614d615aa1d5e5d90a45e4c7bfa2c9", + "reference": "48d0477483614d615aa1d5e5d90a45e4c7bfa2c9", "shasum": "" }, "require": { @@ -5989,7 +5989,7 @@ "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/serializer/tree/v6.4.27" + "source": "https://github.com/symfony/serializer/tree/v6.4.26" }, "funding": [ { @@ -6009,7 +6009,7 @@ "type": "tidelift" } ], - "time": "2025-10-08T04:24:22+00:00" + "time": "2025-09-15T13:37:27+00:00" }, { "name": "symfony/service-contracts", @@ -6186,16 +6186,16 @@ }, { "name": "symfony/type-info", - "version": "v7.3.5", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/type-info.git", - "reference": "8b36f41421160db56914f897b57eaa6a830758b3" + "reference": "d34eaeb57f39c8a9c97eb72a977c423207dfa35b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/type-info/zipball/8b36f41421160db56914f897b57eaa6a830758b3", - "reference": "8b36f41421160db56914f897b57eaa6a830758b3", + "url": "https://api.github.com/repos/symfony/type-info/zipball/d34eaeb57f39c8a9c97eb72a977c423207dfa35b", + "reference": "d34eaeb57f39c8a9c97eb72a977c423207dfa35b", "shasum": "" }, "require": { @@ -6245,7 +6245,7 @@ "type" ], "support": { - "source": "https://github.com/symfony/type-info/tree/v7.3.5" + "source": "https://github.com/symfony/type-info/tree/v7.3.4" }, "funding": [ { @@ -6265,7 +6265,7 @@ "type": "tidelift" } ], - "time": "2025-10-16T12:30:12+00:00" + "time": "2025-09-11T15:33:27+00:00" }, { "name": "symfony/uid", @@ -6343,16 +6343,16 @@ }, { "name": "symfony/var-dumper", - "version": "v7.3.5", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "476c4ae17f43a9a36650c69879dcf5b1e6ae724d" + "reference": "b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/476c4ae17f43a9a36650c69879dcf5b1e6ae724d", - "reference": "476c4ae17f43a9a36650c69879dcf5b1e6ae724d", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb", + "reference": "b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb", "shasum": "" }, "require": { @@ -6406,7 +6406,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.3.5" + "source": "https://github.com/symfony/var-dumper/tree/v7.3.4" }, "funding": [ { @@ -6426,20 +6426,20 @@ "type": "tidelift" } ], - "time": "2025-09-27T09:00:46+00:00" + "time": "2025-09-11T10:12:26+00:00" }, { "name": "symfony/yaml", - "version": "v7.3.5", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "90208e2fc6f68f613eae7ca25a2458a931b1bacc" + "reference": "d4f4a66866fe2451f61296924767280ab5732d9d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/90208e2fc6f68f613eae7ca25a2458a931b1bacc", - "reference": "90208e2fc6f68f613eae7ca25a2458a931b1bacc", + "url": "https://api.github.com/repos/symfony/yaml/zipball/d4f4a66866fe2451f61296924767280ab5732d9d", + "reference": "d4f4a66866fe2451f61296924767280ab5732d9d", "shasum": "" }, "require": { @@ -6482,7 +6482,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.3.5" + "source": "https://github.com/symfony/yaml/tree/v7.3.3" }, "funding": [ { @@ -6502,7 +6502,7 @@ "type": "tidelift" } ], - "time": "2025-09-27T09:00:46+00:00" + "time": "2025-08-27T11:34:33+00:00" }, { "name": "theiconic/name-parser", @@ -7448,28 +7448,28 @@ }, { "name": "webmozart/assert", - "version": "1.12.1", + "version": "1.11.0", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "9be6926d8b485f55b9229203f962b51ed377ba68" + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/9be6926d8b485f55b9229203f962b51ed377ba68", - "reference": "9be6926d8b485f55b9229203f962b51ed377ba68", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", "shasum": "" }, "require": { "ext-ctype": "*", - "ext-date": "*", - "ext-filter": "*", "php": "^7.2 || ^8.0" }, - "suggest": { - "ext-intl": "", - "ext-simplexml": "", - "ext-spl": "" + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" }, "type": "library", "extra": { @@ -7500,9 +7500,9 @@ ], "support": { "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.12.1" + "source": "https://github.com/webmozarts/assert/tree/1.11.0" }, - "time": "2025-10-29T15:56:20+00:00" + "time": "2022-06-03T18:03:27+00:00" }, { "name": "webonyx/graphql-php", @@ -8063,7 +8063,7 @@ "packages-dev": [ { "name": "cakephp/core", - "version": "5.2.9", + "version": "5.2.8", "source": { "type": "git", "url": "https://github.com/cakephp/core.git", @@ -8130,7 +8130,7 @@ }, { "name": "cakephp/utility", - "version": "5.2.9", + "version": "5.2.8", "source": { "type": "git", "url": "https://github.com/cakephp/utility.git", @@ -8296,16 +8296,16 @@ }, { "name": "craftcms/feed-me", - "version": "6.11.0", + "version": "6.10.1", "source": { "type": "git", "url": "https://github.com/craftcms/feed-me.git", - "reference": "46cbf5aeb3c6ddabb9ab248c0e6742444533f1bb" + "reference": "8818ee101baf758225884828b7d11688f373b4d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/craftcms/feed-me/zipball/46cbf5aeb3c6ddabb9ab248c0e6742444533f1bb", - "reference": "46cbf5aeb3c6ddabb9ab248c0e6742444533f1bb", + "url": "https://api.github.com/repos/craftcms/feed-me/zipball/8818ee101baf758225884828b7d11688f373b4d8", + "reference": "8818ee101baf758225884828b7d11688f373b4d8", "shasum": "" }, "require": { @@ -8362,7 +8362,7 @@ "rss": "https://github.com/craftcms/feed-me/commits/master.atom", "source": "https://github.com/craftcms/feed-me" }, - "time": "2025-10-31T00:53:51+00:00" + "time": "2025-08-27T17:11:59+00:00" }, { "name": "craftcms/html-field", @@ -8666,16 +8666,16 @@ }, { "name": "league/csv", - "version": "9.27.1", + "version": "9.26.0", "source": { "type": "git", "url": "https://github.com/thephpleague/csv.git", - "reference": "26de738b8fccf785397d05ee2fc07b6cd8749797" + "reference": "7fce732754d043f3938899e5183e2d0f3d31b571" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/csv/zipball/26de738b8fccf785397d05ee2fc07b6cd8749797", - "reference": "26de738b8fccf785397d05ee2fc07b6cd8749797", + "url": "https://api.github.com/repos/thephpleague/csv/zipball/7fce732754d043f3938899e5183e2d0f3d31b571", + "reference": "7fce732754d043f3938899e5183e2d0f3d31b571", "shasum": "" }, "require": { @@ -8753,7 +8753,7 @@ "type": "github" } ], - "time": "2025-10-25T08:35:20+00:00" + "time": "2025-10-01T11:24:54+00:00" }, { "name": "league/html-to-markdown", diff --git a/src/templates/webhooks/index.twig b/src/templates/webhooks/index.twig new file mode 100644 index 0000000..00be7e4 --- /dev/null +++ b/src/templates/webhooks/index.twig @@ -0,0 +1,59 @@ +{# @var craft \craft\web\twig\variables\CraftVariable #} +{% extends "_layouts/cp" %} +{% set selectedSubnavItem = 'webhooks' %} + +{% set title = "Webhooks"|t('shopify') %} + +{% set navItems = {} %} + +{% block content %} + +

{{ "Webhook Management"|t('shopify') }}

+ +
+

{{ "Create the webhooks for the current environment."|t('shopify') }}

+
+ {{ actionInput('shopify/webhooks/create') }} + {{ redirectInput('shopify/webhooks') }} + {{ csrfInput() }} + +
+
+ + {# Divs needed for the Admin Table js below #} +
+
+
+ + {% set tableData = [] %} + {% for webhook in webhooks %} + {% set tableData = tableData|merge([{ + id: webhook.id, + title: webhook.topic, + callbackUrl: webhook.endpoint.callbackUrl + }]) %} + {% endfor %} + + {% js %} + var columns = [ + { name: '__slot:title', title: '{{ 'Topic'|t('shopify') }}' }, + { name: 'callbackUrl', title: '{{ 'URL'|t('shopify') }}' } + ]; + + new Craft.VueAdminTable({ + fullPane: false, + columns: columns, + container: '#webhooks-container', + deleteAction: 'shopify/webhooks/delete', + deleteConfirmationMessage: Craft.t('shopify', "Are you sure you want to delete this webhook?"), + deleteFailMessage: Craft.t('shopify', "Webhook could not be deleted"), + deleteSuccessMessage: Craft.t('shopify', "Webhook deleted"), + emptyMessage: Craft.t('shopify', 'No webhooks exist yet.'), + tableData: {{ tableData|json_encode|raw }}, + deleteCallback: function(){ + window.location.reload(); // We need to reload to get the create button showing again + } + }); + {% endjs %} +{% endblock %} + From e3edd7a1ee0b681c8a25029f66a1af777714b33e Mon Sep 17 00:00:00 2001 From: Nathaniel Hammond Date: Thu, 27 Nov 2025 11:06:11 +0000 Subject: [PATCH 03/10] Bump shopify deps --- composer.json | 2 +- composer.lock | 368 ++++++++++++++++++++++++++------------------------ 2 files changed, 189 insertions(+), 181 deletions(-) diff --git a/composer.json b/composer.json index 4d77183..fcd8e26 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "php": "^8.2", "carnage/php-graphql-client": "^1.14", "craftcms/cms": "^5.0.0-beta.10||^4.15.0", - "shopify/shopify-api": "^5.11.0" + "shopify/shopify-api": "^6.0.0" }, "require-dev": { "craftcms/feed-me": "^6.6.1||^5.9.0", diff --git a/composer.lock b/composer.lock index 4b01964..7433963 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "24939c1fcdda7ece25f15e8e71719862", + "content-hash": "fffa4c7b5d520b39f150fc69fa3aa850", "packages": [ { "name": "bacon/bacon-qr-code", @@ -390,16 +390,16 @@ }, { "name": "craftcms/cms", - "version": "5.8.18", + "version": "5.8.20", "source": { "type": "git", "url": "https://github.com/craftcms/cms.git", - "reference": "ff394ccb9e70c83f16f47ea159ab2331b5e05084" + "reference": "cec2d4fa0d9d158df458ea7222e87a1d33fe60bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/craftcms/cms/zipball/ff394ccb9e70c83f16f47ea159ab2331b5e05084", - "reference": "ff394ccb9e70c83f16f47ea159ab2331b5e05084", + "url": "https://api.github.com/repos/craftcms/cms/zipball/cec2d4fa0d9d158df458ea7222e87a1d33fe60bb", + "reference": "cec2d4fa0d9d158df458ea7222e87a1d33fe60bb", "shasum": "" }, "require": { @@ -513,7 +513,7 @@ "rss": "https://github.com/craftcms/cms/releases.atom", "source": "https://github.com/craftcms/cms" }, - "time": "2025-10-06T18:25:26+00:00" + "time": "2025-11-18T16:45:49+00:00" }, { "name": "craftcms/plugin-installer", @@ -773,16 +773,16 @@ }, { "name": "doctrine/collections", - "version": "2.3.0", + "version": "2.4.0", "source": { "type": "git", "url": "https://github.com/doctrine/collections.git", - "reference": "2eb07e5953eed811ce1b309a7478a3b236f2273d" + "reference": "9acfeea2e8666536edff3d77c531261c63680160" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/collections/zipball/2eb07e5953eed811ce1b309a7478a3b236f2273d", - "reference": "2eb07e5953eed811ce1b309a7478a3b236f2273d", + "url": "https://api.github.com/repos/doctrine/collections/zipball/9acfeea2e8666536edff3d77c531261c63680160", + "reference": "9acfeea2e8666536edff3d77c531261c63680160", "shasum": "" }, "require": { @@ -791,11 +791,11 @@ "symfony/polyfill-php84": "^1.30" }, "require-dev": { - "doctrine/coding-standard": "^12", + "doctrine/coding-standard": "^14", "ext-json": "*", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^10.5" + "phpstan/phpstan": "^2.1.30", + "phpstan/phpstan-phpunit": "^2.0.7", + "phpunit/phpunit": "^10.5.58 || ^11.5.42 || ^12.4" }, "type": "library", "autoload": { @@ -839,7 +839,7 @@ ], "support": { "issues": "https://github.com/doctrine/collections/issues", - "source": "https://github.com/doctrine/collections/tree/2.3.0" + "source": "https://github.com/doctrine/collections/tree/2.4.0" }, "funding": [ { @@ -855,7 +855,7 @@ "type": "tidelift" } ], - "time": "2025-03-22T10:17:19+00:00" + "time": "2025-10-25T09:18:13+00:00" }, { "name": "doctrine/deprecations", @@ -1229,20 +1229,20 @@ }, { "name": "ezyang/htmlpurifier", - "version": "v4.18.0", + "version": "v4.19.0", "source": { "type": "git", "url": "https://github.com/ezyang/htmlpurifier.git", - "reference": "cb56001e54359df7ae76dc522d08845dc741621b" + "reference": "b287d2a16aceffbf6e0295559b39662612b77fcf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/cb56001e54359df7ae76dc522d08845dc741621b", - "reference": "cb56001e54359df7ae76dc522d08845dc741621b", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/b287d2a16aceffbf6e0295559b39662612b77fcf", + "reference": "b287d2a16aceffbf6e0295559b39662612b77fcf", "shasum": "" }, "require": { - "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" + "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" }, "require-dev": { "cerdic/css-tidy": "^1.7 || ^2.0", @@ -1284,9 +1284,9 @@ ], "support": { "issues": "https://github.com/ezyang/htmlpurifier/issues", - "source": "https://github.com/ezyang/htmlpurifier/tree/v4.18.0" + "source": "https://github.com/ezyang/htmlpurifier/tree/v4.19.0" }, - "time": "2024-11-01T03:51:45+00:00" + "time": "2025-10-17T16:34:55+00:00" }, { "name": "firebase/php-jwt", @@ -1937,33 +1937,38 @@ }, { "name": "league/uri", - "version": "7.5.1", + "version": "7.6.0", "source": { "type": "git", "url": "https://github.com/thephpleague/uri.git", - "reference": "81fb5145d2644324614cc532b28efd0215bda430" + "reference": "f625804987a0a9112d954f9209d91fec52182344" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri/zipball/81fb5145d2644324614cc532b28efd0215bda430", - "reference": "81fb5145d2644324614cc532b28efd0215bda430", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/f625804987a0a9112d954f9209d91fec52182344", + "reference": "f625804987a0a9112d954f9209d91fec52182344", "shasum": "" }, "require": { - "league/uri-interfaces": "^7.5", - "php": "^8.1" + "league/uri-interfaces": "^7.6", + "php": "^8.1", + "psr/http-factory": "^1" }, "conflict": { "league/uri-schemes": "^1.0" }, "suggest": { "ext-bcmath": "to improve IPV4 host parsing", + "ext-dom": "to convert the URI into an HTML anchor tag", "ext-fileinfo": "to create Data URI from file contennts", "ext-gmp": "to improve IPV4 host parsing", "ext-intl": "to handle IDN host with the best performance", + "ext-uri": "to use the PHP native URI class", "jeremykendall/php-domain-parser": "to resolve Public Suffix and Top Level Domain", "league/uri-components": "Needed to easily manipulate URI objects components", + "league/uri-polyfill": "Needed to backport the PHP URI extension for older versions of PHP", "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle WHATWG URL", "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" }, "type": "library", @@ -1991,6 +1996,7 @@ "description": "URI manipulation library", "homepage": "https://uri.thephpleague.com", "keywords": [ + "URN", "data-uri", "file-uri", "ftp", @@ -2003,9 +2009,11 @@ "psr-7", "query-string", "querystring", + "rfc2141", "rfc3986", "rfc3987", "rfc6570", + "rfc8141", "uri", "uri-template", "url", @@ -2015,7 +2023,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri/tree/7.5.1" + "source": "https://github.com/thephpleague/uri/tree/7.6.0" }, "funding": [ { @@ -2023,26 +2031,25 @@ "type": "github" } ], - "time": "2024-12-08T08:40:02+00:00" + "time": "2025-11-18T12:17:23+00:00" }, { "name": "league/uri-interfaces", - "version": "7.5.0", + "version": "7.6.0", "source": { "type": "git", "url": "https://github.com/thephpleague/uri-interfaces.git", - "reference": "08cfc6c4f3d811584fb09c37e2849e6a7f9b0742" + "reference": "ccbfb51c0445298e7e0b7f4481b942f589665368" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/08cfc6c4f3d811584fb09c37e2849e6a7f9b0742", - "reference": "08cfc6c4f3d811584fb09c37e2849e6a7f9b0742", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/ccbfb51c0445298e7e0b7f4481b942f589665368", + "reference": "ccbfb51c0445298e7e0b7f4481b942f589665368", "shasum": "" }, "require": { "ext-filter": "*", "php": "^8.1", - "psr/http-factory": "^1", "psr/http-message": "^1.1 || ^2.0" }, "suggest": { @@ -2050,6 +2057,7 @@ "ext-gmp": "to improve IPV4 host parsing", "ext-intl": "to handle IDN host with the best performance", "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle WHATWG URL", "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" }, "type": "library", @@ -2074,7 +2082,7 @@ "homepage": "https://nyamsprod.com" } ], - "description": "Common interfaces and classes for URI representation and interaction", + "description": "Common tools for parsing and resolving RFC3987/RFC3986 URI", "homepage": "https://uri.thephpleague.com", "keywords": [ "data-uri", @@ -2099,7 +2107,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri-interfaces/tree/7.5.0" + "source": "https://github.com/thephpleague/uri-interfaces/tree/7.6.0" }, "funding": [ { @@ -2107,7 +2115,7 @@ "type": "github" } ], - "time": "2024-12-08T08:18:47+00:00" + "time": "2025-11-18T12:17:23+00:00" }, { "name": "masterminds/html5", @@ -2224,23 +2232,23 @@ }, { "name": "moneyphp/money", - "version": "v4.7.1", + "version": "v4.8.0", "source": { "type": "git", "url": "https://github.com/moneyphp/money.git", - "reference": "1a23f0e1b22e2c59ed5ed70cfbe4cbe696be9348" + "reference": "b358727ea5a5cd2d7475e59c31dfc352440ae7ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/moneyphp/money/zipball/1a23f0e1b22e2c59ed5ed70cfbe4cbe696be9348", - "reference": "1a23f0e1b22e2c59ed5ed70cfbe4cbe696be9348", + "url": "https://api.github.com/repos/moneyphp/money/zipball/b358727ea5a5cd2d7475e59c31dfc352440ae7ec", + "reference": "b358727ea5a5cd2d7475e59c31dfc352440ae7ec", "shasum": "" }, "require": { "ext-bcmath": "*", "ext-filter": "*", "ext-json": "*", - "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" + "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" }, "require-dev": { "cache/taggable-cache": "^1.1.0", @@ -2308,9 +2316,9 @@ ], "support": { "issues": "https://github.com/moneyphp/money/issues", - "source": "https://github.com/moneyphp/money/tree/v4.7.1" + "source": "https://github.com/moneyphp/money/tree/v4.8.0" }, - "time": "2025-06-06T07:12:38+00:00" + "time": "2025-10-23T07:55:09+00:00" }, { "name": "monolog/monolog", @@ -2589,16 +2597,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.6.3", + "version": "5.6.4", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "94f8051919d1b0369a6bcc7931d679a511c03fe9" + "reference": "90a04bcbf03784066f16038e87e23a0a83cee3c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94f8051919d1b0369a6bcc7931d679a511c03fe9", - "reference": "94f8051919d1b0369a6bcc7931d679a511c03fe9", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/90a04bcbf03784066f16038e87e23a0a83cee3c2", + "reference": "90a04bcbf03784066f16038e87e23a0a83cee3c2", "shasum": "" }, "require": { @@ -2647,22 +2655,22 @@ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.3" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.4" }, - "time": "2025-08-01T19:43:32+00:00" + "time": "2025-11-17T21:13:10+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "1.10.0", + "version": "1.12.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a" + "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/679e3ce485b99e84c775d28e2e96fade9a7fb50a", - "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/92a98ada2b93d9b201a613cb5a33584dde25f195", + "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195", "shasum": "" }, "require": { @@ -2705,9 +2713,9 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.10.0" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.12.0" }, - "time": "2024-11-09T15:12:26+00:00" + "time": "2025-11-21T15:09:14+00:00" }, { "name": "phpstan/phpdoc-parser", @@ -3733,16 +3741,16 @@ }, { "name": "shopify/shopify-api", - "version": "v5.11.0", + "version": "v6.0.0", "source": { "type": "git", "url": "https://github.com/Shopify/shopify-api-php.git", - "reference": "ed1c9cd01b68a0beb89ad770123ebc926bb7a98c" + "reference": "f4d177e8ce062aa302aef10e29bc274ac26ccd19" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Shopify/shopify-api-php/zipball/ed1c9cd01b68a0beb89ad770123ebc926bb7a98c", - "reference": "ed1c9cd01b68a0beb89ad770123ebc926bb7a98c", + "url": "https://api.github.com/repos/Shopify/shopify-api-php/zipball/f4d177e8ce062aa302aef10e29bc274ac26ccd19", + "reference": "f4d177e8ce062aa302aef10e29bc274ac26ccd19", "shasum": "" }, "require": { @@ -3801,46 +3809,34 @@ ], "support": { "issues": "https://github.com/Shopify/shopify-api-php/issues", - "source": "https://github.com/Shopify/shopify-api-php/tree/v5.11.0" + "source": "https://github.com/Shopify/shopify-api-php/tree/v6.0.0" }, - "time": "2025-07-11T14:07:16+00:00" + "time": "2025-10-28T19:19:42+00:00" }, { "name": "spomky-labs/cbor-php", - "version": "3.1.1", + "version": "3.2.2", "source": { "type": "git", "url": "https://github.com/Spomky-Labs/cbor-php.git", - "reference": "5404f3e21cbe72f5cf612aa23db2b922fd2f43bf" + "reference": "2a5fb86aacfe1004611370ead6caa2bfc88435d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Spomky-Labs/cbor-php/zipball/5404f3e21cbe72f5cf612aa23db2b922fd2f43bf", - "reference": "5404f3e21cbe72f5cf612aa23db2b922fd2f43bf", + "url": "https://api.github.com/repos/Spomky-Labs/cbor-php/zipball/2a5fb86aacfe1004611370ead6caa2bfc88435d0", + "reference": "2a5fb86aacfe1004611370ead6caa2bfc88435d0", "shasum": "" }, "require": { - "brick/math": "^0.9|^0.10|^0.11|^0.12|^0.13", + "brick/math": "^0.9|^0.10|^0.11|^0.12|^0.13|^0.14", "ext-mbstring": "*", "php": ">=8.0" }, "require-dev": { - "deptrac/deptrac": "^3.0", - "ekino/phpstan-banned-code": "^1.0|^2.0|^3.0", "ext-json": "*", - "infection/infection": "^0.29", - "php-parallel-lint/php-parallel-lint": "^1.3", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1.0|^2.0", - "phpstan/phpstan-beberlei-assert": "^1.0|^2.0", - "phpstan/phpstan-deprecation-rules": "^1.0|^2.0", - "phpstan/phpstan-phpunit": "^1.0|^2.0", - "phpstan/phpstan-strict-rules": "^1.0|^2.0", - "phpunit/phpunit": "^10.1|^11.0|^12.0", - "rector/rector": "^1.0|^2.0", "roave/security-advisories": "dev-latest", - "symfony/var-dumper": "^6.0|^7.0", - "symplify/easy-coding-standard": "^12.0" + "symfony/error-handler": "^6.4|^7.1|^8.0", + "symfony/var-dumper": "^6.4|^7.1|^8.0" }, "suggest": { "ext-bcmath": "GMP or BCMath extensions will drastically improve the library performance. BCMath extension needed to handle the Big Float and Decimal Fraction Tags", @@ -3874,7 +3870,7 @@ ], "support": { "issues": "https://github.com/Spomky-Labs/cbor-php/issues", - "source": "https://github.com/Spomky-Labs/cbor-php/tree/3.1.1" + "source": "https://github.com/Spomky-Labs/cbor-php/tree/3.2.2" }, "funding": [ { @@ -3886,24 +3882,24 @@ "type": "patreon" } ], - "time": "2025-06-13T11:57:55+00:00" + "time": "2025-11-13T13:00:34+00:00" }, { "name": "spomky-labs/pki-framework", - "version": "1.3.0", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/Spomky-Labs/pki-framework.git", - "reference": "eced5b5ce70518b983ff2be486e902bbd15135ae" + "reference": "bf6f55a9d9eb25b7781640221cb54f5c727850d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/eced5b5ce70518b983ff2be486e902bbd15135ae", - "reference": "eced5b5ce70518b983ff2be486e902bbd15135ae", + "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/bf6f55a9d9eb25b7781640221cb54f5c727850d7", + "reference": "bf6f55a9d9eb25b7781640221cb54f5c727850d7", "shasum": "" }, "require": { - "brick/math": "^0.10|^0.11|^0.12|^0.13", + "brick/math": "^0.10|^0.11|^0.12|^0.13|^0.14", "ext-mbstring": "*", "php": ">=8.1" }, @@ -3911,7 +3907,7 @@ "ekino/phpstan-banned-code": "^1.0|^2.0|^3.0", "ext-gmp": "*", "ext-openssl": "*", - "infection/infection": "^0.28|^0.29", + "infection/infection": "^0.28|^0.29|^0.31", "php-parallel-lint/php-parallel-lint": "^1.3", "phpstan/extension-installer": "^1.3|^2.0", "phpstan/phpstan": "^1.8|^2.0", @@ -3921,8 +3917,8 @@ "phpunit/phpunit": "^10.1|^11.0|^12.0", "rector/rector": "^1.0|^2.0", "roave/security-advisories": "dev-latest", - "symfony/string": "^6.4|^7.0", - "symfony/var-dumper": "^6.4|^7.0", + "symfony/string": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0", "symplify/easy-coding-standard": "^12.0" }, "suggest": { @@ -3983,7 +3979,7 @@ ], "support": { "issues": "https://github.com/Spomky-Labs/pki-framework/issues", - "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.3.0" + "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.4.0" }, "funding": [ { @@ -3995,20 +3991,20 @@ "type": "patreon" } ], - "time": "2025-06-13T08:35:04+00:00" + "time": "2025-10-22T08:24:34+00:00" }, { "name": "symfony/css-selector", - "version": "v7.3.0", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2" + "reference": "84321188c4754e64273b46b406081ad9b18e8614" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/601a5ce9aaad7bf10797e3663faefce9e26c24e2", - "reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/84321188c4754e64273b46b406081ad9b18e8614", + "reference": "84321188c4754e64273b46b406081ad9b18e8614", "shasum": "" }, "require": { @@ -4044,7 +4040,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v7.3.0" + "source": "https://github.com/symfony/css-selector/tree/v7.3.6" }, "funding": [ { @@ -4055,12 +4051,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2025-10-29T17:24:25+00:00" }, { "name": "symfony/deprecation-contracts", @@ -4432,16 +4432,16 @@ }, { "name": "symfony/http-client", - "version": "v7.3.4", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "4b62871a01c49457cf2a8e560af7ee8a94b87a62" + "reference": "3c0a55a2c8e21e30a37022801c11c7ab5a6cb2de" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/4b62871a01c49457cf2a8e560af7ee8a94b87a62", - "reference": "4b62871a01c49457cf2a8e560af7ee8a94b87a62", + "url": "https://api.github.com/repos/symfony/http-client/zipball/3c0a55a2c8e21e30a37022801c11c7ab5a6cb2de", + "reference": "3c0a55a2c8e21e30a37022801c11c7ab5a6cb2de", "shasum": "" }, "require": { @@ -4508,7 +4508,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.3.4" + "source": "https://github.com/symfony/http-client/tree/v7.3.6" }, "funding": [ { @@ -4528,7 +4528,7 @@ "type": "tidelift" } ], - "time": "2025-09-11T10:12:26+00:00" + "time": "2025-11-05T17:41:46+00:00" }, { "name": "symfony/http-client-contracts", @@ -4610,16 +4610,16 @@ }, { "name": "symfony/mailer", - "version": "v7.3.4", + "version": "v7.3.5", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "ab97ef2f7acf0216955f5845484235113047a31d" + "reference": "fd497c45ba9c10c37864e19466b090dcb60a50ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/ab97ef2f7acf0216955f5845484235113047a31d", - "reference": "ab97ef2f7acf0216955f5845484235113047a31d", + "url": "https://api.github.com/repos/symfony/mailer/zipball/fd497c45ba9c10c37864e19466b090dcb60a50ba", + "reference": "fd497c45ba9c10c37864e19466b090dcb60a50ba", "shasum": "" }, "require": { @@ -4670,7 +4670,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.3.4" + "source": "https://github.com/symfony/mailer/tree/v7.3.5" }, "funding": [ { @@ -4690,7 +4690,7 @@ "type": "tidelift" } ], - "time": "2025-09-17T05:51:54+00:00" + "time": "2025-10-24T14:27:20+00:00" }, { "name": "symfony/mime", @@ -5821,23 +5821,23 @@ }, { "name": "symfony/property-info", - "version": "v7.3.4", + "version": "v7.3.5", "source": { "type": "git", "url": "https://github.com/symfony/property-info.git", - "reference": "7b6db23f23d13ada41e1cb484748a8ec028fbace" + "reference": "0b346ed259dc5da43535caf243005fe7d4b0f051" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-info/zipball/7b6db23f23d13ada41e1cb484748a8ec028fbace", - "reference": "7b6db23f23d13ada41e1cb484748a8ec028fbace", + "url": "https://api.github.com/repos/symfony/property-info/zipball/0b346ed259dc5da43535caf243005fe7d4b0f051", + "reference": "0b346ed259dc5da43535caf243005fe7d4b0f051", "shasum": "" }, "require": { "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/string": "^6.4|^7.0", - "symfony/type-info": "~7.2.8|^7.3.1" + "symfony/type-info": "^7.3.5" }, "conflict": { "phpdocumentor/reflection-docblock": "<5.2", @@ -5887,7 +5887,7 @@ "validator" ], "support": { - "source": "https://github.com/symfony/property-info/tree/v7.3.4" + "source": "https://github.com/symfony/property-info/tree/v7.3.5" }, "funding": [ { @@ -5907,20 +5907,20 @@ "type": "tidelift" } ], - "time": "2025-09-15T13:55:54+00:00" + "time": "2025-10-05T22:12:41+00:00" }, { "name": "symfony/serializer", - "version": "v6.4.26", + "version": "v6.4.27", "source": { "type": "git", "url": "https://github.com/symfony/serializer.git", - "reference": "48d0477483614d615aa1d5e5d90a45e4c7bfa2c9" + "reference": "28779bbdb398cac3421d0e51f7ca669e4a27c5ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/48d0477483614d615aa1d5e5d90a45e4c7bfa2c9", - "reference": "48d0477483614d615aa1d5e5d90a45e4c7bfa2c9", + "url": "https://api.github.com/repos/symfony/serializer/zipball/28779bbdb398cac3421d0e51f7ca669e4a27c5ac", + "reference": "28779bbdb398cac3421d0e51f7ca669e4a27c5ac", "shasum": "" }, "require": { @@ -5989,7 +5989,7 @@ "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/serializer/tree/v6.4.26" + "source": "https://github.com/symfony/serializer/tree/v6.4.27" }, "funding": [ { @@ -6009,20 +6009,20 @@ "type": "tidelift" } ], - "time": "2025-09-15T13:37:27+00:00" + "time": "2025-10-08T04:24:22+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.6.0", + "version": "v3.6.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", - "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", "shasum": "" }, "require": { @@ -6076,7 +6076,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" }, "funding": [ { @@ -6087,12 +6087,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-25T09:37:31+00:00" + "time": "2025-07-15T11:30:57+00:00" }, { "name": "symfony/string", @@ -6186,16 +6190,16 @@ }, { "name": "symfony/type-info", - "version": "v7.3.4", + "version": "v7.3.5", "source": { "type": "git", "url": "https://github.com/symfony/type-info.git", - "reference": "d34eaeb57f39c8a9c97eb72a977c423207dfa35b" + "reference": "8b36f41421160db56914f897b57eaa6a830758b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/type-info/zipball/d34eaeb57f39c8a9c97eb72a977c423207dfa35b", - "reference": "d34eaeb57f39c8a9c97eb72a977c423207dfa35b", + "url": "https://api.github.com/repos/symfony/type-info/zipball/8b36f41421160db56914f897b57eaa6a830758b3", + "reference": "8b36f41421160db56914f897b57eaa6a830758b3", "shasum": "" }, "require": { @@ -6245,7 +6249,7 @@ "type" ], "support": { - "source": "https://github.com/symfony/type-info/tree/v7.3.4" + "source": "https://github.com/symfony/type-info/tree/v7.3.5" }, "funding": [ { @@ -6265,7 +6269,7 @@ "type": "tidelift" } ], - "time": "2025-09-11T15:33:27+00:00" + "time": "2025-10-16T12:30:12+00:00" }, { "name": "symfony/uid", @@ -6343,16 +6347,16 @@ }, { "name": "symfony/var-dumper", - "version": "v7.3.4", + "version": "v7.3.5", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb" + "reference": "476c4ae17f43a9a36650c69879dcf5b1e6ae724d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb", - "reference": "b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/476c4ae17f43a9a36650c69879dcf5b1e6ae724d", + "reference": "476c4ae17f43a9a36650c69879dcf5b1e6ae724d", "shasum": "" }, "require": { @@ -6406,7 +6410,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.3.4" + "source": "https://github.com/symfony/var-dumper/tree/v7.3.5" }, "funding": [ { @@ -6426,20 +6430,20 @@ "type": "tidelift" } ], - "time": "2025-09-11T10:12:26+00:00" + "time": "2025-09-27T09:00:46+00:00" }, { "name": "symfony/yaml", - "version": "v7.3.3", + "version": "v7.3.5", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "d4f4a66866fe2451f61296924767280ab5732d9d" + "reference": "90208e2fc6f68f613eae7ca25a2458a931b1bacc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/d4f4a66866fe2451f61296924767280ab5732d9d", - "reference": "d4f4a66866fe2451f61296924767280ab5732d9d", + "url": "https://api.github.com/repos/symfony/yaml/zipball/90208e2fc6f68f613eae7ca25a2458a931b1bacc", + "reference": "90208e2fc6f68f613eae7ca25a2458a931b1bacc", "shasum": "" }, "require": { @@ -6482,7 +6486,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.3.3" + "source": "https://github.com/symfony/yaml/tree/v7.3.5" }, "funding": [ { @@ -6502,7 +6506,7 @@ "type": "tidelift" } ], - "time": "2025-08-27T11:34:33+00:00" + "time": "2025-09-27T09:00:46+00:00" }, { "name": "theiconic/name-parser", @@ -7448,28 +7452,28 @@ }, { "name": "webmozart/assert", - "version": "1.11.0", + "version": "1.12.1", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + "reference": "9be6926d8b485f55b9229203f962b51ed377ba68" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/9be6926d8b485f55b9229203f962b51ed377ba68", + "reference": "9be6926d8b485f55b9229203f962b51ed377ba68", "shasum": "" }, "require": { "ext-ctype": "*", + "ext-date": "*", + "ext-filter": "*", "php": "^7.2 || ^8.0" }, - "conflict": { - "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<4.6.1 || 4.6.2" - }, - "require-dev": { - "phpunit/phpunit": "^8.5.13" + "suggest": { + "ext-intl": "", + "ext-simplexml": "", + "ext-spl": "" }, "type": "library", "extra": { @@ -7500,9 +7504,9 @@ ], "support": { "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.11.0" + "source": "https://github.com/webmozarts/assert/tree/1.12.1" }, - "time": "2022-06-03T18:03:27+00:00" + "time": "2025-10-29T15:56:20+00:00" }, { "name": "webonyx/graphql-php", @@ -8063,7 +8067,7 @@ "packages-dev": [ { "name": "cakephp/core", - "version": "5.2.8", + "version": "5.2.9", "source": { "type": "git", "url": "https://github.com/cakephp/core.git", @@ -8130,7 +8134,7 @@ }, { "name": "cakephp/utility", - "version": "5.2.8", + "version": "5.2.9", "source": { "type": "git", "url": "https://github.com/cakephp/utility.git", @@ -8296,16 +8300,16 @@ }, { "name": "craftcms/feed-me", - "version": "6.10.1", + "version": "6.11.0", "source": { "type": "git", "url": "https://github.com/craftcms/feed-me.git", - "reference": "8818ee101baf758225884828b7d11688f373b4d8" + "reference": "46cbf5aeb3c6ddabb9ab248c0e6742444533f1bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/craftcms/feed-me/zipball/8818ee101baf758225884828b7d11688f373b4d8", - "reference": "8818ee101baf758225884828b7d11688f373b4d8", + "url": "https://api.github.com/repos/craftcms/feed-me/zipball/46cbf5aeb3c6ddabb9ab248c0e6742444533f1bb", + "reference": "46cbf5aeb3c6ddabb9ab248c0e6742444533f1bb", "shasum": "" }, "require": { @@ -8362,7 +8366,7 @@ "rss": "https://github.com/craftcms/feed-me/commits/master.atom", "source": "https://github.com/craftcms/feed-me" }, - "time": "2025-08-27T17:11:59+00:00" + "time": "2025-10-31T00:53:51+00:00" }, { "name": "craftcms/html-field", @@ -8666,16 +8670,16 @@ }, { "name": "league/csv", - "version": "9.26.0", + "version": "9.27.1", "source": { "type": "git", "url": "https://github.com/thephpleague/csv.git", - "reference": "7fce732754d043f3938899e5183e2d0f3d31b571" + "reference": "26de738b8fccf785397d05ee2fc07b6cd8749797" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/csv/zipball/7fce732754d043f3938899e5183e2d0f3d31b571", - "reference": "7fce732754d043f3938899e5183e2d0f3d31b571", + "url": "https://api.github.com/repos/thephpleague/csv/zipball/26de738b8fccf785397d05ee2fc07b6cd8749797", + "reference": "26de738b8fccf785397d05ee2fc07b6cd8749797", "shasum": "" }, "require": { @@ -8753,7 +8757,7 @@ "type": "github" } ], - "time": "2025-10-01T11:24:54+00:00" + "time": "2025-10-25T08:35:20+00:00" }, { "name": "league/html-to-markdown", @@ -9376,16 +9380,16 @@ }, { "name": "symfony/translation-contracts", - "version": "v3.6.0", + "version": "v3.6.1", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d" + "reference": "65a8bc82080447fae78373aa10f8d13b38338977" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/df210c7a2573f1913b2d17cc95f90f53a73d8f7d", - "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/65a8bc82080447fae78373aa10f8d13b38338977", + "reference": "65a8bc82080447fae78373aa10f8d13b38338977", "shasum": "" }, "require": { @@ -9434,7 +9438,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/translation-contracts/tree/v3.6.1" }, "funding": [ { @@ -9445,12 +9449,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-27T08:32:26+00:00" + "time": "2025-07-15T13:41:35+00:00" }, { "name": "symplify/easy-coding-standard", From 1fa402b9238f027a6f0297573d712678dd041d3a Mon Sep 17 00:00:00 2001 From: Nathaniel Hammond Date: Thu, 27 Nov 2025 11:06:47 +0000 Subject: [PATCH 04/10] Add `SHOPIFY_WEBHOOK_BASE_URL` for local dev webhooks --- src/models/Settings.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/models/Settings.php b/src/models/Settings.php index 5294e43..5316104 100644 --- a/src/models/Settings.php +++ b/src/models/Settings.php @@ -270,6 +270,13 @@ public function setProductFieldLayout(mixed $fieldLayout): void */ public function getWebhookUrl(): string { - return UrlHelper::actionUrl('shopify/webhook/handle'); + $url = UrlHelper::actionUrl('shopify/webhook/handle'); + $webhookBaseUrl = App::env('SHOPIFY_WEBHOOK_BASE_URL'); + + if ($webhookBaseUrl) { + $url = StringHelper::replaceFirst($url, rtrim(UrlHelper::baseUrl(), '/'), rtrim($webhookBaseUrl, '/')); + } + + return $url; } } From 35d26e76a82a5ee2ddc68808ac67815405a955d1 Mon Sep 17 00:00:00 2001 From: Nathaniel Hammond Date: Thu, 27 Nov 2025 11:07:17 +0000 Subject: [PATCH 05/10] Move to new way of creating integration apps --- CHANGELOG.md | 10 +++++ src/controllers/WebhooksController.php | 58 ++++++++++++++++++++++++- src/models/Settings.php | 28 +----------- src/services/Api.php | 60 ++++++++++++++++++++++++-- src/services/BulkOperations.php | 1 + src/templates/settings/index.twig | 9 ---- src/templates/webhooks/index.twig | 59 ------------------------- src/translations/en/shopify.php | 2 +- 8 files changed, 126 insertions(+), 101 deletions(-) delete mode 100644 src/templates/webhooks/index.twig diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d77896..6f69f53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Release Notes for Shopify +## Unreleased + +> [!IMPORTANT] +> After updating, this plugin now requires API version `2025-10` and the creation of an app via the Dev Dashboard. +> Webhooks will need to be recreated for the new app. Go to **Shopify** → **Webhooks** and create the missing webhooks. + +- Shopify for Craft now supports version `2025-10` of Shopify’s GraphQL Admin API. +- It is now possible to configure the webhook URLs using the `SHOPIFY_WEBHOOK_BASE_URL` environment variable. ([#185](https://github.com/craftcms/shopify/issues/185)) +- Added `craft\shopify\services\Api::API_ACCESS_TOKEN_CACHE_KEY`. + ## 6.1.1 - 2025-11-06 - Fixed a bug where file storage could be maxed out when using multiple queue workers. diff --git a/src/controllers/WebhooksController.php b/src/controllers/WebhooksController.php index 1ee1b4a..796444c 100644 --- a/src/controllers/WebhooksController.php +++ b/src/controllers/WebhooksController.php @@ -8,6 +8,8 @@ namespace craft\shopify\controllers; use Craft; +use craft\helpers\Html; +use craft\helpers\Json; use craft\shopify\Plugin; use craft\web\assets\admintable\AdminTableAsset; use craft\web\Controller; @@ -41,13 +43,65 @@ public function actionEdit(): YiiResponse } $webhooks = $api->getWebhooks(); + $tableData = []; // If we don't have all webhooks needed for the current environment show the create button - $containsAllWebhooks = $webhooks->filter(function($item) use ($api) { + $containsAllWebhooks = $webhooks->filter(function($item) use ($api, &$tableData) { + $tableData[] = [ + 'id' => $item['id'], + 'title' => $item['topic'], + 'callbackUrl' => $item['endpoint']['callbackUrl'], + ]; return in_array($item['topic'], $api::WEBHOOK_TOPICS) && $item['endpoint']['callbackUrl'] == Plugin::getInstance()->getSettings()->getWebhookUrl(); })->count() === count($api::WEBHOOK_TOPICS); - return $this->renderTemplate('shopify/webhooks/index', compact('webhooks', 'containsAllWebhooks')); + $view->registerTranslations('shopify', [ + 'Are you sure you want to delete this webhook?', + 'No webhooks exist yet.', + 'Topic', + 'URL', + 'Webhook could not be deleted', + 'Webhook deleted', + ]); + + $tableData = Json::encode($tableData); + + $view->registerJs(<<asCpScreen() + ->title(Craft::t('shopify', 'Webhooks')) + ->selectedSubnavItem('webhooks') + ->contentHtml( + Html::tag('p', Craft::t('shopify', 'Webhooks for the current environment.')) . + Html::tag('div', Html::tag('div', '', ['id' => 'webhooks-container']), ['class' => 'field']) + ); + + if (!$containsAllWebhooks) { + $screen->action('shopify/webhooks/create') + ->submitButtonLabel(Craft::t('shopify', 'Create webhooks')); + } + + return $screen; } /** diff --git a/src/models/Settings.php b/src/models/Settings.php index 5316104..d43c7e5 100644 --- a/src/models/Settings.php +++ b/src/models/Settings.php @@ -27,7 +27,6 @@ class Settings extends Model { private string $_apiKey = ''; private string $_apiSecretKey = ''; - private string $_accessToken = ''; private string $_hostName = ''; public string $uriFormat = ''; public string $template = ''; @@ -43,7 +42,7 @@ class Settings extends Model * @see setApiVersion() * @see getApiVersion() */ - private string $_apiVersion = ApiVersion::JULY_2025; + private string $_apiVersion = ApiVersion::OCTOBER_2025; /** * Whether product metafields should be included when syncing products. This adds an extra API request per product. @@ -66,7 +65,7 @@ class Settings extends Model public function rules(): array { return [ - [['apiSecretKey', 'apiKey', 'accessToken', 'hostName', 'apiVersion'], 'required'], + [['apiSecretKey', 'apiKey', 'hostName', 'apiVersion'], 'required'], [['apiVersion'], 'in', 'range' => Plugin::getInstance()->getApi()->getSupportedApiVersions()], [['hostName'], function($attribute) { $hostName = $this->$attribute; @@ -84,7 +83,6 @@ public function attributes() $names[] = 'apiVersion'; $names[] = 'apiKey'; $names[] = 'apiSecretKey'; - $names[] = 'accessToken'; $names[] = 'contextualPricingCountries'; $names[] = 'hostName'; $names[] = 'uriFormat'; @@ -99,7 +97,6 @@ public function fields(): array 'apiVersion' => fn() => $this->getApiVersion(false), 'apiKey' => fn() => $this->getApiKey(false), 'apiSecretKey' => fn() => $this->getApiSecretKey(false), - 'accessToken' => fn() => $this->getAccessToken(false), 'contextualPricingCountries' => fn() => $this->getContextualPricingCountries(false), 'hostName' => fn() => $this->getHostName(false), 'uriFormat' => 'uriFormat', @@ -116,7 +113,6 @@ public function attributeLabels(): array 'apiKey' => Craft::t('shopify', 'Shopify API Key'), 'apiSecretKey' => Craft::t('shopify', 'Shopify API Secret Key'), 'apiVersion' => Craft::t('shopify', 'Shopify API Version'), - 'accessToken' => Craft::t('shopify', 'Shopify Access Token'), 'contextualPricingCountries' => Craft::t('shopify', 'Context Pricing Countries'), 'hostName' => Craft::t('shopify', 'Shopify Host Name'), 'uriFormat' => Craft::t('shopify', 'Product URI format'), @@ -204,26 +200,6 @@ public function getHostName(bool $parse = true): string return ($parse ? App::parseEnv($this->_hostName) : $this->_hostName) ?? ''; } - /** - * @param string $accessToken - * @return void - * @since 6.0.0 - */ - public function setAccessToken(string $accessToken): void - { - $this->_accessToken = $accessToken; - } - - /** - * @param bool $parse - * @return string - * @since 6.0.0 - */ - public function getAccessToken(bool $parse = true): string - { - return ($parse ? App::parseEnv($this->_accessToken) : $this->_accessToken) ?? ''; - } - /** * @param string $contextualPricingCountries * @return void diff --git a/src/services/Api.php b/src/services/Api.php index 15d2cf1..0aaef4a 100644 --- a/src/services/Api.php +++ b/src/services/Api.php @@ -19,6 +19,7 @@ use GraphQL\QueryBuilder\QueryBuilder; use GraphQL\Variable; use GuzzleHttp\Client; +use GuzzleHttp\Exception\GuzzleException; use Illuminate\Support\Collection; use Psr\Http\Client\ClientInterface; use Shopify\ApiVersion; @@ -36,6 +37,7 @@ use Shopify\Rest\Admin2024_10\Product as ShopifyProduct2410; use Shopify\Rest\Admin2024_10\Variant as ShopifyVariant2410; use Shopify\Rest\Base as ShopifyBaseResource; +use Shopify\Utils; use Shopify\Webhooks\Topics; /** @@ -61,6 +63,11 @@ class Api extends Component Topics::SHOP_UPDATE, ]; + /** + * @since 7.0.0 + */ + public const API_ACCESS_TOKEN_CACHE_KEY = 'shopifyApiAccessToken'; + /** * @var Session|null */ @@ -83,9 +90,7 @@ class Api extends Component public function getSupportedApiVersions(): array { return [ - ApiVersion::JULY_2025, - ApiVersion::OCTOBER_2024, - ApiVersion::OCTOBER_2023, + ApiVersion::OCTOBER_2025, ]; } @@ -508,7 +513,7 @@ public function client(): ClientInterface }; $hostName = $pluginSettings->getHostName(true); - $accessToken = $pluginSettings->getAccessToken(true); + $accessToken = $this->getAccessToken(); $this->_session = new Session( id: 'NA', @@ -523,6 +528,53 @@ public function client(): ClientInterface return $this->_session; } + + /** + * @return string + * @throws GuzzleException + * @since 7.0.0 + */ + public function getAccessToken(): string + { + // Try and retrieve the access token from the cache + if ($accessToken = Craft::$app->getCache()->get(self::API_ACCESS_TOKEN_CACHE_KEY)) { + return $accessToken; + } + + $client = Craft::createGuzzleClient([ + 'headers' => [ + 'Content-Type' => 'application/x-www-form-urlencoded', + ], + ]); + + $shopDomain = Utils::sanitizeShopDomain(Plugin::getInstance()->getSettings()->getHostName()); + $endpoint = 'https://' . $shopDomain . '/admin/oauth/access_token'; + + try { + $response = $client->post($endpoint, [ + 'form_params' => [ + 'client_id' => Plugin::getInstance()->getSettings()->getApiKey(true), + 'client_secret' => Plugin::getInstance()->getSettings()->getApiSecretKey(true), + 'grant_type' => 'client_credentials', + ], + ]); + + $body = Json::decodeIfJson((string)$response->getBody()); + + if (!isset($body['access_token'])) { + throw new \Exception('No access token returned from Shopify.'); + } + + // Cache the access token for its lifetime minus 2 minutes + Craft::$app->getCache()->set(self::API_ACCESS_TOKEN_CACHE_KEY, $body['access_token'], $body['expires_in'] - 120); + + return $body['access_token']; + } catch (\Exception $e) { + Craft::error('Could not get access token from Shopify: ' . $e->getMessage(), __METHOD__); + throw $e; + } + } + /** * @return Collection * @throws \Exception diff --git a/src/services/BulkOperations.php b/src/services/BulkOperations.php index 9a19d16..2c4c7ff 100644 --- a/src/services/BulkOperations.php +++ b/src/services/BulkOperations.php @@ -155,6 +155,7 @@ public function nextBulkOperation(): bool ]), (new \GraphQL\Query('userErrors')) ->setSelectionSet([ + 'code', 'field', 'message', ]), diff --git a/src/templates/settings/index.twig b/src/templates/settings/index.twig index 1c3a2a2..4c147f2 100644 --- a/src/templates/settings/index.twig +++ b/src/templates/settings/index.twig @@ -88,15 +88,6 @@ suggestEnvVars: true, }) }} - {{ forms.autosuggestField({ - label: 'Access Token'|t('shopify'), - id: 'accessToken', - name: 'settings[accessToken]', - value: settings.getAccessToken(false), - errors: settings.getErrors('accessToken'), - suggestEnvVars: true, - }) }} - {{ forms.autosuggestField({ label: 'Host Name'|t('shopify'), instructions: 'The Shopify store hostname.'|t('shopify'), diff --git a/src/templates/webhooks/index.twig b/src/templates/webhooks/index.twig deleted file mode 100644 index 00be7e4..0000000 --- a/src/templates/webhooks/index.twig +++ /dev/null @@ -1,59 +0,0 @@ -{# @var craft \craft\web\twig\variables\CraftVariable #} -{% extends "_layouts/cp" %} -{% set selectedSubnavItem = 'webhooks' %} - -{% set title = "Webhooks"|t('shopify') %} - -{% set navItems = {} %} - -{% block content %} - -

{{ "Webhook Management"|t('shopify') }}

- -
-

{{ "Create the webhooks for the current environment."|t('shopify') }}

-
- {{ actionInput('shopify/webhooks/create') }} - {{ redirectInput('shopify/webhooks') }} - {{ csrfInput() }} - -
-
- - {# Divs needed for the Admin Table js below #} -
-
-
- - {% set tableData = [] %} - {% for webhook in webhooks %} - {% set tableData = tableData|merge([{ - id: webhook.id, - title: webhook.topic, - callbackUrl: webhook.endpoint.callbackUrl - }]) %} - {% endfor %} - - {% js %} - var columns = [ - { name: '__slot:title', title: '{{ 'Topic'|t('shopify') }}' }, - { name: 'callbackUrl', title: '{{ 'URL'|t('shopify') }}' } - ]; - - new Craft.VueAdminTable({ - fullPane: false, - columns: columns, - container: '#webhooks-container', - deleteAction: 'shopify/webhooks/delete', - deleteConfirmationMessage: Craft.t('shopify', "Are you sure you want to delete this webhook?"), - deleteFailMessage: Craft.t('shopify', "Webhook could not be deleted"), - deleteSuccessMessage: Craft.t('shopify', "Webhook deleted"), - emptyMessage: Craft.t('shopify', 'No webhooks exist yet.'), - tableData: {{ tableData|json_encode|raw }}, - deleteCallback: function(){ - window.location.reload(); // We need to reload to get the create button showing again - } - }); - {% endjs %} -{% endblock %} - diff --git a/src/translations/en/shopify.php b/src/translations/en/shopify.php index 2acd171..f906d51 100644 --- a/src/translations/en/shopify.php +++ b/src/translations/en/shopify.php @@ -24,7 +24,6 @@ 'Channel' => 'Channel', 'Completed' => 'Completed', 'Couldn’t save settings.' => 'Couldn’t save settings.', - 'Create the webhooks for the current environment.' => 'Create the webhooks for the current environment.', 'Create' => 'Create', 'Created' => 'Created', 'Created At' => 'Created At', @@ -85,6 +84,7 @@ 'Webhook deleted' => 'Webhook deleted', 'Webhooks could not be deleted' => 'Webhooks could not be deleted', 'Webhooks could not be registered.' => 'Webhooks could not be registered.', + 'Webhooks for the current environment.' => 'Webhooks for the current environment.', 'Webhooks registered.' => 'Webhooks registered.', 'Webhooks' => 'Webhooks', '{name} option values: {values}' => '{name} option values: {values}', From 054fcf6a6d1573c4ccb78b7e444842e3b8d04f67 Mon Sep 17 00:00:00 2001 From: Nathaniel Hammond Date: Wed, 3 Dec 2025 10:01:49 +0000 Subject: [PATCH 06/10] Added new Media, Meta fields, Options and Variants native fields --- CHANGELOG.md | 5 ++ src/Plugin.php | 28 +++++++ src/controllers/ProductsController.php | 13 --- src/elements/Product.php | 24 +++++- src/fieldlayoutelements/MediaField.php | 83 +++++++++++++++++++ src/fieldlayoutelements/MetafieldsField.php | 86 +++++++++++++++++++ src/fieldlayoutelements/OptionsField.php | 92 +++++++++++++++++++++ src/fieldlayoutelements/VariantsField.php | 88 ++++++++++++++++++++ src/helpers/Product.php | 27 +++--- src/translations/en/shopify.php | 15 ++++ 10 files changed, 429 insertions(+), 32 deletions(-) create mode 100644 src/fieldlayoutelements/MediaField.php create mode 100644 src/fieldlayoutelements/MetafieldsField.php create mode 100644 src/fieldlayoutelements/OptionsField.php create mode 100644 src/fieldlayoutelements/VariantsField.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f69f53..a6c5e1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,11 @@ - Shopify for Craft now supports version `2025-10` of Shopify’s GraphQL Admin API. - It is now possible to configure the webhook URLs using the `SHOPIFY_WEBHOOK_BASE_URL` environment variable. ([#185](https://github.com/craftcms/shopify/issues/185)) - Added `craft\shopify\services\Api::API_ACCESS_TOKEN_CACHE_KEY`. +- Added `craft\shopify\fieldlayoutelements\MediaField`. +- Added `craft\shopify\fieldlayoutelements\MetafieldsField`. +- Added `craft\shopify\fieldlayoutelements\OptionsField`. +- Added `craft\shopify\fieldlayoutelements\VariantsField`. +- Removed `craft\shopify\controllers\ProductsController::actionRenderCardHtml()` ## 6.1.1 - 2025-11-06 diff --git a/src/Plugin.php b/src/Plugin.php index fc6df28..0594974 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -18,6 +18,7 @@ use craft\console\controllers\ResaveController; use craft\db\Query; use craft\events\DefineConsoleActionsEvent; +use craft\events\DefineFieldLayoutFieldsEvent; use craft\events\RegisterComponentTypesEvent; use craft\events\RegisterUrlRulesEvent; use craft\feedme\events\RegisterFeedMeFieldsEvent; @@ -25,6 +26,7 @@ use craft\helpers\ArrayHelper; use craft\helpers\Console; use craft\helpers\UrlHelper; +use craft\models\FieldLayout; use craft\services\Elements; use craft\services\Fields; use craft\services\Gc; @@ -32,6 +34,10 @@ use craft\shopify\db\Table; use craft\shopify\elements\Product; use craft\shopify\feedme\fields\Products as FeedMeProductsField; +use craft\shopify\fieldlayoutelements\MediaField; +use craft\shopify\fieldlayoutelements\MetafieldsField; +use craft\shopify\fieldlayoutelements\OptionsField; +use craft\shopify\fieldlayoutelements\VariantsField; use craft\shopify\fields\Products as ProductsField; use craft\shopify\handlers\Webhook; use craft\shopify\linktypes\Product as ProductLinkType; @@ -125,6 +131,7 @@ public function init() $this->_registerElementTypes(); $this->_registerUtilityTypes(); $this->_registerFieldTypes(); + $this->_registerFieldLayoutElements(); $this->_registerLinkTypes(); $this->_registerVariables(); $this->_registerResaveCommands(); @@ -256,6 +263,27 @@ private function _registerFieldTypes(): void }); } + /** + * @return void + * @since 7.0.0 + */ + private function _registerFieldLayoutElements(): void + { + Event::on(FieldLayout::class, FieldLayout::EVENT_DEFINE_NATIVE_FIELDS, static function(DefineFieldLayoutFieldsEvent $e) { + /** @var FieldLayout $fieldLayout */ + $fieldLayout = $e->sender; + + switch ($fieldLayout->type) { + case Product::class: + $e->fields[] = VariantsField::class; + $e->fields[] = OptionsField::class; + $e->fields[] = MetafieldsField::class; + $e->fields[] = MediaField::class; + break; + } + }); + } + /** * Register Link types * diff --git a/src/controllers/ProductsController.php b/src/controllers/ProductsController.php index c764f53..cfa53e7 100644 --- a/src/controllers/ProductsController.php +++ b/src/controllers/ProductsController.php @@ -53,17 +53,4 @@ public function actionSync(): ?Response return $this->asSuccess(Craft::t('shopify', 'Products sync created')); } - - /** - * Renders the card HTML. - * - * @return string - */ - public function actionRenderCardHtml(): string - { - $id = (int)Craft::$app->request->getParam('id'); - /** @var Product $product */ - $product = Product::find()->id($id)->status(null)->one(); - return ProductHelper::renderCardHtml($product); - } } diff --git a/src/elements/Product.php b/src/elements/Product.php index 04bcdc0..8229e9f 100644 --- a/src/elements/Product.php +++ b/src/elements/Product.php @@ -20,6 +20,9 @@ use craft\models\FieldLayout; use craft\shopify\elements\conditions\products\ProductCondition; use craft\shopify\elements\db\ProductQuery; +use craft\shopify\fieldlayoutelements\MetafieldsField; +use craft\shopify\fieldlayoutelements\OptionsField; +use craft\shopify\fieldlayoutelements\VariantsField; use craft\shopify\helpers\Product as ProductHelper; use craft\shopify\Plugin; use craft\shopify\records\Product as ProductRecord; @@ -696,8 +699,25 @@ public function getSidebarHtml(bool $static): string { /** @noinspection PhpUnhandledExceptionInspection */ Craft::$app->getView()->registerAssetBundle(ShopifyCpAsset::class); - $productCard = ProductHelper::renderCardHtml($this); - return $productCard . parent::getSidebarHtml($static); + + // Conditionally show metadata in the sidebar dependent on the field layout + $excludeKeys = []; + $this->getFieldLayout()->getFields(Function ($field) use (&$excludeKeys) { + if ($field instanceof VariantsField) { + $excludeKeys[] = 'Variants'; + return true; + } else if ($field instanceof OptionsField) { + $excludeKeys[] = 'Options'; + return true; + } else if ($field instanceof MetafieldsField) { + $excludeKeys[] = 'Metafields'; + return true; + } + + return false; + }); + + return ProductHelper::renderCardHtml($this, $excludeKeys) . parent::getSidebarHtml($static); } /** diff --git a/src/fieldlayoutelements/MediaField.php b/src/fieldlayoutelements/MediaField.php new file mode 100644 index 0000000..87473b7 --- /dev/null +++ b/src/fieldlayoutelements/MediaField.php @@ -0,0 +1,83 @@ + + * @since 7.0.0 + */ +class MediaField extends BaseNativeField +{ + /** + * @inheritdoc + */ + public string $attribute = 'images'; + + /** + * @inheritdoc + */ + public function hasCustomWidth(): bool + { + return false; + } + + /** + * @inheritdoc + */ + protected function defaultLabel(ElementInterface $element = null, bool $static = false): ?string + { + return Craft::t('shopify', 'Media'); + } + + /** + * @inheritdoc + */ + protected function inputHtml(ElementInterface $element = null, bool $static = false): ?string + { + if (!$element instanceof Product) { + throw new InvalidArgumentException(__CLASS__ . ' can only be used in product field layouts.'); + } + + $media = $element->getImages(); + + if (empty($media)) { + return Html::beginTag('div', ['class' => 'zilch']) . + Html::tag('p', Craft::t('shopify', 'This product has no media.')) . + Html::endTag('div'); + } + + $html = + Html::beginTag('div', ['class' => 'elements']) . + Html::beginTag('ul', ['class' => 'thumbsview']); + + foreach ($media as $item) { + $html .= + Html::beginTag('li') . + Html::beginTag('div', ['class' => 'chip large element']) . + Html::tag('div', Html::img($item['image']['url']), ['class' => 'thumb']) . + Html::endTag('div') . + Html::endTag('li'); + } + + $html .= Html::endTag('ul') . + Html::endTag('div'); + + return $html; + } +} diff --git a/src/fieldlayoutelements/MetafieldsField.php b/src/fieldlayoutelements/MetafieldsField.php new file mode 100644 index 0000000..ec9d5c2 --- /dev/null +++ b/src/fieldlayoutelements/MetafieldsField.php @@ -0,0 +1,86 @@ + + * @since 7.0.0 + */ +class MetafieldsField extends BaseNativeField +{ + /** + * @inheritdoc + */ + public string $attribute = 'metafields'; + + /** + * @inheritdoc + */ + public function hasCustomWidth(): bool + { + return false; + } + + /** + * @inheritdoc + */ + protected function defaultLabel(ElementInterface $element = null, bool $static = false): ?string + { + return Craft::t('shopify', 'Meta fields'); + } + + /** + * @inheritdoc + */ + protected function inputHtml(ElementInterface $element = null, bool $static = false): ?string + { + if (!$element instanceof Product) { + throw new InvalidArgumentException(__CLASS__ . ' can only be used in product field layouts.'); + } + + $metafields = $element->getMetafields(); + + if (empty($metafields)) { + return Html::beginTag('div', ['class' => 'zilch']) . + Html::tag('p', Craft::t('shopify', 'This product has no meta fields.')) . + Html::endTag('div'); + } + + $cols = [ + 'key' => ['heading' => Craft::t('shopify', 'Key'), 'type' => 'html'], + 'value' => ['heading' => Craft::t('shopify', 'Value'), 'type' => 'html'], + ]; + + $tableData = []; + foreach ($metafields as $key => $value) { + $tableData[] = [ + 'key' => Html::tag('code', Html::encode($key)), + 'value' => Html::encode($value), + ]; + } + + return Cp::editableTableHtml([ + 'id' => $this->id(), + 'name' => $this->baseInputName(), + 'cols' => $cols, + 'rows' => $tableData, + 'static' => true, + ]); + } +} diff --git a/src/fieldlayoutelements/OptionsField.php b/src/fieldlayoutelements/OptionsField.php new file mode 100644 index 0000000..0835f58 --- /dev/null +++ b/src/fieldlayoutelements/OptionsField.php @@ -0,0 +1,92 @@ + + * @since 7.0.0 + */ +class OptionsField extends BaseNativeField +{ + /** + * @inheritdoc + */ + public string $attribute = 'options'; + + /** + * @inheritdoc + */ + public function hasCustomWidth(): bool + { + return false; + } + + /** + * @inheritdoc + */ + protected function defaultLabel(ElementInterface $element = null, bool $static = false): ?string + { + return Craft::t('shopify', 'Options'); + } + + /** + * @inheritdoc + */ + protected function inputHtml(ElementInterface $element = null, bool $static = false): ?string + { + if (!$element instanceof Product) { + throw new InvalidArgumentException(__CLASS__ . ' can only be used in product field layouts.'); + } + + $options = $element->getOptions(); + + if (empty($options)) { + return Html::beginTag('div', ['class' => 'zilch']) . + Html::tag('p', Craft::t('shopify', 'This product has no options.')) . + Html::endTag('div'); + } + + $cols = [ + 'option' => ['heading' => Craft::t('shopify', 'Option'), 'type' => 'html'], + 'values' => ['heading' => Craft::t('shopify', 'Values'), 'type' => 'html'], + 'hasVariants' => ['heading' => Craft::t('shopify', 'Has variants'), 'type' => 'html'], + ]; + + $tableData = []; + foreach ($options as $opt) { + foreach ($opt['optionValues'] as $i => $val) { + $tableData[] = [ + 'option' => $i === 0 ? Html::tag('strong', Html::encode($opt['name'])) : '', + 'values' => Html::encode($val['name']), + 'hasVariants' => (bool)$val['hasVariants'] ? Html::tag('div', Cp::iconSvg('check'), [ + 'class' => array_filter(['thumb', 'cp-icon', Color::Green->value]), + ]) : '', + ]; + } + } + + return Cp::editableTableHtml([ + 'id' => $this->id(), + 'name' => $this->baseInputName(), + 'cols' => $cols, + 'rows' => $tableData, + 'static' => true, + ]); + } +} diff --git a/src/fieldlayoutelements/VariantsField.php b/src/fieldlayoutelements/VariantsField.php new file mode 100644 index 0000000..b5c7962 --- /dev/null +++ b/src/fieldlayoutelements/VariantsField.php @@ -0,0 +1,88 @@ + + * @since 7.0.0 + */ +class VariantsField extends BaseNativeField +{ + /** + * @inheritdoc + */ + public string $attribute = 'variants'; + + /** + * @inheritdoc + */ + public function hasCustomWidth(): bool + { + return false; + } + + /** + * @inheritdoc + */ + protected function defaultLabel(ElementInterface $element = null, bool $static = false): ?string + { + return Craft::t('shopify', 'Variants'); + } + + /** + * @inheritdoc + */ + protected function inputHtml(ElementInterface $element = null, bool $static = false): ?string + { + if (!$element instanceof Product) { + throw new InvalidArgumentException(__CLASS__ . ' can only be used in product field layouts.'); + } + + $variants = $element->getVariants(); + + $cols = [ + 'title' => ['heading' => Craft::t('shopify', 'Variant'), 'type' => 'html'], + 'sku' => ['heading' => Craft::t('shopify', 'SKU'), 'type' => 'html'], + 'price' => ['heading' => Craft::t('shopify', 'Price'), 'type' => 'html'], + ]; + + foreach ($variants as &$variant) { + $link = sprintf('%s/variants/%s', $element->getShopifyEditUrl(), str_replace('gid://shopify/ProductVariant/', '', $variant['id'])); + $variant['title'] = Html::a(Html::encode($variant['title']), $link, [ + 'aria-label' => Craft::t('shopify', 'Edit variant {title} on Shopify', ['title' => $variant['title']]), + 'target' => '_blank', + 'class' => '' + ]); + $variant['sku'] = Html::tag('code', $variant['sku']); + } + + if (empty($variants)) { + return Html::beginTag('div', ['class' => 'zilch']) . + Html::tag('p', Craft::t('shopify', 'This product has no variants.')) . + Html::endTag('div'); + } + + return Cp::editableTableHtml([ + 'id' => $this->id(), + 'name' => $this->baseInputName(), + 'cols' => $cols, + 'rows' => $variants, + 'static' => true, + ]); + } +} diff --git a/src/helpers/Product.php b/src/helpers/Product.php index 3363fde..fb37912 100644 --- a/src/helpers/Product.php +++ b/src/helpers/Product.php @@ -29,10 +29,11 @@ class Product { /** * @param ProductElement $product + * @param array $excludeMetaDataKeys * @return string * @throws InvalidConfigException */ - public static function renderCardHtml(ProductElement $product): string + public static function renderCardHtml(ProductElement $product, array $excludeMetaDataKeys = []): string { $formatter = Craft::$app->getFormatter(); @@ -105,7 +106,7 @@ public static function renderCardHtml(ProductElement $product): string // Metafields if (count($product->getMetafields()) > 0) { - $meta[Craft::t('shopify', 'Metafields')] = collect($product->getMetafields()) + $meta[Craft::t('shopify', 'Meta fields')] = collect($product->getMetafields()) ->keys() ->join(', '); } @@ -116,14 +117,13 @@ public static function renderCardHtml(ProductElement $product): string $meta[Craft::t('shopify', 'Published at')] = $formatter->asDatetime($product->publishedAt, Formatter::FORMAT_WIDTH_SHORT); $meta[Craft::t('shopify', 'Updated at')] = $formatter->asDatetime($product->updatedAt, Formatter::FORMAT_WIDTH_SHORT); - $metadataHtml = Cp::metadataHtml($meta); + foreach ($excludeMetaDataKeys as $key) { + if (array_key_exists($key, $meta)) { + unset($meta[$key]); + } + } - $spinner = Html::tag('div', '', [ - 'class' => 'spinner', - 'hx' => [ - 'indicator', - ], - ]); + $metadataHtml = Cp::metadataHtml($meta); // This is the date updated in the database which represents the last time it was updated from a Shopify webhook or sync. /** @var ShopifyData $productData */ @@ -132,20 +132,13 @@ public static function renderCardHtml(ProductElement $product): string $now = new \DateTime(); $diff = $now->diff($dateUpdated); $duration = DateTimeHelper::humanDuration($diff, false); - $footer = Html::tag('div', 'Updated ' . $duration . ' ago.' . $spinner, [ + $footer = Html::tag('div', 'Updated ' . $duration . ' ago.', [ 'class' => 'pec-footer', ]); return Html::tag('div', $cardHeader . $hr . $metadataHtml . $footer, [ 'class' => 'meta proxy-element-card', 'id' => 'pec-' . $product->id, - 'hx' => [ - 'get' => UrlHelper::actionUrl('shopify/products/render-card-html', [ - 'id' => $product->id, - ]), - 'swap' => 'outerHTML', - 'trigger' => 'every 15s', - ], ]); } diff --git a/src/translations/en/shopify.php b/src/translations/en/shopify.php index f906d51..bf33188 100644 --- a/src/translations/en/shopify.php +++ b/src/translations/en/shopify.php @@ -29,16 +29,23 @@ 'Created At' => 'Created At', 'Description HTML' => 'Description HTML', 'Draft in Shopify' => 'Draft in Shopify', + 'Edit variant {title} on Shopify' => 'Edit variant {title} on Shopify', 'Failed to create products sync' => 'Failed to create products sync', 'Failed to delete sync' => 'Failed to delete sync', 'General' => 'General', 'Handle' => 'Handle', + 'Has variants' => 'Has variants', + 'Key' => 'Key', 'Live' => 'Live', + 'Media' => 'Media', + 'Meta fields' => 'Meta fields', 'Meta Fields' => 'Meta Fields', 'New Product' => 'New Product', 'No Shopify session available.' => 'No Shopify session available.', 'Objects' => 'Objects', + 'Option' => 'Option', 'Options' => 'Options', + 'Price' => 'Price', 'Processing' => 'Processing', 'Processing bulk operation data' => 'Processing bulk operation data', 'Product Template' => 'Product Template', @@ -65,6 +72,7 @@ 'Shopify Status' => 'Shopify Status', 'Shopify plugin loaded' => 'Shopify plugin loaded', 'Shopify' => 'Shopify', + 'SKU' => 'SKU', 'Status' => 'Status', 'Supported API versions: {versions}' => 'Supported API versions: {versions}', 'Sync all' => 'Sync all', @@ -73,12 +81,19 @@ 'Sync deleted' => 'Sync deleted', 'Tags' => 'Tags', 'Template Suffix' => 'Template Suffix', + 'This product has no media.' => 'This product has no media.', + 'This product has no meta fields.' => 'This product has no meta fields.', + 'This product has no options.' => 'This product has no options.', + 'This product has no variants.' => 'This product has no variants.', 'Total variants' => 'Total variants', 'Unpublished' => 'Unpublished', 'Untitled product' => 'Untitled product', 'Updated At' => 'Updated At', 'Updating product metafields for “{title}”' => 'Updating product metafields for “{title}”', 'Updating product variants for “{title}”' => 'Updating product variants for “{title}”', + 'Value' => 'Value', + 'Values' => 'Values', + 'Variant' => 'Variant', 'Variants' => 'Variants', 'Vendor' => 'Vendor', 'Webhook deleted' => 'Webhook deleted', From 3ff4ecfe421b110c8b118f75478a76d4c22d5576 Mon Sep 17 00:00:00 2001 From: Nathaniel Hammond Date: Wed, 14 Jan 2026 13:43:51 +0000 Subject: [PATCH 07/10] Move variants to be actual models --- CHANGELOG.md | 6 +- src/collections/VariantCollection.php | 73 ++++++++++ src/elements/Product.php | 66 ++++++--- src/models/Variant.php | 185 ++++++++++++++++++++++++++ src/records/ShopifyData.php | 1 + src/services/Products.php | 44 +++++- 6 files changed, 353 insertions(+), 22 deletions(-) create mode 100644 src/collections/VariantCollection.php create mode 100644 src/models/Variant.php diff --git a/CHANGELOG.md b/CHANGELOG.md index a6c5e1d..5f3895c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,12 +8,16 @@ - Shopify for Craft now supports version `2025-10` of Shopify’s GraphQL Admin API. - It is now possible to configure the webhook URLs using the `SHOPIFY_WEBHOOK_BASE_URL` environment variable. ([#185](https://github.com/craftcms/shopify/issues/185)) -- Added `craft\shopify\services\Api::API_ACCESS_TOKEN_CACHE_KEY`. +- Fixed a bug where product slugs weren’t syncing correctly. +- Added `craft\shopify\collections\VariantCollection`. - Added `craft\shopify\fieldlayoutelements\MediaField`. - Added `craft\shopify\fieldlayoutelements\MetafieldsField`. - Added `craft\shopify\fieldlayoutelements\OptionsField`. - Added `craft\shopify\fieldlayoutelements\VariantsField`. +- Added `craft\shopify\models\Variant`. +- Added `craft\shopify\services\Api::API_ACCESS_TOKEN_CACHE_KEY`. - Removed `craft\shopify\controllers\ProductsController::actionRenderCardHtml()` +- `craft\shopify\elements\Product::getVariants()` now returns a collection. ## 6.1.1 - 2025-11-06 diff --git a/src/collections/VariantCollection.php b/src/collections/VariantCollection.php new file mode 100644 index 0000000..27b63c3 --- /dev/null +++ b/src/collections/VariantCollection.php @@ -0,0 +1,73 @@ + + * + * @author Pixel & Tonic, Inc. + * @since 7.0.0 + */ +class VariantCollection extends Collection +{ + /** + * Creates a VariantCollection from an array of Variant attributes. + * + * @param array $items + * @return static + */ + public static function make($items = []) + { + foreach ($items as &$item) { + if ($item instanceof Variant) { + continue; + } else if (is_array($item)) { + $item += ['class' => Variant::class]; + $item = \Craft::createObject($item); + } else if ($item instanceof ShopifyData) { + $item = Craft::createObject([ + 'class' => Variant::class, + 'id' => $item->id, + 'shopifyId' => $item->shopifyId, + 'type' => $item->type, + 'parentId' => $item->parentId, + 'dateCreated' => $item->dateCreated, + 'dateUpdated' => $item->dateUpdated, + 'uid' => $item->uid, + 'data' => $item->data, + ]); + } else { + throw new \InvalidArgumentException('Items must be arrays, ShopifyData instances, or Variant instances.'); + } + } + + /** @var static $collection */ + $collection = parent::make($items); + return $collection; + } + + /** + * Returns the cheapest variant in the collection. + * + * @return Variant|null The cheapest variant in the collection, or null if there aren't any + */ + public function cheapest(): ?Variant + { + $variant = $this->sortBy('price')->first(); + return $variant instanceof Variant ? $variant : null; + } +} diff --git a/src/elements/Product.php b/src/elements/Product.php index 8229e9f..f7fc6db 100644 --- a/src/elements/Product.php +++ b/src/elements/Product.php @@ -18,18 +18,22 @@ use craft\helpers\Template; use craft\helpers\UrlHelper; use craft\models\FieldLayout; +use craft\shopify\collections\VariantCollection; use craft\shopify\elements\conditions\products\ProductCondition; use craft\shopify\elements\db\ProductQuery; use craft\shopify\fieldlayoutelements\MetafieldsField; use craft\shopify\fieldlayoutelements\OptionsField; use craft\shopify\fieldlayoutelements\VariantsField; use craft\shopify\helpers\Product as ProductHelper; +use craft\shopify\models\Variant; use craft\shopify\Plugin; use craft\shopify\records\Product as ProductRecord; +use craft\shopify\records\ShopifyData; use craft\shopify\web\assets\shopifycp\ShopifyCpAsset; use craft\web\CpScreenResponseBehavior; use DateTime; use Exception; +use Illuminate\Support\Collection; use yii\base\InvalidConfigException; use yii\helpers\Html as HtmlHelper; use yii\web\Response; @@ -167,9 +171,9 @@ public function getDescriptionHtml(): ?string public ?DateTime $updatedAt = null; /** - * @var array|null + * @var VariantCollection|null */ - private ?array $_variants = null; + private ?VariantCollection $_variants = null; /** * @var string @@ -393,37 +397,47 @@ public function getMetafields(): array } /** - * @param string|array $value + * @param string|array|Collection $value * @return void */ - public function setVariants(string|array $value): void + public function setVariants(string|array|Collection $value): void { if (is_string($value)) { $value = Json::decodeIfJson($value); } + if (is_iterable($value)) { + if (is_array($value)) { + $value = VariantCollection::make($value); + } else if ($value instanceof Collection && !($value instanceof VariantCollection)) { + $value = VariantCollection::make($value->all()); + } + } + $this->_variants = $value; } /** - * @return array + * @return VariantCollection * @throws InvalidConfigException */ - public function getVariants(): array + public function getVariants(): VariantCollection { if (!$this->shopifyGid) { - return []; + return VariantCollection::make(); } - if ($this->_variants !== null) { + if ($this->_variants instanceof VariantCollection){ return $this->_variants; + } else if ($this->_variants === null) { + $variants = Plugin::getInstance()->getApi()->getShopifyDataByType('ProductVariant', $this->shopifyGid, true); + } else { + $variants = $this->_variants; } - $variants = Plugin::getInstance()->getApi()->getShopifyDataByType('ProductVariant', $this->shopifyGid); + $this->setVariants($variants); - $this->setVariants($variants->all()); - - return $this->_variants ?? []; + return $this->_variants ?? VariantCollection::make(); } /** @@ -443,21 +457,24 @@ public function attributes(): array /** * Gets the cheapest variant. * - * @return array + * @return Variant|null + * @throws InvalidConfigException */ - public function getCheapestVariant(): array + public function getCheapestVariant(): ?Variant { - return collect($this->getVariants())->sortBy('price')->first() ?? []; + return $this->getVariants()->cheapest(); } /** * Gets the first variant which is Shopify's default variant. * - * @return array + * @return Variant|null */ - public function getDefaultVariant(): array + public function getDefaultVariant(): ?Variant { - return collect($this->getVariants())->first() ?? []; + /** @var Variant|null $variant */ + $variant = $this->getVariants()->first(); + return $variant ?? null; } /** @@ -736,6 +753,19 @@ public static function defineSources(string $context): array ]; } + /** + * @inerhitdoc + */ + public function beforeSave(bool $isNew): bool + { + // Ensure slug and handle match + if ($this->handle !== $this->slug) { + $this->slug = $this->handle; + } + + return parent::beforeSave($isNew); + } + /** * @param bool $isNew * @return void diff --git a/src/models/Variant.php b/src/models/Variant.php new file mode 100644 index 0000000..2fe636e --- /dev/null +++ b/src/models/Variant.php @@ -0,0 +1,185 @@ + + * @since 7.0.0 + */ +class Variant extends Model +{ + /** + * @var int|null + */ + public ?int $id = null; + + /** + * @var string|null The Shopify ID of the variant. + */ + public ?string $shopifyId = null; + + /** + * @var string|null + */ + public ?string $type = null; + + /** + * @var string|null + */ + public ?string $parentId = null; + + /** + * @var array|null + * @see getData() + * @see setData() + */ + private ?array $_data = null; + + /** + * @var array|null + * @see getMetafields() + * @see setMetafields() + */ + private ?array $_metaFields = null; + + /** + * @var DateTime|null + */ + public ?DateTime $dateCreated = null; + + /** + * @var DateTime|null + */ + public ?DateTime $dateUpdated = null; + + /** + * @var string|null + */ + public ?string $uid = null; + + + public function __call($name, $params) + { + if (array_key_exists($name, $this->_data)) { + return $this->_data[$name]; + } + + return parent::__call($name, $params); + } + + /** + * @inheritdoc + */ + public function __get($name) + { + if (array_key_exists($name, $this->_data)) { + return $this->_data[$name]; + } + + return parent::__get($name); + } + + /** + * @inheritdoc + */ + protected function defineRules(): array + { + $rules = parent::defineRules(); + + $rules[] = [['id', 'shopifyId', 'type', 'parentId', 'data', 'dateCreated', 'dateUpdated', 'uid'], 'safe']; + + return $rules; + } + + /** + * @inheritdoc + */ + public function attributes() + { + $names = parent::attributes(); + $names[] = 'data'; + $names[] = 'metafields'; + + return $names; + } + + /** + * @param string|array|null $data + * @return void + */ + public function setData(string|array|null $data): void + { + if (is_string($data)) { + $data = Json::decodeIfJson($data); + } + + $this->_data = $data; + } + + /** + * @return array + */ + public function getData(): array + { + return $this->_data; + } + + /** + * @param string|array $value + * @return void + */ + public function setMetafields(string|array $value): void + { + if (is_string($value)) { + $value = Json::decodeIfJson($value); + $value = collect($value)->mapWithKeys(function($d) { + return [ + $d['key'] => Json::decodeIfJson($d['value']), + ]; + }); + } + + $this->_metaFields = $value; + } + + /** + * @return array + * @throws InvalidConfigException + */ + public function getMetafields(): array + { + if (!$this->shopifyId) { + return []; + } + + if ($this->_metaFields !== null) { + return $this->_metaFields; + } + + $data = Plugin::getInstance()->getApi()->getShopifyDataByType('Metafield', $this->shopifyId); + + $metafields = $data + ->mapWithKeys(function($d) { + return [ + $d['key'] => Json::decodeIfJson($d['value']), + ]; + }); + + $this->setMetafields($metafields->all()); + + return $this->_metaFields ?? []; + } +} diff --git a/src/records/ShopifyData.php b/src/records/ShopifyData.php index b725939..c4aa2d0 100644 --- a/src/records/ShopifyData.php +++ b/src/records/ShopifyData.php @@ -23,6 +23,7 @@ * @property string $parentId * @property string $dateCreated * @property string $dateUpdated + * @property string $uid */ class ShopifyData extends ActiveRecord { diff --git a/src/services/Products.php b/src/services/Products.php index 0455c61..8b68865 100644 --- a/src/services/Products.php +++ b/src/services/Products.php @@ -12,9 +12,11 @@ use craft\helpers\ProjectConfig; use craft\helpers\StringHelper; use craft\models\FieldLayout; +use craft\shopify\collections\VariantCollection; use craft\shopify\db\Table; use craft\shopify\elements\Product; use craft\shopify\events\ShopifyProductSyncEvent; +use craft\shopify\models\Variant; use craft\shopify\Plugin; use craft\shopify\records\ShopifyData; use GraphQL\QueryBuilder\QueryBuilder; @@ -289,15 +291,51 @@ public function eagerLoadImagesForProducts(array $products): array */ public function eagerLoadVariantsForProducts(array $products): array { - return $this->_eagerLoadTypeOnProducts($products, 'ProductVariant', function($product, $rows) { - $product->setVariants(array_column($rows, 'data')); + $variantIds = []; + $variantsByProductId = []; + $return = $this->_eagerLoadTypeOnProducts($products, 'ProductVariant', function($product, $rows) use (&$variantsByProductId, &$variantIds) { + foreach ($rows as $row) { + $variantIds[] = $row->shopifyId; + } + + $variantsByProductId[$product->shopifyGid] = $rows; }); + + // If we are eager loading the variants, for best performance we should also eager load the metafields on the variants + $metafieldsData = collect(); + if (!empty($variantIds)) { + $metafieldsData = Plugin::getInstance() + ->getApi() + ->getShopifyDataByType('Metafield', $variantIds, true) + ->groupBy('parentId'); + } + + foreach ($return as $product) { + $variants = VariantCollection::make($variantsByProductId[$product->shopifyGid]); + + if ($metafieldsData->isNotEmpty()) { + $variants?->map(function(Variant$variant) use ($metafieldsData) { + $metafields = $metafieldsData->get($variant->shopifyId); + if (!empty($metafields)) { + $variant->setMetafields(collect($metafields)->mapWithKeys(function($d) { + return [ + $d->data['key'] => Json::decodeIfJson($d->data['value']), + ]; + })->all()); + } + }); + } + + $product->setMetafields($variants); + } + + return $return; } /** * @param array|Product[] $products * @param string $type - * @param callable $callback + * @param callable(Product, ShopifyData[]): void $callback * @return array * @throws InvalidConfigException */ From 2f32c8f0370882da3bb95eb3aafda4c1da5af670 Mon Sep 17 00:00:00 2001 From: Nathaniel Hammond Date: Wed, 14 Jan 2026 17:36:11 +0000 Subject: [PATCH 08/10] cleanup --- src/collections/VariantCollection.php | 4 +-- src/controllers/ProductsController.php | 1 - src/elements/Product.php | 13 +++++----- src/fieldlayoutelements/MediaField.php | 2 -- src/fieldlayoutelements/MetafieldsField.php | 1 - src/fieldlayoutelements/OptionsField.php | 2 +- src/fieldlayoutelements/VariantsField.php | 27 ++++++++++++++------- src/helpers/Product.php | 1 - src/models/Variant.php | 4 +++ src/records/ShopifyData.php | 2 +- src/services/Products.php | 9 ++++++- 11 files changed, 40 insertions(+), 26 deletions(-) diff --git a/src/collections/VariantCollection.php b/src/collections/VariantCollection.php index 27b63c3..a489f64 100644 --- a/src/collections/VariantCollection.php +++ b/src/collections/VariantCollection.php @@ -35,10 +35,10 @@ public static function make($items = []) foreach ($items as &$item) { if ($item instanceof Variant) { continue; - } else if (is_array($item)) { + } elseif (is_array($item)) { $item += ['class' => Variant::class]; $item = \Craft::createObject($item); - } else if ($item instanceof ShopifyData) { + } elseif ($item instanceof ShopifyData) { $item = Craft::createObject([ 'class' => Variant::class, 'id' => $item->id, diff --git a/src/controllers/ProductsController.php b/src/controllers/ProductsController.php index cfa53e7..d6c220c 100644 --- a/src/controllers/ProductsController.php +++ b/src/controllers/ProductsController.php @@ -11,7 +11,6 @@ use craft\helpers\App; use craft\helpers\UrlHelper; use craft\shopify\elements\Product; -use craft\shopify\helpers\Product as ProductHelper; use craft\shopify\Plugin; use yii\web\Response; diff --git a/src/elements/Product.php b/src/elements/Product.php index f7fc6db..5205560 100644 --- a/src/elements/Product.php +++ b/src/elements/Product.php @@ -28,7 +28,6 @@ use craft\shopify\models\Variant; use craft\shopify\Plugin; use craft\shopify\records\Product as ProductRecord; -use craft\shopify\records\ShopifyData; use craft\shopify\web\assets\shopifycp\ShopifyCpAsset; use craft\web\CpScreenResponseBehavior; use DateTime; @@ -409,7 +408,7 @@ public function setVariants(string|array|Collection $value): void if (is_iterable($value)) { if (is_array($value)) { $value = VariantCollection::make($value); - } else if ($value instanceof Collection && !($value instanceof VariantCollection)) { + } elseif ($value instanceof Collection && !($value instanceof VariantCollection)) { $value = VariantCollection::make($value->all()); } } @@ -427,9 +426,9 @@ public function getVariants(): VariantCollection return VariantCollection::make(); } - if ($this->_variants instanceof VariantCollection){ + if ($this->_variants instanceof VariantCollection) { return $this->_variants; - } else if ($this->_variants === null) { + } elseif ($this->_variants === null) { $variants = Plugin::getInstance()->getApi()->getShopifyDataByType('ProductVariant', $this->shopifyGid, true); } else { $variants = $this->_variants; @@ -719,14 +718,14 @@ public function getSidebarHtml(bool $static): string // Conditionally show metadata in the sidebar dependent on the field layout $excludeKeys = []; - $this->getFieldLayout()->getFields(Function ($field) use (&$excludeKeys) { + $this->getFieldLayout()->getFields(function($field) use (&$excludeKeys) { if ($field instanceof VariantsField) { $excludeKeys[] = 'Variants'; return true; - } else if ($field instanceof OptionsField) { + } elseif ($field instanceof OptionsField) { $excludeKeys[] = 'Options'; return true; - } else if ($field instanceof MetafieldsField) { + } elseif ($field instanceof MetafieldsField) { $excludeKeys[] = 'Metafields'; return true; } diff --git a/src/fieldlayoutelements/MediaField.php b/src/fieldlayoutelements/MediaField.php index 87473b7..fa6e6df 100644 --- a/src/fieldlayoutelements/MediaField.php +++ b/src/fieldlayoutelements/MediaField.php @@ -9,9 +9,7 @@ use Craft; use craft\base\ElementInterface; -use craft\enums\Color; use craft\fieldlayoutelements\BaseNativeField; -use craft\helpers\Cp; use craft\helpers\Html; use craft\shopify\elements\Product; use yii\base\InvalidArgumentException; diff --git a/src/fieldlayoutelements/MetafieldsField.php b/src/fieldlayoutelements/MetafieldsField.php index ec9d5c2..5ffe7f3 100644 --- a/src/fieldlayoutelements/MetafieldsField.php +++ b/src/fieldlayoutelements/MetafieldsField.php @@ -9,7 +9,6 @@ use Craft; use craft\base\ElementInterface; -use craft\enums\Color; use craft\fieldlayoutelements\BaseNativeField; use craft\helpers\Cp; use craft\helpers\Html; diff --git a/src/fieldlayoutelements/OptionsField.php b/src/fieldlayoutelements/OptionsField.php index 0835f58..3c43f14 100644 --- a/src/fieldlayoutelements/OptionsField.php +++ b/src/fieldlayoutelements/OptionsField.php @@ -70,7 +70,7 @@ protected function inputHtml(ElementInterface $element = null, bool $static = fa $tableData = []; foreach ($options as $opt) { - foreach ($opt['optionValues'] as $i => $val) { + foreach ($opt['optionValues'] as $i => $val) { $tableData[] = [ 'option' => $i === 0 ? Html::tag('strong', Html::encode($opt['name'])) : '', 'values' => Html::encode($val['name']), diff --git a/src/fieldlayoutelements/VariantsField.php b/src/fieldlayoutelements/VariantsField.php index b5c7962..1e32b2f 100644 --- a/src/fieldlayoutelements/VariantsField.php +++ b/src/fieldlayoutelements/VariantsField.php @@ -54,6 +54,7 @@ protected function inputHtml(ElementInterface $element = null, bool $static = fa } $variants = $element->getVariants(); + $variantRows = []; $cols = [ 'title' => ['heading' => Craft::t('shopify', 'Variant'), 'type' => 'html'], @@ -61,17 +62,25 @@ protected function inputHtml(ElementInterface $element = null, bool $static = fa 'price' => ['heading' => Craft::t('shopify', 'Price'), 'type' => 'html'], ]; - foreach ($variants as &$variant) { + foreach ($variants as $variant) { $link = sprintf('%s/variants/%s', $element->getShopifyEditUrl(), str_replace('gid://shopify/ProductVariant/', '', $variant['id'])); - $variant['title'] = Html::a(Html::encode($variant['title']), $link, [ - 'aria-label' => Craft::t('shopify', 'Edit variant {title} on Shopify', ['title' => $variant['title']]), - 'target' => '_blank', - 'class' => '' - ]); - $variant['sku'] = Html::tag('code', $variant['sku']); + + $title = $variant->title; + $sku = $variant->sku; + $price = $variant->price; + + $variantRows[] = [ + 'title' => Html::a(Html::encode($title), $link, [ + 'aria-label' => Craft::t('shopify', 'Edit variant {title} on Shopify', ['title' => $title]), + 'target' => '_blank', + 'class' => '', + ]), + 'sku' => Html::tag('code', $sku), + 'price' => $price, + ]; } - if (empty($variants)) { + if (empty($variantRows)) { return Html::beginTag('div', ['class' => 'zilch']) . Html::tag('p', Craft::t('shopify', 'This product has no variants.')) . Html::endTag('div'); @@ -81,7 +90,7 @@ protected function inputHtml(ElementInterface $element = null, bool $static = fa 'id' => $this->id(), 'name' => $this->baseInputName(), 'cols' => $cols, - 'rows' => $variants, + 'rows' => $variantRows, 'static' => true, ]); } diff --git a/src/helpers/Product.php b/src/helpers/Product.php index fb37912..abd0ba5 100644 --- a/src/helpers/Product.php +++ b/src/helpers/Product.php @@ -13,7 +13,6 @@ use craft\helpers\DateTimeHelper; use craft\helpers\Html; use craft\helpers\StringHelper; -use craft\helpers\UrlHelper; use craft\i18n\Formatter; use craft\shopify\elements\Product as ProductElement; use craft\shopify\records\ShopifyData; diff --git a/src/models/Variant.php b/src/models/Variant.php index 2fe636e..b70af2e 100644 --- a/src/models/Variant.php +++ b/src/models/Variant.php @@ -7,6 +7,7 @@ namespace craft\shopify\models; +use AllowDynamicProperties; use craft\base\Model; use craft\helpers\Json; use craft\shopify\Plugin; @@ -16,6 +17,9 @@ /** * Variant model. * + * @property-read string $title + * @property-read string $sku + * @property-read string $price * @author Pixel & Tonic, Inc. * @since 7.0.0 */ diff --git a/src/records/ShopifyData.php b/src/records/ShopifyData.php index c4aa2d0..59625b0 100644 --- a/src/records/ShopifyData.php +++ b/src/records/ShopifyData.php @@ -19,7 +19,7 @@ * @property int $id * @property string $shopifyId * @property string $type - * @property string $data + * @property string|array $data * @property string $parentId * @property string $dateCreated * @property string $dateUpdated diff --git a/src/services/Products.php b/src/services/Products.php index 8b68865..da5a5bb 100644 --- a/src/services/Products.php +++ b/src/services/Products.php @@ -262,6 +262,13 @@ public function eagerLoadMetafieldsForProducts(array $products): array return $this->_eagerLoadTypeOnProducts($products, 'Metafield', function($product, $rows) { $metafields = collect($rows) ->mapWithKeys(function($d, $key) { + /** @var ShopifyData $d */ + + // Map if the data has `key` and `value` properties + if (!isset($d->data['key']) || !isset($d->data['value'])) { + return []; + } + return [ $d->data['key'] => Json::decodeIfJson($d->data['value']), ]; @@ -314,7 +321,7 @@ public function eagerLoadVariantsForProducts(array $products): array $variants = VariantCollection::make($variantsByProductId[$product->shopifyGid]); if ($metafieldsData->isNotEmpty()) { - $variants?->map(function(Variant$variant) use ($metafieldsData) { + $variants->map(function(Variant$variant) use ($metafieldsData) { $metafields = $metafieldsData->get($variant->shopifyId); if (!empty($metafields)) { $variant->setMetafields(collect($metafields)->mapWithKeys(function($d) { From 0832474e6e6c35771d8ddad9e0b3ae1002eabbc4 Mon Sep 17 00:00:00 2001 From: Nathaniel Hammond Date: Wed, 14 Jan 2026 17:36:27 +0000 Subject: [PATCH 09/10] tidy --- src/models/Variant.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/models/Variant.php b/src/models/Variant.php index b70af2e..5cafc97 100644 --- a/src/models/Variant.php +++ b/src/models/Variant.php @@ -7,7 +7,6 @@ namespace craft\shopify\models; -use AllowDynamicProperties; use craft\base\Model; use craft\helpers\Json; use craft\shopify\Plugin; From 474018207246ebf323362513eb562663c2dbbf8c Mon Sep 17 00:00:00 2001 From: Nathaniel Hammond Date: Wed, 14 Jan 2026 17:49:59 +0000 Subject: [PATCH 10/10] Fix variants link --- src/fieldlayoutelements/VariantsField.php | 2 +- src/models/Variant.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/fieldlayoutelements/VariantsField.php b/src/fieldlayoutelements/VariantsField.php index 1e32b2f..46511a2 100644 --- a/src/fieldlayoutelements/VariantsField.php +++ b/src/fieldlayoutelements/VariantsField.php @@ -63,7 +63,7 @@ protected function inputHtml(ElementInterface $element = null, bool $static = fa ]; foreach ($variants as $variant) { - $link = sprintf('%s/variants/%s', $element->getShopifyEditUrl(), str_replace('gid://shopify/ProductVariant/', '', $variant['id'])); + $link = sprintf('%s/variants/%s', $element->getShopifyEditUrl(), str_replace('gid://shopify/ProductVariant/', '', $variant->shopifyId)); $title = $variant->title; $sku = $variant->sku; diff --git a/src/models/Variant.php b/src/models/Variant.php index 5cafc97..688e499 100644 --- a/src/models/Variant.php +++ b/src/models/Variant.php @@ -16,6 +16,7 @@ /** * Variant model. * + * @property-read string $shopifyId * @property-read string $title * @property-read string $sku * @property-read string $price