Skip to content

Commit 633cb07

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

2 files changed

Lines changed: 164 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: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616
use Ymir\Plugin\CloudStorage\PrivateCloudStorageStreamWrapper;
1717
use Ymir\Plugin\CloudStorage\PublicCloudStorageStreamWrapper;
1818
use Ymir\Plugin\Subscriber\Compatibility\WooCommerceSubscriber;
19+
use Ymir\Plugin\Support\WordPress;
1920
use Ymir\Plugin\Tests\Mock\ContentDeliveryNetworkPageCacheClientInterfaceMockTrait;
2021
use Ymir\Plugin\Tests\Mock\EventManagerMockTrait;
2122
use Ymir\Plugin\Tests\Mock\FunctionMockTrait;
23+
use Ymir\Plugin\Tests\Mock\WPPostMockTrait;
2224
use Ymir\Plugin\Tests\Mock\WPTermMockTrait;
2325
use Ymir\Plugin\Tests\Unit\TestCase;
2426

@@ -27,6 +29,7 @@ class WooCommerceSubscriberTest extends TestCase
2729
use ContentDeliveryNetworkPageCacheClientInterfaceMockTrait;
2830
use EventManagerMockTrait;
2931
use FunctionMockTrait;
32+
use WPPostMockTrait;
3033
use WPTermMockTrait;
3134

3235
public function testChangeLogDirectoryWhenLogDirectoryIsAStringThatDoesntStartWithThePublicCloudStorageProtocol()
@@ -48,6 +51,63 @@ public function testChangeLogDirectoryWhenLogDirectoryIsntAString()
4851
$this->assertSame($logDirectory, $this->createSubscriber()->changeLogDirectory($logDirectory));
4952
}
5053

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

183+
public function testClearCacheOnProductVariationSave()
184+
{
185+
$function_exists = $this->getFunctionMock($this->getNamespace(WordPress::class), 'function_exists');
186+
$function_exists->expects($this->exactly(2))
187+
->withConsecutive(
188+
[$this->identicalTo('wp_is_post_autosave')],
189+
[$this->identicalTo('wp_is_post_revision')]
190+
)
191+
->willReturn(true);
192+
193+
$wp_is_post_autosave = $this->getFunctionMock($this->getNamespace(WordPress::class), 'wp_is_post_autosave');
194+
$wp_is_post_autosave->expects($this->once())
195+
->with(456)
196+
->willReturn(false);
197+
198+
$wp_is_post_revision = $this->getFunctionMock($this->getNamespace(WordPress::class), 'wp_is_post_revision');
199+
$wp_is_post_revision->expects($this->once())
200+
->with(456)
201+
->willReturn(false);
202+
203+
$wp_get_post_parent_id = $this->getFunctionMock($this->getNamespace(WooCommerceSubscriber::class), 'wp_get_post_parent_id');
204+
$wp_get_post_parent_id->expects($this->once())
205+
->with(456)
206+
->willReturn(123);
207+
208+
$function_exists = $this->getFunctionMock($this->getNamespace(WooCommerceSubscriber::class), 'function_exists');
209+
$function_exists->expects($this->once())
210+
->with('wc_get_page_permalink')
211+
->willReturn(true);
212+
213+
$get_permalink = $this->getFunctionMock($this->getNamespace(WooCommerceSubscriber::class), 'get_permalink');
214+
$get_permalink->expects($this->once())
215+
->with(123)
216+
->willReturn('https://foo.com/product/bar/');
217+
218+
$wc_get_page_permalink = $this->getFunctionMock($this->getNamespace(WooCommerceSubscriber::class), 'wc_get_page_permalink');
219+
$wc_get_page_permalink->expects($this->once())
220+
->with('shop')
221+
->willReturn('https://foo.com/shop/');
222+
223+
$get_the_terms = $this->getFunctionMock($this->getNamespace(WooCommerceSubscriber::class), 'get_the_terms');
224+
$get_the_terms->expects($this->exactly(2))
225+
->willReturn([]);
226+
227+
$pageCacheClient = $this->getContentDeliveryNetworkPageCacheClientInterfaceMock();
228+
$pageCacheClient->expects($this->once())
229+
->method('clearUrls')
230+
->with($this->callback(function ($urls) {
231+
$this->assertSame([
232+
'https://foo.com/product/bar/',
233+
'https://foo.com/shop/*',
234+
], $urls->all());
235+
236+
return true;
237+
}));
238+
239+
$post = $this->getWPPostMock();
240+
241+
$this->createSubscriber('https://foo.com', '', false, ['invalidation_enabled' => true], $pageCacheClient)
242+
->clearCacheOnProductVariationSave(456, $post, true);
243+
}
244+
123245
public function testClearCacheOnProductVariationUpdate()
124246
{
125247
$function_exists = $this->getFunctionMock($this->getNamespace(WooCommerceSubscriber::class), 'function_exists');
@@ -312,6 +434,8 @@ public function testGetSubscribedEvents()
312434
$subscribedEvents = [
313435
'transient_woocommerce_blocks_asset_api_script_data' => 'fixAssetUrlPathsInCachedScriptData',
314436
'transient_woocommerce_blocks_asset_api_script_data_ssl' => 'fixAssetUrlPathsInCachedScriptData',
437+
'save_post_product' => ['clearCacheOnProductSave', 20, 3],
438+
'save_post_product_variation' => ['clearCacheOnProductVariationSave', 20, 3],
315439
'woocommerce_csv_importer_check_import_file_path' => 'disableCheckImportFilePath',
316440
'woocommerce_log_directory' => 'changeLogDirectory',
317441
'woocommerce_product_csv_importer_check_import_file_path' => 'disableCheckImportFilePath',

0 commit comments

Comments
 (0)