Skip to content

Commit

Permalink
Merge pull request #23 from sitegeist/task/addTestsAndAdjustService
Browse files Browse the repository at this point in the history
TASK: Add tests and basic ci qs
  • Loading branch information
jonnitto authored Feb 8, 2024
2 parents 70f94fb + d263f43 commit 88bde49
Show file tree
Hide file tree
Showing 6 changed files with 499 additions and 78 deletions.
40 changes: 40 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: build

on:
push:
branches:
- 'main'
pull_request: ~

jobs:
test:
name: "Test (PHP ${{ matrix.php-versions }}, Flow ${{ matrix.flow-versions }})"

strategy:
fail-fast: false
matrix:
php-versions: ['8.0', '8.1']
flow-versions: ['8.3']
include:
- php-versions: '8.0'
flow-versions: '8.0'
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v2
with:
path: ${{ env.FLOW_FOLDER }}

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
extensions: mbstring, xml, json, zlib, iconv, intl, pdo_sqlite
ini-values: date.timezone="Africa/Tunis", opcache.fast_shutdown=0, apc.enable_cli=on

- name: Set Flow Version
run: composer require neos/flow ^${{ matrix.flow-versions }} --no-progress --no-interaction

- name: Run Tests
run: composer test
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
composer.lock
Packages
vendor
213 changes: 136 additions & 77 deletions Classes/Service/SlipStreamService.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

namespace Sitegeist\Slipstream\Service;

use Neos\Flow\Annotations as Flow;
Expand All @@ -7,7 +8,6 @@

class SlipStreamService
{

/**
* @var bool
* @Flow\InjectConfiguration(path="debugMode")
Expand All @@ -25,6 +25,8 @@ class SlipStreamService

protected const AT_CHARACTER_SEARCH = ' @';

protected const HTML_DOCTYPE = '<!DOCTYPE html>';

/**
* Modify the given response and return a new one with the data-slipstream elements moved to
* the target location
Expand All @@ -36,12 +38,36 @@ public function processResponse(ResponseInterface $response): ResponseInterface
{
$html = $response->getBody()->getContents();

$alteredHtml = $this->processHtml($html);

if (is_null($alteredHtml)) {
$response->getBody()->rewind();
return $response;
}

$response = $response->withBody(Utils::streamFor($alteredHtml));
if (!$this->debugMode) {
$response = $response->withoutHeader('X-Slipstream');
}

return $response;
}

public function processHtml(string $html): ?string
{
if (!str_contains($html, 'data-slipstream')) {
return null;
}

// Starting with Neos 7.3 it is possible to have attributes with @ (e.g. @click).
// This replacement preserves attributes with @ character
$html = str_replace(self::AT_CHARACTER_SEARCH, self::AT_CHARACTER_REPLACEMENT, $html);

// detect xml or html declaration
$hasXmlDeclaration = (substr($html, 0, 5) === '<?xml') || (substr($html, 0, 15) === '<!DOCTYPE html>');
// detect html doctype
$hasHtmlDoctype = substr($html, 0, 15) === self::HTML_DOCTYPE;

// detect xml declaration
$hasXmlDeclaration = substr($html, 0, 5) === '<?xml';

// ignore xml parsing errors
$useInternalErrorsBackup = libxml_use_internal_errors(true);
Expand All @@ -51,113 +77,146 @@ public function processResponse(ResponseInterface $response): ResponseInterface

// in case of parsing errors return original body
if (!$success) {
$response->getBody()->rewind();
return $response;
if ($useInternalErrorsBackup !== true) {
libxml_use_internal_errors($useInternalErrorsBackup);
}
return null;
}

$xPath = new \DOMXPath($domDocument);

$sourceNodes = $xPath->query("//*[@data-slipstream]");
$nodesByTargetAndContentHash = [];
foreach ($sourceNodes as $node) {
if ($sourceNodes instanceof \DOMNodeList) {
$nodesByTargetAndContentHash = [];

/**
* @var \DOMNode $node
* @var \DOMElement $node
*/
$content = $domDocument->saveHTML($node);
$target = $node->getAttribute('data-slipstream');
if (empty($target)) {
$target = '//head';
}
foreach ($sourceNodes as $node) {
/**
* @var string $content
*/
$content = $domDocument->saveHTML($node);
$target = $node->getAttribute('data-slipstream');
if (empty($target)) {
$target = '//head';
}

$prepend = $node->hasAttribute('data-slipstream-prepend');
$contentHash = md5($content);
$clone = $node->cloneNode(true);
if ($this->removeAttributes) {
$clone->removeAttribute('data-slipstream');
$clone->removeAttribute('data-slipstream-prepend');
}
$nodesByTargetAndContentHash[$target][$contentHash] = [
'prepend' => $prepend,
'node' => $clone
];

// in debug mode leave a comment behind
if ($this->debugMode) {
$comment = $domDocument->createComment(' ' . $content . ' ');
$node->parentNode->insertBefore($comment, $node);
$prepend = $node->hasAttribute('data-slipstream-prepend');
$contentHash = md5($content);

/**
* @var \DOMElement $clone
*/
$clone = $node->cloneNode(true);
if ($this->removeAttributes) {
$clone->removeAttribute('data-slipstream');
$clone->removeAttribute('data-slipstream-prepend');
}
$nodesByTargetAndContentHash[$target][$contentHash] = [
'prepend' => $prepend,
'node' => $clone
];

/**
* @var \DOMElement $parentNode
*/
$parentNode = $node->parentNode;
// in debug mode leave a comment behind
if ($this->debugMode) {
$comment = $domDocument->createComment(' ' . $content . ' ');
$parentNode->insertBefore($comment, $node);
}

$parentNode->removeChild($node);
}

$node->parentNode->removeChild($node);
}

foreach ($nodesByTargetAndContentHash as $targetPath => $configurations) {
$query = $xPath->query($targetPath);
if ($query && $query->count()) {
$targetNode = $query->item(0);
foreach ($nodesByTargetAndContentHash as $targetPath => $configurations) {
$query = $xPath->query($targetPath);
if ($query && $query->count()) {
/**
* @var \DOMElement $targetNode
*/
$targetNode = $query->item(0);

$prepend = [];
$append = [];
foreach ($configurations as $config) {
if ($config['prepend']) {
$prepend[] = $config['node'];
} else {
$append[] = $config['node'];
}
}
$hasPrepend = count($prepend);
$hasAppend = count($append);

$prepend = [];
$append = [];
foreach ($configurations as $config) {
if ($config['prepend']) {
$prepend[] = $config['node'];
if ($hasPrepend) {
$nodeToInsertBefore = $targetNode->firstChild;
} else {
$append[] = $config['node'];
$nodeToInsertBefore = null;
}
}
$hasPrepend = count($prepend);
$hasAppend = count($append);

if ($hasPrepend) {
$firstChildNode = $targetNode->firstChild;
}
// start comment
if ($this->debugMode) {
if ($hasPrepend) {
if ($nodeToInsertBefore) {
$targetNode->insertBefore($domDocument->createComment('slipstream-for: ' . $targetPath . ' prepend begin'), $nodeToInsertBefore);
} else {
$targetNode->appendChild($domDocument->createComment('slipstream-for: ' . $targetPath . ' prepend begin'));
}
}
if ($hasAppend) {
$targetNode->appendChild($domDocument->createComment('slipstream-for: ' . $targetPath . ' begin'));
}
}

if ($this->debugMode) {
$comment = 'slipstream-for: ' . $targetPath . ' ';
if ($hasPrepend) {
$targetNode->insertBefore($domDocument->createComment($comment . 'prepend begin'), $firstChildNode);
foreach ($prepend as $node) {
if ($nodeToInsertBefore) {
$targetNode->insertBefore($node, $nodeToInsertBefore);
} else {
$targetNode->appendChild($node);
}
}
if ($hasAppend) {
$targetNode->appendChild($domDocument->createComment($comment . 'begin'));
foreach ($append as $node) {
$targetNode->appendChild($node);
}
}

foreach ($prepend as $node) {
$targetNode->insertBefore($node, $firstChildNode);
}
foreach ($append as $node) {
$targetNode->appendChild($node);
}

if ($this->debugMode) {
if ($hasPrepend) {
$targetNode->insertBefore($domDocument->createComment($comment . 'prepend end'), $firstChildNode);
}
if ($hasAppend) {
$targetNode->appendChild($domDocument->createComment($comment . 'end'));
// end comment
if ($this->debugMode) {
if ($hasPrepend) {
if ($nodeToInsertBefore) {
$targetNode->insertBefore($domDocument->createComment('slipstream-for: ' . $targetPath . ' prepend end'), $nodeToInsertBefore);
} else {
$targetNode->appendChild($domDocument->createComment('slipstream-for: ' . $targetPath . ' prepend end'));
}
}
if ($hasAppend) {
$targetNode->appendChild($domDocument->createComment('slipstream-for: ' . $targetPath . ' end'));
}
}
}
}
}

if ($hasXmlDeclaration) {
$alteredBody = $domDocument->saveHTML();
$alteredHtml = $domDocument->saveHTML();
} else {
$alteredBody = $domDocument->saveHTML($domDocument->documentElement);
$alteredHtml = $domDocument->saveHTML($domDocument->documentElement);
}

// Replace the interal @ character with the original one
$alteredBody = str_replace(self::AT_CHARACTER_REPLACEMENT, self::AT_CHARACTER_SEARCH, $alteredBody);

$response = $response->withBody(Utils::streamFor($alteredBody));
if (!$this->debugMode) {
$response = $response->withoutHeader('X-Slipstream');
if ($alteredHtml === false) {
return null;
}

// restore previous parsing behavior
// Replace the interal @ character with the original one
$alteredHtml = str_replace(self::AT_CHARACTER_REPLACEMENT, self::AT_CHARACTER_SEARCH, $alteredHtml);

if ($useInternalErrorsBackup !== true) {
libxml_use_internal_errors($useInternalErrorsBackup);
}

return $response;
return $hasHtmlDoctype ? self::HTML_DOCTYPE . $alteredHtml : $alteredHtml ;
}
}
Loading

0 comments on commit 88bde49

Please sign in to comment.