Skip to content
Draft

7.x #195

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
# Release Notes for Shopify

## 7.x - WIP

> [!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))
- 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.

## Unreleased

- Fixed a PHP error that could occur when contextual pricing countries aren’t set. ([#191](https://github.com/craftcms/shopify/issues/191))
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
368 changes: 188 additions & 180 deletions composer.lock

Large diffs are not rendered by default.

28 changes: 28 additions & 0 deletions src/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,26 @@
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;
use craft\fields\Link;
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;
use craft\services\Utilities;
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;
Expand Down Expand Up @@ -125,6 +131,7 @@ public function init()
$this->_registerElementTypes();
$this->_registerUtilityTypes();
$this->_registerFieldTypes();
$this->_registerFieldLayoutElements();
$this->_registerLinkTypes();
$this->_registerVariables();
$this->_registerResaveCommands();
Expand Down Expand Up @@ -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
*
Expand Down
73 changes: 73 additions & 0 deletions src/collections/VariantCollection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php
/**
* @link https://craftcms.com/
* @copyright Copyright (c) Pixel & Tonic, Inc.
* @license https://craftcms.github.io/license/
*/

namespace craft\shopify\collections;

use Craft;
use craft\shopify\models\Variant;
use craft\shopify\records\ShopifyData;
use Illuminate\Support\Collection;

/**
* VariantCollection represents a collection of Variants.
*
* @template TKey of array-key
* @template TValue of Variant
* @extends Collection<TKey,TValue>
*
* @author Pixel & Tonic, Inc. <support@pixelandtonic.com>
* @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;
} elseif (is_array($item)) {
$item += ['class' => Variant::class];
$item = \Craft::createObject($item);
} elseif ($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;
}
}
14 changes: 0 additions & 14 deletions src/controllers/ProductsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -53,17 +52,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);
}
}
58 changes: 56 additions & 2 deletions src/controllers/WebhooksController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(<<<JS
var columns = [
{ name: '__slot:title', title: Craft.t('shopify', 'Topic') },
{ name: 'callbackUrl', title: Craft.t('shopify', 'URL') }
];

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,
deleteCallback: function(){
window.location.reload(); // We need to reload to get the create button showing again
}
});
JS);

$screen = $this->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;
}

/**
Expand Down
Loading
Loading