diff --git a/code/Model/SiteTree.php b/code/Model/SiteTree.php index ea189770..7424253c 100755 --- a/code/Model/SiteTree.php +++ b/code/Model/SiteTree.php @@ -70,6 +70,7 @@ use SilverStripe\View\ArrayData; use SilverStripe\View\HTML; use SilverStripe\View\Parsers\ShortcodeParser; use SilverStripe\View\Parsers\URLSegmentFilter; +use SilverStripe\View\Shortcodes\EmbedShortcodeProvider; use SilverStripe\View\SSViewer; /** @@ -1607,6 +1608,24 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi if ($oneChangedFields && !array_diff($changedFields, $fieldsIgnoredByVersioning)) { $this->setNextWriteWithoutVersion(true); } + + // Flush cached [embed] shortcodes + // Flush on both DRAFT and LIVE because VersionedCacheAdapter has separate caches for both + // Clear both caches at once for the scenario where a CMS-author updates a remote resource + // on a 3rd party service and the url for the resource stays the same. Either saving or publishing + // the page will clear both caches. This allow a CMS-author to clear the live cache by only + // saving the draft page and not publishing any changes they may not want live yet. + $parser = ShortcodeParser::get('default'); + foreach ([Versioned::DRAFT, Versioned::LIVE] as $stage) { + Versioned::withVersionedMode(function () use ($parser, $stage) { + Versioned::set_reading_mode("Stage.$stage"); + // $this->Content may be null on brand new SiteTree objects + if (!$this->Content) { + return; + } + EmbedShortcodeProvider::flushCachedShortcodes($parser, $this->Content); + }); + } } /** diff --git a/tests/php/Model/SiteTreeTest.php b/tests/php/Model/SiteTreeTest.php index 384df5a4..f3b04557 100644 --- a/tests/php/Model/SiteTreeTest.php +++ b/tests/php/Model/SiteTreeTest.php @@ -4,6 +4,7 @@ namespace SilverStripe\CMS\Tests\Model; use LogicException; use Page; +use Psr\SimpleCache\CacheInterface; use ReflectionMethod; use SilverStripe\CMS\Model\RedirectorPage; use SilverStripe\CMS\Model\SiteTree; @@ -30,6 +31,7 @@ use SilverStripe\Versioned\Versioned; use SilverStripe\View\Parsers\Diff; use SilverStripe\View\Parsers\ShortcodeParser; use SilverStripe\View\Parsers\URLSegmentFilter; +use SilverStripe\View\Shortcodes\EmbedShortcodeProvider; use TractorCow\Fluent\Extension\FluentSiteTreeExtension; use const RESOURCES_DIR; @@ -1691,4 +1693,44 @@ class SiteTreeTest extends SapphireTest $pages = $record->DependentPages(); $this->assertCount(0, $pages, 'Unsaved pages should have no dependent pages'); } + + public function testOnBeforeWriteClearsEmbedShortcodeCache() + { + /** @var CacheInterface $cache */ + $url = 'http://www.test-service.com/abc123'; + $content = '
Some content with an [embed url="' . $url . '" thumbnail="https://example.com/mythumb.jpg" ' . + 'class="leftAlone ss-htmleditorfield-file embed" width="480" height="270"]' . $url . '[/embed]
'; + $embedHtml = ''; + + // use reflection to access private methods + $provider = new EmbedShortcodeProvider(); + $reflector = new \ReflectionClass(EmbedShortcodeProvider::class); + $method = $reflector->getMethod('getCache'); + $method->setAccessible(true); + $cache = $method->invokeArgs($provider, []); + $method = $reflector->getMethod('deriveCacheKey'); + $method->setAccessible(true); + $key = $method->invokeArgs($provider, [$url]); + + // Set cache (VersionedCacheAdapter) on both DRAFT and LIVE + foreach ([Versioned::DRAFT, Versioned::LIVE] as $stage) { + Versioned::withVersionedMode(function () use ($cache, $key, $embedHtml, $stage) { + Versioned::set_reading_mode("Stage.$stage"); + $cache->set($key, $embedHtml); + }); + } + + // Create new page on DRAFT + $page = SiteTree::create(); + $page->Content = $content; + $page->write(); + + // Assert both DRAFT and LIVE caches were cleared on DRAFT $page->write() + foreach ([Versioned::DRAFT, Versioned::LIVE] as $stage) { + Versioned::withVersionedMode(function () use ($cache, $key, $stage) { + Versioned::set_reading_mode("Stage.$stage"); + $this->assertFalse($cache->has($key)); + }); + } + } }