Skip to content

Commit

Permalink
feat: use the_content filter to replace broken urls in post content
Browse files Browse the repository at this point in the history
  • Loading branch information
carlalexander committed Nov 29, 2021
1 parent d779759 commit 94d69f1
Show file tree
Hide file tree
Showing 9 changed files with 173 additions and 14 deletions.
115 changes: 101 additions & 14 deletions src/Subscriber/AssetsSubscriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
namespace Ymir\Plugin\Subscriber;

use Ymir\Plugin\EventManagement\SubscriberInterface;
use Ymir\Plugin\Support\Collection;

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

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

if (empty($matches[1])) {
return $url;
// The assumption is that all URLs are surrounded by either quotes or double quotes.
$patterns = [
'%"(?P<url>https?://[^"]*?)"%is',
"%'(?P<url>https?://[^']*?)'%is",
];
$urls = new Collection();

foreach ($patterns as $pattern) {
$matches = [];

preg_match_all($pattern, $content, $matches);

$urls = $urls->merge($matches['url'] ?? []);
}

if ($urls->isEmpty()) {
return $content;
}

return $this->assetsUrl.$matches[1];
$assetsHost = parse_url($this->assetsUrl, PHP_URL_HOST);
$siteHost = parse_url($this->siteUrl, PHP_URL_HOST);
$uploadsDirectory = $this->contentDirectory.'/uploads';
$urls = $urls->unique();

// If we have a hardcoded URL to an asset, we want to dynamically update it to the
// current asset URL.
$assetsUrls = $urls->filter(function (string $url) use ($assetsHost) {
return parse_url($url, PHP_URL_HOST) === $assetsHost;
})->mapWithKeys(function (string $url) {
return [$url => $this->rewriteAssetsUrl('%https?://[^/]*/assets/[^/]*(.*)%i', $url)];
})->all();

// Get all the URLs pointing to the "/wp-content" directory
$contentUrls = $urls->filter(function (string $url) use ($siteHost) {
return parse_url($url, PHP_URL_HOST) === $siteHost;
})->filter(function (string $url) {
return 0 === stripos(parse_url($url, PHP_URL_PATH), $this->contentDirectory);
});

// Point all non-uploads "/wp-content" URLs to the assets URL.
$nonUploadsUrls = $contentUrls->filter(function (string $url) use ($uploadsDirectory) {
return false === stripos(parse_url($url, PHP_URL_PATH), $uploadsDirectory);
})->mapWithKeys(function (string $url) {
return [$url => $this->rewriteAssetsUrl(sprintf('%%https?://[^/]*(%s.*)%%', $this->contentDirectory), $url)];
})->all();

// Point all URLs to "/wp-content/uploads" to the uploads URL.
$uploadsUrls = $contentUrls->filter(function (string $url) use ($uploadsDirectory) {
return 0 === stripos(parse_url($url, PHP_URL_PATH), $uploadsDirectory);
})->mapWithKeys(function (string $url) use ($uploadsDirectory) {
return [$url => $this->rewriteUploadsUrl(sprintf('%%https?://[^/]*%s(.*)%%', $uploadsDirectory), $url)];
})->all();

foreach (array_merge($assetsUrls, $nonUploadsUrls, $uploadsUrls) as $originalUrl => $newUrl) {
$content = str_replace($originalUrl, $newUrl, $content);
}

return $content;
}

/**
* Rewrite the wp-content URL so it points to the assets URL.
*/
public function rewriteContentUrl(string $url): string
{
return $this->rewriteAssetsUrl(sprintf('%%https?://.*(%s.*)%%', $this->contentDirectory), $url);
}

/**
* Rewrite the plugins URL so it points to the assets URL.
*/
public function rewritePluginsUrl(string $url): string
{
return $this->rewriteAssetsUrl('%https?://.*(/[^/]*/plugins.*)%', $url);
}

/**
* Check if we need to rewrite the given URL.
*/
private function doesUrlNeedRewrite(string $url): bool
{
return false !== stripos($url, $this->siteUrl)
&& (!empty($this->assetsUrl) && false === stripos($url, $this->assetsUrl))
&& (empty($this->uploadsUrl) || false === stripos($url, $this->uploadsUrl));
}

/**
* Rewrite the given URL to point to the assets URL based on the given REGEX pattern.
*/
private function rewriteAssetsUrl(string $pattern, string $url): string
{
$matches = [];
preg_match('/https?:\/\/.*(\/[^\/]*\/plugins.*)/', $url, $matches);
preg_match($pattern, $url, $matches);

if (empty($matches[1])) {
return $url;
}

return $this->assetsUrl.$matches[1];
return $this->assetsUrl.'/'.ltrim($matches[1], '/');
}

/**
* Check if we need to rewrite the given URL.
* Rewrite the given URL to point to the uploads URL based on the given REGEX pattern.
*/
private function doesUrlNeedRewrite(string $url): bool
private function rewriteUploadsUrl(string $pattern, string $url): string
{
return false !== stripos($url, $this->siteUrl)
&& (!empty($this->assetsUrl) && false === stripos($url, $this->assetsUrl))
&& (empty($this->uploadsUrl) || false === stripos($url, $this->uploadsUrl));
$matches = [];
preg_match($pattern, $url, $matches);

if (empty($matches[1])) {
return $url;
}

return $this->uploadsUrl.'/'.ltrim($matches[1], '/');
}
}
8 changes: 8 additions & 0 deletions src/Support/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,14 @@ public function slice(int $offset, ?int $length = null): self
return new self(array_slice($this->items, $offset, $length, true));
}

/**
* Return a collection with only unique values in the collection array.
*/
public function unique(): self
{
return new self(array_unique($this->items));
}

/**
* Reset the keys on the underlying array.
*/
Expand Down
30 changes: 30 additions & 0 deletions tests/Unit/Subscriber/AssetsSubscriberTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@
*/
class AssetsSubscriberTest extends TestCase
{
public function provideReplaceUrlsInContent(): array
{
return [
['replaces-with-assets-url.html'],
['replaces-with-uploads-url.html'],
['updates-assets-urls.html'],
];
}

public function testAddAssetsUrlToDnsPrefetchDoesntAddAssetsUrlWhenDomainDifferentFromSiteUrl()
{
$this->assertSame(['https://assets.com/assets/uuid'], (new AssetsSubscriber('content_dir', 'https://foo.com', 'https://assets.com/assets/uuid'))->addAssetsUrlToDnsPrefetch([], 'dns-prefetch'));
Expand Down Expand Up @@ -54,6 +63,7 @@ public function testGetSubscribedEvents()
'plugins_url' => 'rewritePluginsUrl',
'script_loader_src' => 'replaceSiteUrlWithAssetsUrl',
'style_loader_src' => 'replaceSiteUrlWithAssetsUrl',
'the_content' => ['replaceUrlsInContent', 99999],
'wp_resource_hints' => ['addAssetsUrlToDnsPrefetch', 10, 2],
];

Expand Down Expand Up @@ -95,6 +105,26 @@ public function testReplaceSiteUrlWithAssetsUrlWithSourceSameAsUploadUrl()
$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'));
}

/**
* @dataProvider provideReplaceUrlsInContent
*/
public function testReplaceUrlsInContentWithDifferentAssetsAndSiteDomain(string $filename)
{
list($content, $expected) = explode("\n--EXPECTED--\n", trim(file_get_contents(__DIR__.'/data/replace-urls-content/different-assets-and-site-domain/'.$filename)), 2);

$this->assertSame($expected, (new AssetsSubscriber('content_dir', 'https://foo.com', 'https://assets.com/assets/uuid', '', 'https://assets.com/uploads'))->replaceUrlsInContent($content));
}

/**
* @dataProvider provideReplaceUrlsInContent
*/
public function testReplaceUrlsInContentWithSameAssetsAndSiteDomain(string $filename)
{
list($content, $expected) = explode("\n--EXPECTED--\n", trim(file_get_contents(__DIR__.'/data/replace-urls-content/same-assets-and-site-domain/'.$filename)), 2);

$this->assertSame($expected, (new AssetsSubscriber('content_dir', 'https://foo.com', 'https://foo.com/assets/uuid', '', 'https://foo.com/uploads'))->replaceUrlsInContent($content));
}

public function testRewriteContentUrlDoesntKeepDirectoryBelowContentDir()
{
$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'));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<img src="https://foo.com/content_dir/plugins/plugin_name/img.png" />
<img src='https://foo.com/content_dir/plugins/plugin_name/img.png' />
--EXPECTED--
<img src="https://assets.com/assets/uuid/content_dir/plugins/plugin_name/img.png" />
<img src='https://assets.com/assets/uuid/content_dir/plugins/plugin_name/img.png' />
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<img src="https://foo.com/content_dir/uploads/year/month/day/img.png" />
<img src='https://foo.com/content_dir/uploads/year/month/day/img.png' />
--EXPECTED--
<img src="https://assets.com/uploads/year/month/day/img.png" />
<img src='https://assets.com/uploads/year/month/day/img.png' />
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<img src="https://foo.com/assets/old-uuid/content_dir/plugins/plugin_name/img.png" />
<img src="https://assets.com/assets/old-uuid/content_dir/plugins/plugin_name/img.png" />
<img src='https://foo.com/assets/old-uuid/content_dir/plugins/plugin_name/img.png' />
<img src='https://assets.com/assets/old-uuid/content_dir/plugins/plugin_name/img.png' />
--EXPECTED--
<img src="https://foo.com/assets/old-uuid/content_dir/plugins/plugin_name/img.png" />
<img src="https://assets.com/assets/uuid/content_dir/plugins/plugin_name/img.png" />
<img src='https://foo.com/assets/old-uuid/content_dir/plugins/plugin_name/img.png' />
<img src='https://assets.com/assets/uuid/content_dir/plugins/plugin_name/img.png' />
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<img src="https://foo.com/content_dir/plugins/plugin_name/img.png" />
<img src='https://foo.com/content_dir/plugins/plugin_name/img.png' />
--EXPECTED--
<img src="https://foo.com/assets/uuid/content_dir/plugins/plugin_name/img.png" />
<img src='https://foo.com/assets/uuid/content_dir/plugins/plugin_name/img.png' />
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<img src="https://foo.com/content_dir/uploads/year/month/day/img.png" />
<img src='https://foo.com/content_dir/uploads/year/month/day/img.png' />
--EXPECTED--
<img src="https://foo.com/uploads/year/month/day/img.png" />
<img src='https://foo.com/uploads/year/month/day/img.png' />
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<img src="https://foo.com/assets/old-uuid/content_dir/plugins/plugin_name/img.png" />
<img src='https://foo.com/assets/old-uuid/content_dir/plugins/plugin_name/img.png' />
--EXPECTED--
<img src="https://foo.com/assets/uuid/content_dir/plugins/plugin_name/img.png" />
<img src='https://foo.com/assets/uuid/content_dir/plugins/plugin_name/img.png' />

0 comments on commit 94d69f1

Please sign in to comment.