Commit 7461c419 authored by samuel.mortenson's avatar samuel.mortenson Committed by Samuel Mortenson

Issue #2997011 by samuel.mortenson: Split static site generation and content...

Issue #2997011 by samuel.mortenson: Split static site generation and content import/export into separate sub-modules
parent 59227265
services:
tome.export_commands:
class: \Drupal\tome\Commands\ExportCommands
arguments: ['@tome.exporter', '@entity_type.manager', '@account_switcher', '@event_dispatcher']
tags:
- { name: drush.command }
tome.import_commands:
class: \Drupal\tome\Commands\ImportCommands
arguments: ['@tome.importer', '@entity_type.manager', '@account_switcher']
tags:
- { name: drush.command }
tome.static_commands:
class: \Drupal\tome\Commands\StaticCommands
arguments: ['@tome.static', '@tome.static_cache']
tags:
- { name: drush.command }
tome.file_commands:
class: \Drupal\tome\Commands\FileCommands
arguments: ['@tome.storage.content', '@config.storage.sync']
tags:
- { name: drush.command }
tome.deprecated_commands:
class: \Drupal\tome\Commands\DeprecatedCommands
arguments: ['@tome.storage.content']
tags:
- { name: drush.command }
<?php
namespace Drupal\tome;
namespace Drupal\tome_base;
use Symfony\Component\Process\Process;
......
<?php
namespace Drupal\Tests\tome\Kernel;
namespace Drupal\Tests\tome_base\Kernel;
use Drupal\Core\Site\Settings;
use Drupal\field\Tests\EntityReference\EntityReferenceTestTrait;
......@@ -28,7 +28,6 @@ abstract class TestBase extends KernelTestBase {
'field',
'node',
'text',
'tome',
'user',
'file',
'serialization',
......
name: 'Tome Base'
type: module
description: 'Contains shared services and traits between Tome Sync and Tome Static.'
core: 8.x
package: Tome
services:
tome_static.commands:
class: \Drupal\tome_static\Commands\StaticCommands
arguments: ['@tome_static.generator', '@tome_static.cache']
tags:
- { name: drush.command }
<?php
namespace Drupal\tome\Commands;
namespace Drupal\tome_static\Commands;
use Drupal\tome\ConcurrentProcessTrait;
use Drupal\tome\StaticCache;
use Drupal\tome\StaticGeneratorInterface;
use Drupal\tome_base\ConcurrentProcessTrait;
use Drupal\tome_static\StaticCacheInterface;
use Drupal\tome_static\StaticGeneratorInterface;
use Drush\Commands\DrushCommands;
use Drush\Drush;
use Drush\Exec\ExecTrait;
use Symfony\Component\Process\Process;
/**
* Contains static site commands for tome.
* Contains commands for Tome Static.
*/
class StaticCommands extends DrushCommands {
......@@ -28,26 +28,26 @@ class StaticCommands extends DrushCommands {
/**
* The static service.
*
* @var \Drupal\tome\StaticGeneratorInterface
* @var \Drupal\tome_static\StaticGeneratorInterface
*/
protected $static;
/**
* The static cache.
*
* @var \Drupal\tome\StaticCache
* @var \Drupal\tome_static\StaticCacheInterface
*/
protected $cache;
/**
* Constructs a StaticCommands instance.
*
* @param \Drupal\tome\StaticGeneratorInterface $static
* @param \Drupal\tome_static\StaticGeneratorInterface $static
* The static service.
* @param \Drupal\tome\StaticCache $cache
* @param \Drupal\tome_static\StaticCacheInterface $cache
* The static cache.
*/
public function __construct(StaticGeneratorInterface $static, StaticCache $cache) {
public function __construct(StaticGeneratorInterface $static, StaticCacheInterface $cache) {
$this->static = $static;
$this->cache = $cache;
}
......
<?php
namespace Drupal\tome\Event;
namespace Drupal\tome_static\Event;
use Symfony\Component\EventDispatcher\Event;
......
<?php
namespace Drupal\tome\Event;
namespace Drupal\tome_static\Event;
use Symfony\Component\EventDispatcher\Event;
......
<?php
namespace Drupal\tome_static\Event;
/**
* Defines events for Tome Static.
*/
final class TomeStaticEvents {
/**
* Name of the event fired when collecting paths for the static generator.
*
* @Event
*
* @see \Drupal\tome_static\Event\CollectPathsEvent
*
* @var string
*/
const COLLECT_PATHS = 'tome_static.collect_paths';
/**
* Name of the event fired when replacing a path placeholder.
*
* @Event
*
* @see \Drupal\tome_static\Event\PathPlaceholderEvent
*
* @var string
*/
const PATH_PLACEHOLDER = 'tome_static.path_placeholder';
}
<?php
namespace Drupal\tome\EventSubscriber;
namespace Drupal\tome_static\EventSubscriber;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\tome\Event\CollectPathsEvent;
use Drupal\tome\Event\PathPlaceholderEvent;
use Drupal\tome\Event\TomeEvents;
use Drupal\tome_static\Event\CollectPathsEvent;
use Drupal\tome_static\Event\PathPlaceholderEvent;
use Drupal\tome_static\Event\TomeStaticEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
......@@ -52,7 +52,7 @@ class EntityPathSubscriber implements EventSubscriberInterface {
/**
* Reacts to a collect paths event.
*
* @param \Drupal\tome\Event\CollectPathsEvent $event
* @param \Drupal\tome_static\Event\CollectPathsEvent $event
* The collect paths event.
*/
public function collectPaths(CollectPathsEvent $event) {
......@@ -90,7 +90,7 @@ class EntityPathSubscriber implements EventSubscriberInterface {
/**
* Reacts to a path placeholder event.
*
* @param \Drupal\tome\Event\PathPlaceholderEvent $event
* @param \Drupal\tome_static\Event\PathPlaceholderEvent $event
* The path placeholder event.
*/
public function replacePathPlaceholder(PathPlaceholderEvent $event) {
......@@ -117,8 +117,8 @@ class EntityPathSubscriber implements EventSubscriberInterface {
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events[TomeEvents::COLLECT_PATHS][] = ['collectPaths'];
$events[TomeEvents::PATH_PLACEHOLDER][] = ['replacePathPlaceholder'];
$events[TomeStaticEvents::COLLECT_PATHS][] = ['collectPaths'];
$events[TomeStaticEvents::PATH_PLACEHOLDER][] = ['replacePathPlaceholder'];
return $events;
}
......
<?php
namespace Drupal\tome\EventSubscriber;
namespace Drupal\tome_static\EventSubscriber;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl;
use Drupal\tome\Event\CollectPathsEvent;
use Drupal\tome\Event\TomeEvents;
use Drupal\tome_static\Event\CollectPathsEvent;
use Drupal\tome_static\Event\TomeStaticEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
......@@ -44,7 +44,7 @@ class LanguagePathSubscriber implements EventSubscriberInterface {
/**
* Reacts to a collect paths event to add multilingual homepage paths.
*
* @param \Drupal\tome\Event\CollectPathsEvent $event
* @param \Drupal\tome_static\Event\CollectPathsEvent $event
* The collect paths event.
*/
public function collectPaths(CollectPathsEvent $event) {
......@@ -63,7 +63,7 @@ class LanguagePathSubscriber implements EventSubscriberInterface {
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events[TomeEvents::COLLECT_PATHS][] = ['collectPaths'];
$events[TomeStaticEvents::COLLECT_PATHS][] = ['collectPaths'];
return $events;
}
......
<?php
namespace Drupal\tome\EventSubscriber;
namespace Drupal\tome_static\EventSubscriber;
use Drupal\Core\Routing\RouteProviderInterface;
use Drupal\Core\Url;
use Drupal\tome\Event\CollectPathsEvent;
use Drupal\tome\Event\TomeEvents;
use Drupal\tome_static\Event\CollectPathsEvent;
use Drupal\tome_static\Event\TomeStaticEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
......@@ -33,7 +33,7 @@ class RoutePathSubscriber implements EventSubscriberInterface {
/**
* Reacts to a collect paths event.
*
* @param \Drupal\tome\Event\CollectPathsEvent $event
* @param \Drupal\tome_static\Event\CollectPathsEvent $event
* The collect paths event.
*/
public function collectPaths(CollectPathsEvent $event) {
......@@ -54,7 +54,7 @@ class RoutePathSubscriber implements EventSubscriberInterface {
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events[TomeEvents::COLLECT_PATHS][] = ['collectPaths'];
$events[TomeStaticEvents::COLLECT_PATHS][] = ['collectPaths'];
return $events;
}
......
<?php
namespace Drupal\tome\PageCache\RequestPolicy;
namespace Drupal\tome_static\PageCache\RequestPolicy;
use Drupal\Core\PageCache\ChainRequestPolicy;
use Drupal\Core\PageCache\RequestPolicy\NoSessionOpen;
......
<?php
namespace Drupal\tome\PageCache\RequestPolicy;
namespace Drupal\tome_static\PageCache\RequestPolicy;
use Drupal\Core\PageCache\ChainRequestPolicy;
......
<?php
namespace Drupal\tome\PageCache\RequestPolicy;
namespace Drupal\tome_static\PageCache\RequestPolicy;
use Drupal\Core\PageCache\RequestPolicyInterface;
use Symfony\Component\HttpFoundation\Request;
......
<?php
namespace Drupal\tome_static;
use Symfony\Component\HttpFoundation\Response;
/**
* A default static cache that reports all pages as uncached.
*
* This class should be overridden if static caching is possible.
*
* @internal
*/
class StaticCache implements StaticCacheInterface {
/**
* {@inheritdoc}
*/
public function isCached($path) {
return FALSE;
}
/**
* {@inheritdoc}
*/
public function add($path, Response $response) {}
/**
* {@inheritdoc}
*/
public function deleteAll($reload = FALSE) {}
/**
* {@inheritdoc}
*/
public function commit() {}
}
<?php
namespace Drupal\tome_static;
use Symfony\Component\HttpFoundation\Response;
/**
* Provides an interface for classes that handle static caching.
*/
interface StaticCacheInterface {
/**
* Determines if a request is cached.
*
* @param string $path
* The request path.
*
* @return bool
* TRUE if the request is cached, FALSE otherwise.
*/
public function isCached($path);
/**
* Adds cache metadata for a request/response pair.
*
* Once all static pages are generated, the commit method should be run to
* ensure the cache metadata is included in the static build.
*
* @param string $path
* The request path.
* @param \Symfony\Component\HttpFoundation\Response $response
* The response.
*/
public function add($path, Response $response);
/**
* Removes all cache metadata from the database.
*
* If you're starting a new static build, you should call this to ensure that
* the database does not include any old cache metadata.
*
* @param bool $reload
* If the cache metadata should be reloaded from the filesystem.
*/
public function deleteAll($reload = FALSE);
/**
* Writes the static metadata.
*/
public function commit();
}
<?php
namespace Drupal\tome;
namespace Drupal\tome_static;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Site\Settings;
use Drupal\tome\Event\CollectPathsEvent;
use Drupal\tome\Event\PathPlaceholderEvent;
use Drupal\tome\Event\TomeEvents;
use Drupal\tome_static\Event\CollectPathsEvent;
use Drupal\tome_static\Event\PathPlaceholderEvent;
use Drupal\tome_static\Event\TomeStaticEvents;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\Request;
......@@ -42,7 +42,7 @@ class StaticGenerator implements StaticGeneratorInterface {
/**
* The static cache.
*
* @var \Drupal\tome\StaticCache
* @var \Drupal\tome_static\StaticCacheInterface
*/
protected $cache;
......@@ -55,10 +55,10 @@ class StaticGenerator implements StaticGeneratorInterface {
* The request stack.
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
* The event dispatcher.
* @param \Drupal\tome\StaticCache $cache
* @param \Drupal\tome_static\StaticCacheInterface $cache
* The static cache.
*/
public function __construct(HttpKernelInterface $http_kernel, RequestStack $request_stack, EventDispatcherInterface $event_dispatcher, StaticCache $cache) {
public function __construct(HttpKernelInterface $http_kernel, RequestStack $request_stack, EventDispatcherInterface $event_dispatcher, StaticCacheInterface $cache) {
$this->httpKernel = $http_kernel;
$this->currentRequest = $request_stack->getCurrentRequest();
$this->eventDispatcher = $event_dispatcher;
......@@ -70,7 +70,7 @@ class StaticGenerator implements StaticGeneratorInterface {
*/
public function getPaths($partial = FALSE) {
$event = new CollectPathsEvent([]);
$this->eventDispatcher->dispatch(TomeEvents::COLLECT_PATHS, $event);
$this->eventDispatcher->dispatch(TomeStaticEvents::COLLECT_PATHS, $event);
$paths = array_unique($event->getPaths());
$paths = array_values(array_diff($paths, $this->getExcludedPaths()));
......@@ -92,7 +92,7 @@ class StaticGenerator implements StaticGeneratorInterface {
$original_path = $path;
$event = new PathPlaceholderEvent($path);
$this->eventDispatcher->dispatch(TomeEvents::PATH_PLACEHOLDER, $event);
$this->eventDispatcher->dispatch(TomeStaticEvents::PATH_PLACEHOLDER, $event);
if ($event->isInvalid()) {
return [];
......
<?php
namespace Drupal\tome;
namespace Drupal\tome_static;
/**
* Provides an interface for the static generator.
......
<?php
namespace Drupal\tome_static;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceProviderInterface;
use Drupal\tome_static\EventSubscriber\LanguagePathSubscriber;
use Drupal\tome_static\PageCache\RequestPolicy\DynamicRequestPolicy;
use Symfony\Component\DependencyInjection\Reference;
/**
* Registers services in the container.
*/
class TomeStaticServiceProvider implements ServiceProviderInterface {
/**
* {@inheritdoc}
*/
public function register(ContainerBuilder $container) {
$modules = $container->getParameter('container.modules');
if (isset($modules['language'])) {
$container->register('tome_static.language_path_subscriber', LanguagePathSubscriber::class)
->addTag('event_subscriber')
->addArgument(new Reference('config.factory'))
->addArgument(new Reference('language_manager'));
}
if (isset($modules['dynamic_page_cache'])) {
$container->register('tome_static.dynamic_page_cache_request_policy', DynamicRequestPolicy::class)
->setDecoratedService('dynamic_page_cache_request_policy');
}
}
}
......@@ -4,4 +4,4 @@ hidden: TRUE
description: 'Tome test'
core: 8.x
dependencies:
- tome:tome
- tome:tome_static
<?php
namespace Drupal\Tests\tome\Kernel;
namespace Drupal\Tests\tome_static\Kernel;
use Drupal\Core\Datetime\Entity\DateFormat;
use Drupal\Core\Site\Settings;
use Drupal\node\Entity\Node;
use Drupal\Tests\tome_base\Kernel\TestBase;
/**
* Tests that static site generation works.
*
* @coversDefaultClass \Drupal\tome\StaticGenerator
* @group tome
* @coversDefaultClass \Drupal\tome_static\StaticGenerator
* @group tome_static
*/
class StaticGeneratorTest extends TestBase {
/**
* @covers \Drupal\tome\StaticGenerator::getPaths
* @covers \Drupal\tome\EventSubscriber\LanguagePathSubscriber::collectPaths
* {@inheritdoc}
*/
public static $modules = [
'tome_static',
];
/**
* @covers \Drupal\tome_static\StaticGenerator::getPaths
* @covers \Drupal\tome_static\EventSubscriber\LanguagePathSubscriber::collectPaths
*/
public function testGetPaths() {
/** @var \Drupal\tome\StaticGenerator $static */
$static = \Drupal::service('tome.static');
/** @var \Drupal\tome_static\StaticGenerator $static */
$static = \Drupal::service('tome_static.generator');
$this->assertEquals([
'/',
'/french',
......@@ -48,29 +55,29 @@ class StaticGeneratorTest extends TestBase {
}
/**
* @covers \Drupal\tome\StaticGenerator::requestPath
* @covers \Drupal\tome_static\StaticGenerator::requestPath
*/
public function testRequestPath() {
$this->enableModules(['tome_test']);
/** @var \Drupal\tome\StaticGenerator $static */
$static = \Drupal::service('tome.static');
/** @var \Drupal\tome_static\StaticGenerator $static */
$static = \Drupal::service('tome_static.generator');
$static->requestPath('/tome-test/page');
$this->assertContains('Hello, world', file_get_contents(Settings::get('tome_static_directory') . '/tome-test/page/index.html'));
}
/**
* @covers \Drupal\tome\StaticGenerator::requestPath
* @covers \Drupal\tome\StaticGenerator::getCssAssets
* @covers \Drupal\tome\StaticGenerator::getHtmlAssets
* @covers \Drupal\tome\StaticGenerator::exportPaths
* @covers \Drupal\tome_static\StaticGenerator::requestPath
* @covers \Drupal\tome_static\StaticGenerator::getCssAssets
* @covers \Drupal\tome_static\StaticGenerator::getHtmlAssets
* @covers \Drupal\tome_static\StaticGenerator::exportPaths
*/
public function testComplexHtml() {
$this->enableModules(['tome_test']);
/** @var \Drupal\tome\StaticGenerator $static */
$static = \Drupal::service('tome.static');
/** @var \Drupal\tome_static\StaticGenerator $static */
$static = \Drupal::service('tome_static.generator');
$invoke_paths = $static->requestPath('/tome-test/complex-page');
$static->exportPaths($invoke_paths);
......@@ -85,16 +92,16 @@ class StaticGeneratorTest extends TestBase {
}
/**
* @covers \Drupal\tome\StaticGenerator::requestPath
* @covers \Drupal\tome\StaticGenerator::replacePagerUrl
* @covers \Drupal\tome\StaticGenerator::handlePagers
* @covers \Drupal\tome\StaticGenerator::exportPaths
* @covers \Drupal\tome_static\StaticGenerator::requestPath
* @covers \Drupal\tome_static\StaticGenerator::replacePagerUrl
* @covers \Drupal\tome_static\StaticGenerator::handlePagers
* @covers \Drupal\tome_static\StaticGenerator::exportPaths
*/
public function testPagers() {
$this->enableModules(['tome_test']);
/** @var \Drupal\tome\StaticGenerator $static */
$static = \Drupal::service('tome.static');
/** @var \Drupal\tome_static\StaticGenerator $static */
$static = \Drupal::service('tome_static.generator');
$invoke_paths = $static->requestPath('/tome-test/pager-page');
$this->assertContains('/tome-test/pager-page?page=0', $invoke_paths);
......@@ -107,67 +114,4 @@ class StaticGeneratorTest extends TestBase {
$this->assertContains('href="/foobar/page/3"', $contents);
}
/**
* @covers \Drupal\tome\StaticGenerator::getPaths
* @covers \Drupal\tome\StaticCache::isCached
*/
public function testCache() {
$this->enableModules(['tome_test']);
/** @var \Drupal\tome\StaticGenerator $static */
$static = \Drupal::service('tome.static');
/** @var \Drupal\tome\StaticCache $cache */
$cache = \Drupal::service('tome.static_cache');
DateFormat::create([
'pattern' => 'D, m/d/Y - H:i',
'id' => 'medium',
])->save();
/** @var \Drupal\node\Entity\Node $article */
$article = Node::create(['type' => 'article', 'title' => 'My article']);
$article->save();
$path = '_entity:node:en:' . $article->id();
$this->assertContains($path, $static->getPaths());
$static->requestPath($path);
$cache->commit();
$this->assertNotContains($path, $static->getPaths(TRUE));
$article->setTitle('Changed!');
$article->save();
$this->assertContains($path, $static->getPaths(TRUE));
$static->requestPath($path);
$cache->commit();
$this->assertNotContains($path, $static->getPaths(TRUE));
$settings = Settings::getInstance() ? Settings::getAll() : [];
$settings['tome_static_cache_exclude'] = [$path];
new Settings($settings);
$this->assertContains($path, $static->getPaths(TRUE));
$settings['tome_static_cache_exclude'] = ['/_entity:node:.*/'];
new Settings($settings);
$this->assertContains($path, $static->getPaths(TRUE));
$invoke_paths = $static->requestPath('/tome-test/pager-page');
$this->assertContains('/tome-test/pager-page?page=0', $invoke_paths);
$static->requestPath('/tome-test/pager-page?page=0');
$cache->commit();
$invoke_paths = $static->requestPath('/tome-test/pager-page', TRUE);
$this->assertNotContains('/tome-test/pager-page?page=0', $invoke_paths);
}
}
name: 'Tome Static'
type: module
description: 'Exports an entire Drupal site to static HTML.'
core: 8.x
package: Tome
dependencies:
- tome:tome_base
services:
tome_static.generator:
class: Drupal\tome_static\StaticGenerator
arguments: ['@http_kernel', '@request_stack', '@event_dispatcher', '@tome_static.cache']