Skip to content

Commit 94d69f1

Browse files
committed
feat: use the_content filter to replace broken urls in post content
1 parent d779759 commit 94d69f1

File tree

9 files changed

+173
-14
lines changed

9 files changed

+173
-14
lines changed

src/Subscriber/AssetsSubscriber.php

Lines changed: 101 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
namespace Ymir\Plugin\Subscriber;
1515

1616
use Ymir\Plugin\EventManagement\SubscriberInterface;
17+
use Ymir\Plugin\Support\Collection;
1718

1819
/**
1920
* Subscriber for managing the integration between WordPress and the deployed assets.
@@ -77,6 +78,7 @@ public static function getSubscribedEvents(): array
7778
'plugins_url' => 'rewritePluginsUrl',
7879
'script_loader_src' => 'replaceSiteUrlWithAssetsUrl',
7980
'style_loader_src' => 'replaceSiteUrlWithAssetsUrl',
81+
'the_content' => ['replaceUrlsInContent', 99999], // Make the priority high, but less than 999999 which is the Jetpack Photon hook priority
8082
'wp_resource_hints' => ['addAssetsUrlToDnsPrefetch', 10, 2],
8183
];
8284
}
@@ -116,42 +118,127 @@ public function replaceSiteUrlWithAssetsUrl(string $url): string
116118
}
117119

118120
/**
119-
* Rewrite the wp-content URL so it points to the assets URL.
121+
* Replace broken URLs in the post content so they correctly point to the current assets URL.
120122
*/
121-
public function rewriteContentUrl(string $url): string
123+
public function replaceUrlsInContent(string $content): string
122124
{
123-
$matches = [];
124-
preg_match(sprintf('/https?:\/\/.*(%s.*)/', preg_quote($this->contentDirectory, '/')), $url, $matches);
125+
if (empty($this->assetsUrl)) {
126+
return $content;
127+
}
125128

126-
if (empty($matches[1])) {
127-
return $url;
129+
// The assumption is that all URLs are surrounded by either quotes or double quotes.
130+
$patterns = [
131+
'%"(?P<url>https?://[^"]*?)"%is',
132+
"%'(?P<url>https?://[^']*?)'%is",
133+
];
134+
$urls = new Collection();
135+
136+
foreach ($patterns as $pattern) {
137+
$matches = [];
138+
139+
preg_match_all($pattern, $content, $matches);
140+
141+
$urls = $urls->merge($matches['url'] ?? []);
142+
}
143+
144+
if ($urls->isEmpty()) {
145+
return $content;
128146
}
129147

130-
return $this->assetsUrl.$matches[1];
148+
$assetsHost = parse_url($this->assetsUrl, PHP_URL_HOST);
149+
$siteHost = parse_url($this->siteUrl, PHP_URL_HOST);
150+
$uploadsDirectory = $this->contentDirectory.'/uploads';
151+
$urls = $urls->unique();
152+
153+
// If we have a hardcoded URL to an asset, we want to dynamically update it to the
154+
// current asset URL.
155+
$assetsUrls = $urls->filter(function (string $url) use ($assetsHost) {
156+
return parse_url($url, PHP_URL_HOST) === $assetsHost;
157+
})->mapWithKeys(function (string $url) {
158+
return [$url => $this->rewriteAssetsUrl('%https?://[^/]*/assets/[^/]*(.*)%i', $url)];
159+
})->all();
160+
161+
// Get all the URLs pointing to the "/wp-content" directory
162+
$contentUrls = $urls->filter(function (string $url) use ($siteHost) {
163+
return parse_url($url, PHP_URL_HOST) === $siteHost;
164+
})->filter(function (string $url) {
165+
return 0 === stripos(parse_url($url, PHP_URL_PATH), $this->contentDirectory);
166+
});
167+
168+
// Point all non-uploads "/wp-content" URLs to the assets URL.
169+
$nonUploadsUrls = $contentUrls->filter(function (string $url) use ($uploadsDirectory) {
170+
return false === stripos(parse_url($url, PHP_URL_PATH), $uploadsDirectory);
171+
})->mapWithKeys(function (string $url) {
172+
return [$url => $this->rewriteAssetsUrl(sprintf('%%https?://[^/]*(%s.*)%%', $this->contentDirectory), $url)];
173+
})->all();
174+
175+
// Point all URLs to "/wp-content/uploads" to the uploads URL.
176+
$uploadsUrls = $contentUrls->filter(function (string $url) use ($uploadsDirectory) {
177+
return 0 === stripos(parse_url($url, PHP_URL_PATH), $uploadsDirectory);
178+
})->mapWithKeys(function (string $url) use ($uploadsDirectory) {
179+
return [$url => $this->rewriteUploadsUrl(sprintf('%%https?://[^/]*%s(.*)%%', $uploadsDirectory), $url)];
180+
})->all();
181+
182+
foreach (array_merge($assetsUrls, $nonUploadsUrls, $uploadsUrls) as $originalUrl => $newUrl) {
183+
$content = str_replace($originalUrl, $newUrl, $content);
184+
}
185+
186+
return $content;
187+
}
188+
189+
/**
190+
* Rewrite the wp-content URL so it points to the assets URL.
191+
*/
192+
public function rewriteContentUrl(string $url): string
193+
{
194+
return $this->rewriteAssetsUrl(sprintf('%%https?://.*(%s.*)%%', $this->contentDirectory), $url);
131195
}
132196

133197
/**
134198
* Rewrite the plugins URL so it points to the assets URL.
135199
*/
136200
public function rewritePluginsUrl(string $url): string
201+
{
202+
return $this->rewriteAssetsUrl('%https?://.*(/[^/]*/plugins.*)%', $url);
203+
}
204+
205+
/**
206+
* Check if we need to rewrite the given URL.
207+
*/
208+
private function doesUrlNeedRewrite(string $url): bool
209+
{
210+
return false !== stripos($url, $this->siteUrl)
211+
&& (!empty($this->assetsUrl) && false === stripos($url, $this->assetsUrl))
212+
&& (empty($this->uploadsUrl) || false === stripos($url, $this->uploadsUrl));
213+
}
214+
215+
/**
216+
* Rewrite the given URL to point to the assets URL based on the given REGEX pattern.
217+
*/
218+
private function rewriteAssetsUrl(string $pattern, string $url): string
137219
{
138220
$matches = [];
139-
preg_match('/https?:\/\/.*(\/[^\/]*\/plugins.*)/', $url, $matches);
221+
preg_match($pattern, $url, $matches);
140222

141223
if (empty($matches[1])) {
142224
return $url;
143225
}
144226

145-
return $this->assetsUrl.$matches[1];
227+
return $this->assetsUrl.'/'.ltrim($matches[1], '/');
146228
}
147229

148230
/**
149-
* Check if we need to rewrite the given URL.
231+
* Rewrite the given URL to point to the uploads URL based on the given REGEX pattern.
150232
*/
151-
private function doesUrlNeedRewrite(string $url): bool
233+
private function rewriteUploadsUrl(string $pattern, string $url): string
152234
{
153-
return false !== stripos($url, $this->siteUrl)
154-
&& (!empty($this->assetsUrl) && false === stripos($url, $this->assetsUrl))
155-
&& (empty($this->uploadsUrl) || false === stripos($url, $this->uploadsUrl));
235+
$matches = [];
236+
preg_match($pattern, $url, $matches);
237+
238+
if (empty($matches[1])) {
239+
return $url;
240+
}
241+
242+
return $this->uploadsUrl.'/'.ltrim($matches[1], '/');
156243
}
157244
}

src/Support/Collection.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,14 @@ public function slice(int $offset, ?int $length = null): self
241241
return new self(array_slice($this->items, $offset, $length, true));
242242
}
243243

244+
/**
245+
* Return a collection with only unique values in the collection array.
246+
*/
247+
public function unique(): self
248+
{
249+
return new self(array_unique($this->items));
250+
}
251+
244252
/**
245253
* Reset the keys on the underlying array.
246254
*/

tests/Unit/Subscriber/AssetsSubscriberTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@
2121
*/
2222
class AssetsSubscriberTest extends TestCase
2323
{
24+
public function provideReplaceUrlsInContent(): array
25+
{
26+
return [
27+
['replaces-with-assets-url.html'],
28+
['replaces-with-uploads-url.html'],
29+
['updates-assets-urls.html'],
30+
];
31+
}
32+
2433
public function testAddAssetsUrlToDnsPrefetchDoesntAddAssetsUrlWhenDomainDifferentFromSiteUrl()
2534
{
2635
$this->assertSame(['https://assets.com/assets/uuid'], (new AssetsSubscriber('content_dir', 'https://foo.com', 'https://assets.com/assets/uuid'))->addAssetsUrlToDnsPrefetch([], 'dns-prefetch'));
@@ -54,6 +63,7 @@ public function testGetSubscribedEvents()
5463
'plugins_url' => 'rewritePluginsUrl',
5564
'script_loader_src' => 'replaceSiteUrlWithAssetsUrl',
5665
'style_loader_src' => 'replaceSiteUrlWithAssetsUrl',
66+
'the_content' => ['replaceUrlsInContent', 99999],
5767
'wp_resource_hints' => ['addAssetsUrlToDnsPrefetch', 10, 2],
5868
];
5969

@@ -95,6 +105,26 @@ public function testReplaceSiteUrlWithAssetsUrlWithSourceSameAsUploadUrl()
95105
$this->assertSame('https://foo.com/uploads/asset.css', (new AssetsSubscriber('content_dir', 'https://foo.com', 'https://foo.com/assets/uuid', '', 'https://foo.com/uploads'))->replaceSiteUrlWithAssetsUrl('https://foo.com/uploads/asset.css'));
96106
}
97107

108+
/**
109+
* @dataProvider provideReplaceUrlsInContent
110+
*/
111+
public function testReplaceUrlsInContentWithDifferentAssetsAndSiteDomain(string $filename)
112+
{
113+
list($content, $expected) = explode("\n--EXPECTED--\n", trim(file_get_contents(__DIR__.'/data/replace-urls-content/different-assets-and-site-domain/'.$filename)), 2);
114+
115+
$this->assertSame($expected, (new AssetsSubscriber('content_dir', 'https://foo.com', 'https://assets.com/assets/uuid', '', 'https://assets.com/uploads'))->replaceUrlsInContent($content));
116+
}
117+
118+
/**
119+
* @dataProvider provideReplaceUrlsInContent
120+
*/
121+
public function testReplaceUrlsInContentWithSameAssetsAndSiteDomain(string $filename)
122+
{
123+
list($content, $expected) = explode("\n--EXPECTED--\n", trim(file_get_contents(__DIR__.'/data/replace-urls-content/same-assets-and-site-domain/'.$filename)), 2);
124+
125+
$this->assertSame($expected, (new AssetsSubscriber('content_dir', 'https://foo.com', 'https://foo.com/assets/uuid', '', 'https://foo.com/uploads'))->replaceUrlsInContent($content));
126+
}
127+
98128
public function testRewriteContentUrlDoesntKeepDirectoryBelowContentDir()
99129
{
100130
$this->assertSame('https://assets.com/assets/uuid/content_dir/test.php', (new AssetsSubscriber('content_dir', 'https://foo.com', 'https://assets.com/assets/uuid'))->rewriteContentUrl('https://foo.com/foo/directory/content_dir/test.php'));
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<img src="https://foo.com/content_dir/plugins/plugin_name/img.png" />
2+
<img src='https://foo.com/content_dir/plugins/plugin_name/img.png' />
3+
--EXPECTED--
4+
<img src="https://assets.com/assets/uuid/content_dir/plugins/plugin_name/img.png" />
5+
<img src='https://assets.com/assets/uuid/content_dir/plugins/plugin_name/img.png' />
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<img src="https://foo.com/content_dir/uploads/year/month/day/img.png" />
2+
<img src='https://foo.com/content_dir/uploads/year/month/day/img.png' />
3+
--EXPECTED--
4+
<img src="https://assets.com/uploads/year/month/day/img.png" />
5+
<img src='https://assets.com/uploads/year/month/day/img.png' />
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<img src="https://foo.com/assets/old-uuid/content_dir/plugins/plugin_name/img.png" />
2+
<img src="https://assets.com/assets/old-uuid/content_dir/plugins/plugin_name/img.png" />
3+
<img src='https://foo.com/assets/old-uuid/content_dir/plugins/plugin_name/img.png' />
4+
<img src='https://assets.com/assets/old-uuid/content_dir/plugins/plugin_name/img.png' />
5+
--EXPECTED--
6+
<img src="https://foo.com/assets/old-uuid/content_dir/plugins/plugin_name/img.png" />
7+
<img src="https://assets.com/assets/uuid/content_dir/plugins/plugin_name/img.png" />
8+
<img src='https://foo.com/assets/old-uuid/content_dir/plugins/plugin_name/img.png' />
9+
<img src='https://assets.com/assets/uuid/content_dir/plugins/plugin_name/img.png' />
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<img src="https://foo.com/content_dir/plugins/plugin_name/img.png" />
2+
<img src='https://foo.com/content_dir/plugins/plugin_name/img.png' />
3+
--EXPECTED--
4+
<img src="https://foo.com/assets/uuid/content_dir/plugins/plugin_name/img.png" />
5+
<img src='https://foo.com/assets/uuid/content_dir/plugins/plugin_name/img.png' />
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<img src="https://foo.com/content_dir/uploads/year/month/day/img.png" />
2+
<img src='https://foo.com/content_dir/uploads/year/month/day/img.png' />
3+
--EXPECTED--
4+
<img src="https://foo.com/uploads/year/month/day/img.png" />
5+
<img src='https://foo.com/uploads/year/month/day/img.png' />
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<img src="https://foo.com/assets/old-uuid/content_dir/plugins/plugin_name/img.png" />
2+
<img src='https://foo.com/assets/old-uuid/content_dir/plugins/plugin_name/img.png' />
3+
--EXPECTED--
4+
<img src="https://foo.com/assets/uuid/content_dir/plugins/plugin_name/img.png" />
5+
<img src='https://foo.com/assets/uuid/content_dir/plugins/plugin_name/img.png' />

0 commit comments

Comments
 (0)