Skip to content

Commit

Permalink
1.6.0, check changelog
Browse files Browse the repository at this point in the history
  • Loading branch information
Rico Kaltofen committed Mar 26, 2019
1 parent e5b0479 commit ea1b30a
Show file tree
Hide file tree
Showing 7 changed files with 244 additions and 67 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
@@ -1,6 +1,12 @@
# Changelog
All notable changes to this project will be documented in this file.

## [1.6.0] - 2019-03-26

### Added
- `tl_page` hyphenation disable/enable/nesting handling in backend mask
- `tl_page.customLineBreakExceptions` and `tl_page.lineBreakExceptions` added in order to provide support for line break exception to keep words like company names together and prevent line break

## [1.5.1] - 2019-03-26

### Fixed
Expand Down
4 changes: 3 additions & 1 deletion README.md
Expand Up @@ -8,6 +8,8 @@
A contao bundle that grants server-side hyphenation (thanks to [vanderlee/phpSyllable](https://github.com/vanderlee/phpSyllable)).
It does support headlines and paragraphs by default.

This module also handles line break exceptions, in order to keep words like company names together and prevent line break (see `tl_page` backend entity).

## Options

To extend the functionality, all options can be adjusted within your localconfig.
Expand All @@ -23,7 +25,7 @@ hyphenator_enableCache | bool | true | Enable simple caching and do not hyphenat

## Skip hyphenation

If you want to skip several tags from hyphenation simply add `hyphen-none` as css-class to the appropriate element.
If you want to skip several tags from hyphenation simply add `hyphen-none` as css-class to the appropriate element or use the `tl_page.hyphenation` field.

## Requirements

Expand Down
194 changes: 130 additions & 64 deletions src/Hyphenator/FrontendHyphenator.php
Expand Up @@ -9,51 +9,35 @@
namespace HeimrichHannot\HyphenatorBundle\Hyphenator;

use Contao\Config;
use Contao\CoreBundle\Framework\ContaoFrameworkInterface;
use Contao\PageModel;
use Contao\StringUtil;
use Contao\System;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Vanderlee\Syllable\Syllable;
use Wa72\HtmlPageDom\HtmlPageCrawler;

class FrontendHyphenator
{
/**
* @var ContaoFrameworkInterface
* @var ContainerInterface
*/
private $framework;
private $container;

/**
* Request constructor.
*
* @param ContaoFrameworkInterface $framework
* @param ContainerInterface $container
*/
public function __construct(ContaoFrameworkInterface $framework)
public function __construct(ContainerInterface $container)
{
$this->framework = $framework;
$this->container = $container;
}

public function hyphenate($strBuffer)
{
/* @var PageModel $objPage */
global $objPage;

$arrSkipPages = Config::get('hyphenator_skipPages');

if (null === $objPage || \is_array($arrSkipPages) && \in_array($objPage->id, $arrSkipPages)) {
return $strBuffer;
}

Syllable::setCacheDir(System::getContainer()->getParameter('kernel.cache_dir'));

$language = $objPage->language;

if (isset($GLOBALS['TL_CONFIG']['hyphenator_locale_language_mapping'][$language])) {
$language = $GLOBALS['TL_CONFIG']['hyphenator_locale_language_mapping'][$language];
}

$h = new Syllable($language);
$h->setMinWordLength(Config::get('hyphenator_wordMin'));
$h->setHyphen(Config::get('hyphenator_hyphen'));

// mask esi tags, otherwise dom crawler will remove them
$strBuffer = preg_replace_callback(
'#<esi:((?!\/>).*)\s?\/>#sU',
Expand All @@ -63,61 +47,77 @@ function ($matches) {
$strBuffer
);

// prevent unescape unicode html entities (email obfuscation)
$strBuffer = preg_replace('/&(#+[x0-9a-fA-F]+);/', '&_$1;', $strBuffer);
if (!$this->isHyphenationDisabled($objPage, Config::get('hyphenator_skipPages'))) {
// prevent unescape unicode html entities (email obfuscation)
$strBuffer = preg_replace('/&(#+[x0-9a-fA-F]+);/', '&_$1;', $strBuffer);

$doc = HtmlPageCrawler::create($strBuffer);
$isHtmlDocument = $doc->isHtmlDocument();
Syllable::setCacheDir(System::getContainer()->getParameter('kernel.cache_dir'));

if (false === $isHtmlDocument) {
$doc = HtmlPageCrawler::create(sprintf('<div id="crawler-root">%s</div>', $strBuffer));
}
$language = $objPage->language;

$cacheEnabled = (bool) Config::get('hyphenator_enableCache');
$cache = [];
if (isset($GLOBALS['TL_CONFIG']['hyphenator_locale_language_mapping'][$language])) {
$language = $GLOBALS['TL_CONFIG']['hyphenator_locale_language_mapping'][$language];
}

$doc->filter(Config::get('hyphenator_tags'))->each(
function (HtmlPageCrawler $node, $i) use ($h, &$cache, $cacheEnabled) {
$clone = $node->makeClone(); // make a clone to prevent `Couldn't fetch DOMElement. Node no longer exists`
$html = $clone->html(); // restore nested inserttags that were replaced with %7B or %7D
$cacheKey = $html;
$h = new Syllable($language);
$h->setMinWordLength(Config::get('hyphenator_wordMin'));
$h->setHyphen(Config::get('hyphenator_hyphen'));

if (empty($html)) {
return $node;
}
$doc = HtmlPageCrawler::create($strBuffer);
$isHtmlDocument = $doc->isHtmlDocument();

if (true === $cacheEnabled && isset($cache[$cacheKey])) {
$clone->html(StringUtil::decodeEntities($cache[$cacheKey]));
$node->replaceWith($clone->saveHTML());
if (false === $isHtmlDocument) {
$doc = HtmlPageCrawler::create(sprintf('<div id="crawler-root">%s</div>', $strBuffer));
}

return $node;
}
$cacheEnabled = (bool) Config::get('hyphenator_enableCache');
$cache = [];

$html = str_replace('&shy;', '', $html); // remove manual &shy; html entities before
$doc->filter(Config::get('hyphenator_tags'))->each(
function (HtmlPageCrawler $node, $i) use ($h, &$cache, $cacheEnabled) {
$clone = $node->makeClone(); // make a clone to prevent `Couldn't fetch DOMElement. Node no longer exists`
$html = $clone->html(); // restore nested inserttags that were replaced with %7B or %7D
$cacheKey = $html;

// if html contains nested tags, use the hyphenateHtml that excludes HTML tags and attributes
libxml_use_internal_errors(true); // disable error reporting when potential using HTML5 tags
$html = $h->hyphenateHtml($html);
libxml_clear_errors();
if (empty($html)) {
return $node;
}

if (false === preg_match('#<body>(<p>)?(?<content>.+?)(<\/p>)?<\/body>#is', $html, $matches) || !isset($matches['content'])) {
return $node;
}
if (true === $cacheEnabled && isset($cache[$cacheKey])) {
$clone->html(StringUtil::decodeEntities($cache[$cacheKey]));
$node->replaceWith($clone->saveHTML());

$html = $matches['content'];
$clone->html(StringUtil::decodeEntities($html));
$node->replaceWith($clone->saveHTML());
return $node;
}

$cache[$cacheKey] = $html;
$html = str_replace('&shy;', '', $html); // remove manual &shy; html entities before

return $node;
}
);
// if html contains nested tags, use the hyphenateHtml that excludes HTML tags and attributes
libxml_use_internal_errors(true); // disable error reporting when potential using HTML5 tags
$html = $h->hyphenateHtml($html);
libxml_clear_errors();

$strBuffer = false === $isHtmlDocument ? $doc->filter('#crawler-root')->getInnerHtml() : $doc->saveHTML();
if (false === preg_match('#<body>(<p>)?(?<content>.+?)(<\/p>)?<\/body>#is', $html, $matches) || !isset($matches['content'])) {
return $node;
}

// prevent unescape unicode html entities (email obfuscation)
$strBuffer = preg_replace('/&amp;_(#+[x0-9a-fA-F]+);/', '&$1;', $strBuffer);
$html = $matches['content'];
$clone->html(StringUtil::decodeEntities($html));
$node->replaceWith($clone->saveHTML());

$cache[$cacheKey] = $html;

return $node;
}
);

$strBuffer = false === $isHtmlDocument ? $doc->filter('#crawler-root')->getInnerHtml() : $doc->saveHTML();

// prevent unescape unicode html entities (email obfuscation)
$strBuffer = preg_replace('/&amp;_(#+[x0-9a-fA-F]+);/', '&$1;', $strBuffer);
}

$strBuffer = $this->handleLineBreakExceptions($strBuffer, $objPage);

$strBuffer = preg_replace_callback(
'/####esi:open####(.*)####esi:close####/',
Expand All @@ -129,4 +129,70 @@ function ($matches) {

return $strBuffer;
}

/**
* Determine if hyphenation is enabled within current page scope.
*
* @param PageModel $page
* @param array $skipPageIds
*
* @return bool
*/
protected function isHyphenationDisabled($page = null, array $skipPageIds = []): bool
{
if (null === $page) {
return true;
}

if (\is_array($skipPageIds) && \in_array($page->id, $skipPageIds)) {
return true;
}

if ('inactive' === $page->hyphenation) {
return true;
}

if ('active' === $page->hyphenation) {
return true;
}

if ($page->pid && null !== ($parent = $this->container->get('huh.utils.model')->findModelInstanceByPk('tl_page', $page->pid))) {
return $this->isHyphenationDisabled($parent);
}

return false;
}

/**
* Handle line break exceptions.
*
* @param string $buffer
* @param null $page
*
* @return string
*/
protected function handleLineBreakExceptions(string $buffer, $page = null): string
{
if (true === (bool) $page->customLineBreakExceptions) {
$exceptions = StringUtil::deserialize($page->lineBreakExceptions, true);

foreach ($exceptions as $exception) {
if (!isset($exception['search']) || empty($exception['search'])) {
continue;
}

$search = '#('.$exception['search'].')#';
$replace = implode('&nbsp;', explode(' ', $exception['search']));

$buffer = preg_replace($search, $replace, $buffer);
}
}

// always handle root page exceptions
if ($page->rootId && $page->id !== $page->rootId && (null !== ($root = $this->container->get('huh.utils.model')->findModelInstanceByPk('tl_page', $page->rootId)))) {
$buffer = $this->handleLineBreakExceptions($buffer, $root);
}

return $buffer;
}
}
3 changes: 1 addition & 2 deletions src/Resources/config/services.yml
Expand Up @@ -2,5 +2,4 @@ services:
huh.hyphenator.frontend:
class: HeimrichHannot\HyphenatorBundle\Hyphenator\FrontendHyphenator
public: true
arguments:
- '@contao.framework'
autowire: true
59 changes: 59 additions & 0 deletions src/Resources/contao/dca/tl_page.php
@@ -0,0 +1,59 @@
<?php

$dc = &$GLOBALS['TL_DCA']['tl_page'];

/**
* Selectors
*/
$dc['palettes']['__selector__'][] = 'customLineBreakExceptions';


/**
* Palettes
*/
$dc['palettes']['root'] = str_replace('{layout_legend', '{hyphenator_legend},hyphenation,customLineBreakExceptions;{layout_legend', $dc['palettes']['root']);
$dc['palettes']['regular'] = str_replace('{layout_legend', '{hyphenator_legend},hyphenation,customLineBreakExceptions;{layout_legend', $dc['palettes']['regular']);

/**
* Subpalettes
*/
$dc['subpalettes']['customLineBreakExceptions'] = 'lineBreakExceptions';

$fields = [
'hyphenation' => [
'label' => &$GLOBALS['TL_LANG']['tl_page']['hyphenation'],
'inputType' => 'select',
'default' => 'inactive',
'options' => ['active', 'inactive'],
'reference' => $GLOBALS['TL_LANG']['tl_page']['reference']['hyphenation'],
'eval' => ['includeBlankOption' => true, 'tl_class' => 'w50'],
'sql' => "char(8) NOT NULL default ''",
],
'customLineBreakExceptions' => [
'label' => &$GLOBALS['TL_LANG']['tl_page']['customLineBreakExceptions'],
'exclude' => true,
'inputType' => 'checkbox',
'eval' => ['submitOnChange' => true, 'tl_class' => 'clr'],
'sql' => "char(1) NOT NULL default ''",
],
'lineBreakExceptions' => [
'label' => &$GLOBALS['TL_LANG']['tl_page']['lineBreakExceptions'],
'inputType' => 'multiColumnEditor',
'eval' => [
'tl_class' => 'long clr',
'multiColumnEditor' => [
'fields' => [
'search' => [
'label' => &$GLOBALS['TL_LANG']['tl_page']['lineBreakExceptions_search'],
'exclude' => true,
'inputType' => 'text',
'eval' => ['tl_class' => 'w50', 'mandatory' => true, 'groupStyle' => 'width: 80%;'],
],
],
],
],
'sql' => "blob NULL"
]
];

$dc['fields'] = array_merge($dc['fields'], $fields);
23 changes: 23 additions & 0 deletions src/Resources/contao/languages/de/tl_page.php
@@ -0,0 +1,23 @@
<?php

$lang = &$GLOBALS['TL_LANG']['tl_page'];

/**
* Fields
*/
$lang['hyphenation'] = ['Silbentrennung', 'Silbentrennung auf dieser Seite aktivieren, deaktivieren oder leer lassen für die Verwendung der Vererbung von den übergeordneten Seiten.'];
$lang['customLineBreakExceptions'] = ['Benutzerdefinierte Ausnahmen für Zeilenumbrüche hinzufügen', 'Fügen Sie benutzerdefinierte Ausnahmen für Zeilenumbrüche hinzu, um Wörter zusammenzuhalten.'];
$lang['lineBreakExceptions'] = ['Ausnahmen für Zeilenumbrüche', 'Hinzufügen von Leerraum-Suchmustern (auch reguläre Ausdrücke zulässig) um einen Zeilenumbruch zwischen diesen Wörtern zu verhindern.'];
$lang['lineBreakExceptions_search'] = ['Suchmuster', 'Vergeben Sie ein Suchmuster (reguläre sind Ausdrücke erlaubt). Beispiel: "Meine Firma GmbH", (\s\w{1})(\s)'];

/**
* Legends
*/
$lang['hyphenator_legend'] = 'Silbentrennung & Zeilenumbrüche';


/**
* References
*/
$lang['reference']['hyphenation']['active'] = 'Aktiviert';
$lang['reference']['hyphenation']['inactive'] = 'Deaktiviert';
22 changes: 22 additions & 0 deletions src/Resources/contao/languages/en/tl_page.php
@@ -0,0 +1,22 @@
<?php

$lang = &$GLOBALS['TL_LANG']['tl_page'];

/**
* Fields
*/
$lang['hyphenation'] = ['Hyphenation', 'Enable, disable, or leave blank for inheritance use by the parent pages.'];
$lang['customLineBreakExceptions'] = ['Custom exceptions for line breaks', 'Add custom exceptions for line breaks, to keep words together.'];
$lang['lineBreakExceptions'] = ['Line break exceptions', 'Add white space search patterns (also regular expressions allowed) to prevent a line break between these words.'];
$lang['lineBreakExceptions_search'] = ['Search pattern', 'Assign a search pattern (regular expressions allowed). Beispiel: "My Company GmbH", (\s\w{1})(\s)'];

/**
* Legends
*/
$lang['hyphenator_legend'] = 'Hyphenation & line breaks';

/**
* References
*/
$lang['reference']['hyphenation']['active'] = 'Enabled';
$lang['reference']['hyphenation']['inactive'] = 'Disabled';

0 comments on commit ea1b30a

Please sign in to comment.