Skip to content

Commit 9dd9adf

Browse files
committed
feat: add page cache invalidation to woocommerce product updates
1 parent e992899 commit 9dd9adf

File tree

8 files changed

+402
-89
lines changed

8 files changed

+402
-89
lines changed

src/CloudProvider/Aws/CloudFrontClient.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,24 @@ public function clearUrl(string $url)
6969
$this->addPath('/'.ltrim((string) $path, '/'));
7070
}
7171

72+
/**
73+
* {@inheritdoc}
74+
*/
75+
public function clearUrls($urls)
76+
{
77+
if (is_array($urls) || is_string($urls)) {
78+
$urls = new Collection($urls);
79+
} elseif (!$urls instanceof Collection) {
80+
throw new \InvalidArgumentException('Urls must be an array, a collection or a string');
81+
}
82+
83+
$urls->filter(function ($url) {
84+
return is_string($url) && !empty($url);
85+
})->each(function (string $url) {
86+
$this->clearUrl($url);
87+
});
88+
}
89+
7290
/**
7391
* {@inheritdoc}
7492
*/

src/Configuration/EventManagementConfiguration.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public function modify(Container $container)
6161
new Subscriber\Compatibility\ActionSchedulerSubscriber(),
6262
new Subscriber\Compatibility\DiviSubscriber(),
6363
new Subscriber\Compatibility\LifterLmsSubscriber(),
64-
new Subscriber\Compatibility\WooCommerceSubscriber($container['site_url'], $container['assets_url'], $container['ymir_cdn_image_processing_enabled']),
64+
new Subscriber\Compatibility\WooCommerceSubscriber($container['cloudfront_client'], $container['site_url'], $container['assets_url'], $container['ymir_cdn_image_processing_enabled'], $container['page_caching_options']),
6565
new Subscriber\Compatibility\WpAllImportSubscriber(),
6666
new Subscriber\Compatibility\WpMigrateDbSubscriber(),
6767
];

src/PageCache/ContentDeliveryNetworkPageCacheClientInterface.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ public function clearAll();
2828
*/
2929
public function clearUrl(string $url);
3030

31+
/**
32+
* Clear the given URLs from the page cache.
33+
*/
34+
public function clearUrls($urls);
35+
3136
/**
3237
* Send request to content delivery network to clear all requested URLs from its cache.
3338
*/

src/Subscriber/Compatibility/WooCommerceSubscriber.php

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Ymir\Plugin\CloudStorage\PrivateCloudStorageStreamWrapper;
1717
use Ymir\Plugin\CloudStorage\PublicCloudStorageStreamWrapper;
1818
use Ymir\Plugin\EventManagement\SubscriberInterface;
19+
use Ymir\Plugin\PageCache\ContentDeliveryNetworkPageCacheClientInterface;
1920
use Ymir\Plugin\Support\Collection;
2021

2122
/**
@@ -37,6 +38,20 @@ class WooCommerceSubscriber implements SubscriberInterface
3738
*/
3839
private $isImageProcessingEnabled;
3940

41+
/**
42+
* Client interacting with the content delivery network handling page caching.
43+
*
44+
* @var ContentDeliveryNetworkPageCacheClientInterface
45+
*/
46+
private $pageCacheClient;
47+
48+
/**
49+
* The page caching options.
50+
*
51+
* @var array
52+
*/
53+
private $pageCachingOptions;
54+
4055
/**
4156
* WordPress site URL.
4257
*
@@ -47,10 +62,12 @@ class WooCommerceSubscriber implements SubscriberInterface
4762
/**
4863
* Constructor.
4964
*/
50-
public function __construct(string $siteUrl, string $assetsUrl = '', bool $isImageProcessingEnabled = false)
65+
public function __construct(ContentDeliveryNetworkPageCacheClientInterface $pageCacheClient, string $siteUrl, string $assetsUrl = '', bool $isImageProcessingEnabled = false, array $pageCachingOptions = [])
5166
{
5267
$this->assetsUrl = rtrim($assetsUrl, '/');
5368
$this->isImageProcessingEnabled = $isImageProcessingEnabled;
69+
$this->pageCacheClient = $pageCacheClient;
70+
$this->pageCachingOptions = $pageCachingOptions;
5471
$this->siteUrl = rtrim($siteUrl, '/');
5572
}
5673

@@ -66,6 +83,8 @@ public static function getSubscribedEvents(): array
6683
'woocommerce_log_directory' => 'changeLogDirectory',
6784
'woocommerce_product_csv_importer_check_import_file_path' => 'disableCheckImportFilePath',
6885
'woocommerce_resize_images' => 'disableImageResizeWithImageProcessing',
86+
'woocommerce_update_product' => 'clearCacheOnProductUpdate',
87+
'woocommerce_update_product_variation' => 'clearCacheOnProductVariationUpdate',
6988
];
7089
}
7190

@@ -79,6 +98,26 @@ public function changeLogDirectory($logDirectory)
7998
: $logDirectory;
8099
}
81100

101+
/**
102+
* Clear all the related product URLs when a product is updated.
103+
*/
104+
public function clearCacheOnProductUpdate($productId)
105+
{
106+
$this->clearProductUrls($productId);
107+
}
108+
109+
/**
110+
* Clear all the related product URLs when a product variation is updated.
111+
*/
112+
public function clearCacheOnProductVariationUpdate($variationId, $variation)
113+
{
114+
if (!is_object($variation) || !method_exists($variation, 'get_parent_id')) {
115+
return;
116+
}
117+
118+
$this->clearProductUrls($variation->get_parent_id());
119+
}
120+
82121
/**
83122
* Disable "check import file path" so that imports work with S3 storage.
84123
*/
@@ -120,4 +159,50 @@ public function fixAssetUrlPathsInCachedScriptData($value)
120159

121160
return wp_json_encode($cachedScriptData);
122161
}
162+
163+
/**
164+
* Clear all the URLs related to the given product from the page cache.
165+
*/
166+
private function clearProductUrls($productId)
167+
{
168+
if (empty($this->pageCachingOptions['invalidation_enabled'])) {
169+
return;
170+
} elseif (!empty($this->pageCachingOptions['clear_all_on_post_update'])) {
171+
$this->pageCacheClient->clearAll();
172+
173+
return;
174+
}
175+
176+
$permalink = get_permalink($productId);
177+
178+
if (!is_string($permalink)) {
179+
return;
180+
}
181+
182+
$urlsToClear = new Collection();
183+
184+
$urlsToClear[] = rtrim($permalink, '/').'/';
185+
186+
if (function_exists('wc_get_page_permalink')) {
187+
$urlsToClear[] = rtrim(wc_get_page_permalink('shop'), '/').'/*';
188+
}
189+
190+
// Product category URLs
191+
$productCategories = (new Collection(get_the_terms($productId, 'product_cat')))->filter(function ($category) {
192+
return $category instanceof \WP_Term;
193+
});
194+
$urlsToClear = $urlsToClear->merge($productCategories->map(function (\WP_Term $category) {
195+
return rtrim(get_term_link($category), '/').'/*';
196+
}));
197+
198+
// Product tag URLs
199+
$productTags = (new Collection(get_the_terms($productId, 'product_tag')))->filter(function ($category) {
200+
return $category instanceof \WP_Term;
201+
});
202+
$urlsToClear = $urlsToClear->merge($productTags->map(function (\WP_Term $tag) {
203+
return rtrim(get_term_link($tag), '/').'/*';
204+
}));
205+
206+
$this->pageCacheClient->clearUrls($urlsToClear);
207+
}
123208
}

src/Subscriber/ContentDeliveryNetworkPageCachingSubscriber.php

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,7 @@ public function clearPost($postId)
103103
return;
104104
}
105105

106-
$urlsToClear->filter(function ($url) {
107-
return is_string($url) && !empty($url);
108-
})->each(function (string $url) {
109-
$this->pageCacheClient->clearUrl($url);
110-
});
106+
$this->pageCacheClient->clearUrls($urlsToClear);
111107
}
112108

113109
/**

tests/Unit/CloudProvider/Aws/CloudFrontClientTest.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
namespace Ymir\Plugin\Tests\Unit\CloudProvider\Aws;
1515

1616
use Ymir\Plugin\CloudProvider\Aws\CloudFrontClient;
17+
use Ymir\Plugin\Support\Collection;
1718
use Ymir\Plugin\Tests\Mock\FunctionMockTrait;
1819
use Ymir\Plugin\Tests\Mock\HttpClientMockTrait;
1920
use Ymir\Plugin\Tests\Unit\TestCase;
@@ -94,6 +95,45 @@ public function testClearUrlAddsSlashToPath()
9495
$this->assertSame(['/'.$path], $invalidationPathsProperty->getValue($client));
9596
}
9697

98+
public function testClearUrlsWithArray()
99+
{
100+
$urls = [$this->faker->url, $this->faker->url];
101+
102+
$invalidationPathsProperty = new \ReflectionProperty(CloudFrontClient::class, 'invalidationPaths');
103+
$invalidationPathsProperty->setAccessible(true);
104+
105+
$client = new CloudFrontClient($this->getHttpClientMock(), 'distribution-id', 'aws-key', 'aws-secret');
106+
$client->clearUrls($urls);
107+
108+
$this->assertSame([parse_url($urls[0], PHP_URL_PATH), parse_url($urls[1], PHP_URL_PATH)], $invalidationPathsProperty->getValue($client));
109+
}
110+
111+
public function testClearUrlsWithCollection()
112+
{
113+
$urls = new Collection([$this->faker->url, $this->faker->url]);
114+
115+
$invalidationPathsProperty = new \ReflectionProperty(CloudFrontClient::class, 'invalidationPaths');
116+
$invalidationPathsProperty->setAccessible(true);
117+
118+
$client = new CloudFrontClient($this->getHttpClientMock(), 'distribution-id', 'aws-key', 'aws-secret');
119+
$client->clearUrls($urls);
120+
121+
$this->assertSame([parse_url($urls[0], PHP_URL_PATH), parse_url($urls[1], PHP_URL_PATH)], $invalidationPathsProperty->getValue($client));
122+
}
123+
124+
public function testClearUrlsWithString()
125+
{
126+
$url = $this->faker->url;
127+
128+
$invalidationPathsProperty = new \ReflectionProperty(CloudFrontClient::class, 'invalidationPaths');
129+
$invalidationPathsProperty->setAccessible(true);
130+
131+
$client = new CloudFrontClient($this->getHttpClientMock(), 'distribution-id', 'aws-key', 'aws-secret');
132+
$client->clearUrls($url);
133+
134+
$this->assertSame([parse_url($url, PHP_URL_PATH)], $invalidationPathsProperty->getValue($client));
135+
}
136+
97137
public function testClearUrlWithAPath()
98138
{
99139
$path = parse_url($this->faker->url, PHP_URL_PATH);

0 commit comments

Comments
 (0)