Skip to content

Commit 1d0af6a

Browse files
committed
fix: add save_post fallbacks for woocommerce product cache invalidation
1 parent 1af9b7c commit 1d0af6a

2 files changed

Lines changed: 147 additions & 0 deletions

File tree

src/Subscriber/Compatibility/WooCommerceSubscriber.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ public static function getSubscribedEvents(): array
7979
return [
8080
'transient_woocommerce_blocks_asset_api_script_data' => 'fixAssetUrlPathsInCachedScriptData',
8181
'transient_woocommerce_blocks_asset_api_script_data_ssl' => 'fixAssetUrlPathsInCachedScriptData',
82+
'save_post_product' => ['clearCacheOnProductSave', 20, 3],
83+
'save_post_product_variation' => ['clearCacheOnProductVariationSave', 20, 3],
8284
'woocommerce_csv_importer_check_import_file_path' => 'disableCheckImportFilePath',
8385
'woocommerce_log_directory' => 'changeLogDirectory',
8486
'woocommerce_product_csv_importer_check_import_file_path' => 'disableCheckImportFilePath',
@@ -98,6 +100,18 @@ public function changeLogDirectory($logDirectory)
98100
: $logDirectory;
99101
}
100102

103+
/**
104+
* Clear product-related URLs when a product post is saved directly.
105+
*/
106+
public function clearCacheOnProductSave(int $postId, \WP_Post $post, bool $update): void
107+
{
108+
if (!$update || WordPress::isAutosaveOrRevision($postId)) {
109+
return;
110+
}
111+
112+
$this->clearProductUrls($postId);
113+
}
114+
101115
/**
102116
* Clear all the related product URLs when a product is updated.
103117
*/
@@ -106,6 +120,24 @@ public function clearCacheOnProductUpdate($productId)
106120
$this->clearProductUrls($productId);
107121
}
108122

123+
/**
124+
* Clear product-related URLs when a product variation post is saved directly.
125+
*/
126+
public function clearCacheOnProductVariationSave(int $postId, \WP_Post $post, bool $update): void
127+
{
128+
if (!$update || WordPress::isAutosaveOrRevision($postId)) {
129+
return;
130+
}
131+
132+
$parentId = (int) wp_get_post_parent_id($postId);
133+
134+
if ($parentId <= 0) {
135+
return;
136+
}
137+
138+
$this->clearProductUrls($parentId);
139+
}
140+
109141
/**
110142
* Clear all the related product URLs when a product variation is updated.
111143
*/
@@ -215,4 +247,12 @@ private function clearProductUrls($productId)
215247

216248
$this->pageCacheClient->clearUrls($urlsToClear);
217249
}
250+
251+
/**
252+
* Check if a post save is an autosave or revision.
253+
*/
254+
private function isAutosaveOrRevision(int $postId): bool
255+
{
256+
return (bool) wp_is_post_autosave($postId) || (bool) wp_is_post_revision($postId);
257+
}
218258
}

tests/Unit/Subscriber/Compatibility/WooCommerceSubscriberTest.php

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use Ymir\Plugin\Tests\Mock\ContentDeliveryNetworkPageCacheClientInterfaceMockTrait;
2020
use Ymir\Plugin\Tests\Mock\EventManagerMockTrait;
2121
use Ymir\Plugin\Tests\Mock\FunctionMockTrait;
22+
use Ymir\Plugin\Tests\Mock\WPPostMockTrait;
2223
use Ymir\Plugin\Tests\Mock\WPTermMockTrait;
2324
use Ymir\Plugin\Tests\Unit\TestCase;
2425

@@ -27,6 +28,7 @@ class WooCommerceSubscriberTest extends TestCase
2728
use ContentDeliveryNetworkPageCacheClientInterfaceMockTrait;
2829
use EventManagerMockTrait;
2930
use FunctionMockTrait;
31+
use WPPostMockTrait;
3032
use WPTermMockTrait;
3133

3234
public function testChangeLogDirectoryWhenLogDirectoryIsAStringThatDoesntStartWithThePublicCloudStorageProtocol()
@@ -48,6 +50,55 @@ public function testChangeLogDirectoryWhenLogDirectoryIsntAString()
4850
$this->assertSame($logDirectory, $this->createSubscriber()->changeLogDirectory($logDirectory));
4951
}
5052

53+
public function testClearCacheOnProductSave()
54+
{
55+
$wp_is_post_autosave = $this->getFunctionMock($this->getNamespace(WooCommerceSubscriber::class), 'wp_is_post_autosave');
56+
$wp_is_post_autosave->expects($this->once())
57+
->with(123)
58+
->willReturn(false);
59+
60+
$wp_is_post_revision = $this->getFunctionMock($this->getNamespace(WooCommerceSubscriber::class), 'wp_is_post_revision');
61+
$wp_is_post_revision->expects($this->once())
62+
->with(123)
63+
->willReturn(false);
64+
65+
$function_exists = $this->getFunctionMock($this->getNamespace(WooCommerceSubscriber::class), 'function_exists');
66+
$function_exists->expects($this->once())
67+
->with('wc_get_page_permalink')
68+
->willReturn(true);
69+
70+
$get_permalink = $this->getFunctionMock($this->getNamespace(WooCommerceSubscriber::class), 'get_permalink');
71+
$get_permalink->expects($this->once())
72+
->with(123)
73+
->willReturn('https://foo.com/product/bar/');
74+
75+
$wc_get_page_permalink = $this->getFunctionMock($this->getNamespace(WooCommerceSubscriber::class), 'wc_get_page_permalink');
76+
$wc_get_page_permalink->expects($this->once())
77+
->with('shop')
78+
->willReturn('https://foo.com/shop/');
79+
80+
$get_the_terms = $this->getFunctionMock($this->getNamespace(WooCommerceSubscriber::class), 'get_the_terms');
81+
$get_the_terms->expects($this->exactly(2))
82+
->willReturn([]);
83+
84+
$pageCacheClient = $this->getContentDeliveryNetworkPageCacheClientInterfaceMock();
85+
$pageCacheClient->expects($this->once())
86+
->method('clearUrls')
87+
->with($this->callback(function ($urls) {
88+
$this->assertSame([
89+
'https://foo.com/product/bar/',
90+
'https://foo.com/shop/*',
91+
], $urls->all());
92+
93+
return true;
94+
}));
95+
96+
$post = $this->getWPPostMock();
97+
98+
$this->createSubscriber('https://foo.com', '', false, ['invalidation_enabled' => true], $pageCacheClient)
99+
->clearCacheOnProductSave(123, $post, true);
100+
}
101+
51102
public function testClearCacheOnProductUpdateWhenClearAllOnPostUpdateIsDisabled()
52103
{
53104
$function_exists = $this->getFunctionMock($this->getNamespace(WooCommerceSubscriber::class), 'function_exists');
@@ -120,6 +171,60 @@ public function testClearCacheOnProductUpdateWhenInvalidationIsDisabled()
120171
$this->createSubscriber('https://foo.com', '', false, ['invalidation_enabled' => false], $pageCacheClient)->clearCacheOnProductUpdate(123);
121172
}
122173

174+
public function testClearCacheOnProductVariationSave()
175+
{
176+
$wp_is_post_autosave = $this->getFunctionMock($this->getNamespace(WooCommerceSubscriber::class), 'wp_is_post_autosave');
177+
$wp_is_post_autosave->expects($this->once())
178+
->with(456)
179+
->willReturn(false);
180+
181+
$wp_is_post_revision = $this->getFunctionMock($this->getNamespace(WooCommerceSubscriber::class), 'wp_is_post_revision');
182+
$wp_is_post_revision->expects($this->once())
183+
->with(456)
184+
->willReturn(false);
185+
186+
$wp_get_post_parent_id = $this->getFunctionMock($this->getNamespace(WooCommerceSubscriber::class), 'wp_get_post_parent_id');
187+
$wp_get_post_parent_id->expects($this->once())
188+
->with(456)
189+
->willReturn(123);
190+
191+
$function_exists = $this->getFunctionMock($this->getNamespace(WooCommerceSubscriber::class), 'function_exists');
192+
$function_exists->expects($this->once())
193+
->with('wc_get_page_permalink')
194+
->willReturn(true);
195+
196+
$get_permalink = $this->getFunctionMock($this->getNamespace(WooCommerceSubscriber::class), 'get_permalink');
197+
$get_permalink->expects($this->once())
198+
->with(123)
199+
->willReturn('https://foo.com/product/bar/');
200+
201+
$wc_get_page_permalink = $this->getFunctionMock($this->getNamespace(WooCommerceSubscriber::class), 'wc_get_page_permalink');
202+
$wc_get_page_permalink->expects($this->once())
203+
->with('shop')
204+
->willReturn('https://foo.com/shop/');
205+
206+
$get_the_terms = $this->getFunctionMock($this->getNamespace(WooCommerceSubscriber::class), 'get_the_terms');
207+
$get_the_terms->expects($this->exactly(2))
208+
->willReturn([]);
209+
210+
$pageCacheClient = $this->getContentDeliveryNetworkPageCacheClientInterfaceMock();
211+
$pageCacheClient->expects($this->once())
212+
->method('clearUrls')
213+
->with($this->callback(function ($urls) {
214+
$this->assertSame([
215+
'https://foo.com/product/bar/',
216+
'https://foo.com/shop/*',
217+
], $urls->all());
218+
219+
return true;
220+
}));
221+
222+
$post = $this->getWPPostMock();
223+
224+
$this->createSubscriber('https://foo.com', '', false, ['invalidation_enabled' => true], $pageCacheClient)
225+
->clearCacheOnProductVariationSave(456, $post, true);
226+
}
227+
123228
public function testClearCacheOnProductVariationUpdate()
124229
{
125230
$function_exists = $this->getFunctionMock($this->getNamespace(WooCommerceSubscriber::class), 'function_exists');
@@ -312,6 +417,8 @@ public function testGetSubscribedEvents()
312417
$subscribedEvents = [
313418
'transient_woocommerce_blocks_asset_api_script_data' => 'fixAssetUrlPathsInCachedScriptData',
314419
'transient_woocommerce_blocks_asset_api_script_data_ssl' => 'fixAssetUrlPathsInCachedScriptData',
420+
'save_post_product' => ['clearCacheOnProductSave', 20, 3],
421+
'save_post_product_variation' => ['clearCacheOnProductVariationSave', 20, 3],
315422
'woocommerce_csv_importer_check_import_file_path' => 'disableCheckImportFilePath',
316423
'woocommerce_log_directory' => 'changeLogDirectory',
317424
'woocommerce_product_csv_importer_check_import_file_path' => 'disableCheckImportFilePath',

0 commit comments

Comments
 (0)