Commit 075bb6b1 authored by samuel.mortenson's avatar samuel.mortenson Committed by Samuel Mortenson

Issue #3047447 by samuel.mortenson: Split document and destination...

Issue #3047447 by samuel.mortenson: Split document and destination modification used for pagers and oembed into event subscribers
parent 88a2fe6c
<?php
namespace Drupal\tome_static\Event;
use Symfony\Component\EventDispatcher\Event;
/**
* Allows modules to react to a file save.
*/
class FileSavedEvent extends Event {
/**
* The path to the saved file.
*
* @var string
*/
protected $path;
/**
* Constructs a FileSavedEvent object.
*
* @param string $path
* A path.
*/
public function __construct($path) {
$this->path = $path;
}
/**
* Gets the path to the saved file.
*
* @return string
* The path.
*/
public function getPath() {
return $this->path;
}
}
<?php
namespace Drupal\tome_static\Event;
use Symfony\Component\EventDispatcher\Event;
/**
* Allows modules to modify the HTML of a static page before save.
*/
class ModifyDestinationEvent extends Event {
/**
* The destination path.
*
* @var string
*/
protected $destination;
/**
* Constructs a ModifyDestinationEvent object.
*
* @param string $destination
* The destination path.
*/
public function __construct($destination) {
$this->destination = $destination;
}
/**
* Gets the destination path.
*
* @return string
* The destination path.
*/
public function getDestination() {
return $this->destination;
}
/**
* Sets the destination path.
*
* @param string $destination
* The destination path.
*/
public function setDestination($destination) {
$this->destination = $destination;
}
}
<?php
namespace Drupal\tome_static\Event;
use Symfony\Component\EventDispatcher\Event;
/**
* Allows modules to modify the HTML of a static page before save.
*/
class ModifyHtmlEvent extends Event {
/**
* The page's HTML.
*
* @var string
*/
protected $html;
/**
* An array of paths to invoke.
*
* @var array
*/
protected $invokePaths = [];
/**
* An array of paths to exclude.
*
* This is useful if you're replacing paths in the HTML.
*
* @var array
*/
protected $excludePaths = [];
/**
* The current path.
*
* @var string
*/
protected $path;
/**
* Constructs a ModifyHtmlEvent object.
*
* @param string $html
* The page's HTML.
* @param string $path
* The current path.
*/
public function __construct($html, $path) {
$this->html = $html;
$this->path = $path;
}
/**
* Returns the current path.
*
* @return string
* The path.
*/
public function getPath() {
return $this->path;
}
/**
* Adds a path to invoke.
*
* @param string $path
* The path.
*/
public function addInvokePath($path) {
$this->invokePaths[] = $path;
}
/**
* Gets the invoke paths.
*
* @return array
* The invoke paths.
*/
public function getInvokePaths() {
return $this->invokePaths;
}
/**
* Gets the exclude paths.
*
* @return array
* The exclude paths.
*/
public function getExcludePaths() {
return $this->excludePaths;
}
/**
* Adds a path to exclude.
*
* @param string $path
* The path.
*/
public function addExcludePath($path) {
$this->excludePaths[] = $path;
}
/**
* Gets the HTML for this page.
*
* @return string
* The HTML.
*/
public function getHtml() {
return $this->html;
}
/**
* Sets the HTML for this page.
*
* @param string $html
* The HTML.
*/
public function setHtml($html) {
$this->html = $html;
}
}
......@@ -38,4 +38,37 @@ final class TomeStaticEvents {
*/
const REQUEST_PREPARE = 'tome_static.request_prepare';
/**
* Name of the event fired when finding paths related to an HTML document.
*
* @Event
*
* @see \Drupal\tome_static\Event\ModifyHtmlEvent
*
* @var string
*/
const MODIFY_HTML = 'tome_static.modify_html';
/**
* Modifies the destination path for a static page.
*
* @Event
*
* @see \Drupal\tome_static\Event\ModifyDestinationEvent
*
* @var string
*/
const MODIFY_DESTINATION = 'tome_static.modify_destination';
/**
* Name of the event fired when a static file is saved.
*
* @Event
*
* @see \Drupal\tome_static\Event\FileSavedEvent
*
* @var string
*/
const FILE_SAVED = 'tome_static.file_saved';
}
<?php
namespace Drupal\tome_static\EventSubscriber;
use Drupal\tome_static\Event\ModifyDestinationEvent;
use Drupal\tome_static\Event\ModifyHtmlEvent;
use Drupal\tome_static\Event\TomeStaticEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Converts Media OEmbed query parameters to static paths.
*
* @internal
*/
class MediaOembedPathSubscriber implements EventSubscriberInterface {
/**
* Reacts to a modify destination event.
*
* @param \Drupal\tome_static\Event\ModifyDestinationEvent $event
* The event.
*/
public function modifyDestination(ModifyDestinationEvent $event) {
$destination = $event->getDestination();
$destination = $this->modifyUrl($destination);
$event->setDestination($destination);
}
/**
* Reacts to a modify HTML event.
*
* @param \Drupal\tome_static\Event\ModifyHtmlEvent $event
* The event.
*/
public function modifyHtml(ModifyHtmlEvent $event) {
$html = $event->getHtml();
$document = new \DOMDocument();
@$document->loadHTML($html);
$xpath = new \DOMXPath($document);
/** @var \DOMElement $node */
foreach ($xpath->query('//iframe[contains(@src,"/media/oembed?url=")]') as $node) {
$original_src = $node->getAttribute('src');
$new_src = $this->modifyUrl($original_src);
$event->addInvokePath($original_src);
$event->addExcludePath($new_src);
$html = str_replace($original_src, $new_src, $html);
$html = str_replace(htmlentities($original_src), $new_src, $html);
}
$event->setHtml($html);
}
/**
* Modifies a URL to replace pager query parameters with paths.
*
* @param string $url
* A URL.
*
* @return string
* The modified URL.
*/
protected function modifyUrl($url) {
$query = parse_url($url, PHP_URL_QUERY);
if (!empty($query) && preg_match('|.*/media/oembed\?.*|', $url)) {
$base_path = preg_replace('/\?.*/', '', $url);
$url = "$base_path/" . md5(urldecode($query));
}
return $url;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events[TomeStaticEvents::MODIFY_DESTINATION][] = ['modifyDestination'];
$events[TomeStaticEvents::MODIFY_HTML][] = ['modifyHtml'];
return $events;
}
}
<?php
namespace Drupal\tome_static\EventSubscriber;
use Drupal\tome_static\Event\ModifyDestinationEvent;
use Drupal\tome_static\Event\ModifyHtmlEvent;
use Drupal\tome_static\Event\TomeStaticEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Converts pager query parameters to static paths.
*
* @internal
*/
class PagerPathSubscriber implements EventSubscriberInterface {
/**
* Reacts to a modify destination event.
*
* @param \Drupal\tome_static\Event\ModifyDestinationEvent $event
* The event.
*/
public function modifyDestination(ModifyDestinationEvent $event) {
$destination = $event->getDestination();
$destination = $this->modifyUrl($destination);
$event->setDestination($destination);
}
/**
* Reacts to a modify HTML event.
*
* @param \Drupal\tome_static\Event\ModifyHtmlEvent $event
* The event.
*/
public function modifyHtml(ModifyHtmlEvent $event) {
$html = $event->getHtml();
$path = $event->getPath();
$document = new \DOMDocument();
@$document->loadHTML($html);
$xpath = new \DOMXPath($document);
/** @var \DOMElement $node */
foreach ($xpath->query('//a[contains(@href,"?page=")]') as $node) {
$original_href = $node->getAttribute('href');
if ($original_href[0] === '?') {
$new_href = strtok($path, '?') . $original_href;
}
else {
$new_href = $original_href;
}
$event->addInvokePath($new_href);
$new_href = $this->modifyUrl($new_href);
$event->addExcludePath($new_href);
$html = str_replace($original_href, $new_href, $html);
$html = str_replace(htmlentities($original_href), $new_href, $html);
}
$event->setHtml($html);
}
/**
* Modifies a URL to replace pager query parameters with paths.
*
* @param string $url
* A URL.
*
* @return string
* The modified URL.
*/
protected function modifyUrl($url) {
parse_str(parse_url($url, PHP_URL_QUERY), $query);
if ($query && isset($query['page']) && is_numeric($query['page'])) {
$base_path = preg_replace('/\?.*/', '', $url);
if ($query['page'] != 0) {
if ($base_path === '/') {
$base_path = '';
}
$url = $base_path . '/page/' . ($query['page'] + 1);
}
else {
$url = !empty($base_path) ? $base_path : '/';
}
}
return $url;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events[TomeStaticEvents::MODIFY_DESTINATION][] = ['modifyDestination'];
$events[TomeStaticEvents::MODIFY_HTML][] = ['modifyHtml'];
return $events;
}
}
......@@ -8,6 +8,9 @@ use Drupal\Core\Session\AnonymousUserSession;
use Drupal\Core\Site\Settings;
use Drupal\tome_base\PathTrait;
use Drupal\tome_static\Event\CollectPathsEvent;
use Drupal\tome_static\Event\FileSavedEvent;
use Drupal\tome_static\Event\ModifyDestinationEvent;
use Drupal\tome_static\Event\ModifyHtmlEvent;
use Drupal\tome_static\Event\PathPlaceholderEvent;
use Drupal\tome_static\Event\TomeStaticEvents;
use Drupal\tome_static\EventSubscriber\ExcludePathSubscriber;
......@@ -149,13 +152,15 @@ class StaticGenerator implements StaticGeneratorInterface {
else {
$content = $response->getContent();
if (strpos($response->headers->get('Content-Type'), 'text/html') === 0) {
$pager_results = $this->handlePagers($content, $path);
$oembed_results = $this->handleOembed($content);
$invoke_paths = array_merge($invoke_paths, $this->getHtmlAssets($content, $path), $pager_results[0], $oembed_results[0]);
$invoke_paths = array_diff($invoke_paths, $pager_results[1], $oembed_results[1]);
$event = new ModifyHtmlEvent($content, $path);
$this->eventDispatcher->dispatch(TomeStaticEvents::MODIFY_HTML, $event);
$content = $event->getHtml();
$invoke_paths = array_merge($invoke_paths, $this->getHtmlAssets($content, $path), $event->getInvokePaths());
$invoke_paths = array_diff($invoke_paths, $event->getExcludePaths());
}
file_put_contents($destination, $content);
}
$this->eventDispatcher->dispatch(TomeStaticEvents::FILE_SAVED, new FileSavedEvent($destination));
if ($response instanceof RedirectResponse) {
$target_url = $this->makeExternalUrlLocal($response->getTargetUrl());
......@@ -328,112 +333,6 @@ class StaticGenerator implements StaticGeneratorInterface {
return $paths;
}
/**
* Converts pager links to distinct URLs and returns paths for processing.
*
* @param string &$content
* An HTML string.
* @param string $path
* The current path.
*
* @return array
* An array containing invoke paths and paths to exclude.
*/
protected function handlePagers(&$content, $path) {
$invoke_paths = [];
$exclude_paths = [];
$document = new \DOMDocument();
@$document->loadHTML($content);
$xpath = new \DOMXPath($document);
/** @var \DOMElement $node */
foreach ($xpath->query('//a[contains(@href,"?page=")]') as $node) {
$original_href = $node->getAttribute('href');
if ($original_href[0] === '?') {
$new_href = strtok($path, '?') . $original_href;
}
else {
$new_href = $original_href;
}
$invoke_paths[] = $new_href;
$new_href = $this->replacePagerUrl($new_href);
$exclude_paths[] = $new_href;
$content = str_replace($original_href, $new_href, $content);
$content = str_replace(htmlentities($original_href), $new_href, $content);
}
return [$invoke_paths, $exclude_paths];
}
/**
* Converts media oembed iframe URLs and returns paths for processing.
*
* @param string &$content
* An HTML string.
*
* @return array
* An array containing invoke paths and paths to exclude.
*/
protected function handleOembed(&$content) {
$invoke_paths = [];
$exclude_paths = [];
$document = new \DOMDocument();
@$document->loadHTML($content);
$xpath = new \DOMXPath($document);
/** @var \DOMElement $node */
foreach ($xpath->query('//iframe[contains(@src,"/media/oembed?url=")]') as $node) {
$original_src = $node->getAttribute('src');
$new_src = $this->replaceOembedUrl($original_src);
$invoke_paths[] = $original_src;
$exclude_paths[] = $new_src;
$content = str_replace($original_src, $new_src, $content);
$content = str_replace(htmlentities($original_src), $new_src, $content);
}
return [$invoke_paths, $exclude_paths];
}
/**
* Replaces media oembed iframe URLs with an appropriate path.
*
* @param string $url
* A URL that may contain a media oembed (/media/eombed?url=...) path.
*
* @return string
* A modified URL.
*/
protected function replaceOembedUrl($url) {
$query = parse_url($url, PHP_URL_QUERY);
if (!empty($query) && preg_match('|.*/media/oembed\?.*|', $url)) {
$base_path = preg_replace('/\?.*/', '', $url);
$url = "$base_path/" . md5(urldecode($query));
}
return $url;
}
/**
* Replaces pager params in a URL with an appropriate path.
*
* @param string $url
* A URL that may contain a pager (?page) query param.
*
* @return string
* A modified URL.
*/
protected function replacePagerUrl($url) {
parse_str(parse_url($url, PHP_URL_QUERY), $query);
if ($query && isset($query['page']) && is_numeric($query['page'])) {
$base_path = preg_replace('/\?.*/', '', $url);
if ($query['page'] != 0) {
if ($base_path === '/') {
$base_path = '';
}
$url = $base_path . '/page/' . ($query['page'] + 1);
}
else {
$url = !empty($base_path) ? $base_path : '/';
}
}
return $url;
}
/**
* Finds and exports assets for the given HTML content.
*
......@@ -513,8 +412,9 @@ class StaticGenerator implements StaticGeneratorInterface {
* The destination.
*/
protected function getDestination($path) {
$path = $this->replacePagerUrl($path);
$path = $this->replaceOembedUrl($path);
$event = new ModifyDestinationEvent($path);
$this->eventDispatcher->dispatch(TomeStaticEvents::MODIFY_DESTINATION, $event);
$path = $event->getDestination();
$path = urldecode($path);
if (empty(pathinfo($path, PATHINFO_EXTENSION))) {
$path .= '/index.html';
......
......@@ -222,10 +222,9 @@ class StaticGeneratorTest extends TestBase {
}
/**
* @covers \Drupal\tome_static\StaticGenerator::requestPath
* @covers \Drupal\tome_static\StaticGenerator::replacePagerUrl
* @covers \Drupal\tome_static\StaticGenerator::handlePagers
* @covers \Drupal\tome_static\StaticGenerator::exportPaths
* @covers \Drupal\tome_static\EventSubscriber\PagerPathSubscriber::modifyDestination
* @covers \Drupal\tome_static\EventSubscriber\PagerPathSubscriber::modifyHtml
* @covers \Drupal\tome_static\EventSubscriber\PagerPathSubscriber::modifyUrl
*/
public function testPagers() {
$this->enableModules(['tome_test']);
......@@ -245,10 +244,9 @@ class StaticGeneratorTest extends TestBase {
}
/**
* @covers \Drupal\tome_static\StaticGenerator::requestPath
* @covers \Drupal\tome_static\StaticGenerator::replaceOembedUrl
* @covers \Drupal\tome_static\StaticGenerator::handleOembed
* @covers \Drupal\tome_static\StaticGenerator::exportPaths
* @covers \Drupal\tome_static\EventSubscriber\MediaOembedPathSubscriber::modifyDestination
* @covers \Drupal\tome_static\EventSubscriber\MediaOembedPathSubscriber::modifyHtml
* @covers \Drupal\tome_static\EventSubscriber\MediaOembedPathSubscriber::modifyUrl
*/
public function testOembed() {
$this->enableModules(['tome_test']);
......
......@@ -21,6 +21,14 @@ services:
class: Drupal\tome_static\EventSubscriber\ExcludePathSubscriber
tags:
- { name: event_subscriber }
tome_static.pager_path_subscriber:
class: Drupal\tome_static\EventSubscriber\PagerPathSubscriber
tags:
- { name: event_subscriber }
tome_static.media_oembed_path_subscriber:
class: Drupal\tome_static\EventSubscriber\MediaOembedPathSubscriber
tags:
- { name: event_subscriber }
tome_static.page_cache_request_policy:
class: Drupal\tome_static\PageCache\RequestPolicy\CoreRequestPolicy
decorates: page_cache_request_policy
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment