diff --git a/eZ/Bundle/EzPublishCoreBundle/DependencyInjection/Configuration.php b/eZ/Bundle/EzPublishCoreBundle/DependencyInjection/Configuration.php
index 6ab29975a75..4289dd3ee03 100644
--- a/eZ/Bundle/EzPublishCoreBundle/DependencyInjection/Configuration.php
+++ b/eZ/Bundle/EzPublishCoreBundle/DependencyInjection/Configuration.php
@@ -479,9 +479,10 @@ private function addRouterSection(ArrayNodeDefinition $rootNode)
*/
private function addRichTextSection(ArrayNodeDefinition $rootNode)
{
- $this->addCustomTagsSection(
- $rootNode->children()->arrayNode('ezrichtext')->children()
- )->end()->end()->end();
+ $ezRichTextNode = $rootNode->children()->arrayNode('ezrichtext')->children();
+ $this->addCustomTagsSection($ezRichTextNode);
+ $this->addCustomStylesSection($ezRichTextNode);
+ $ezRichTextNode->end()->end()->end();
}
/**
@@ -571,6 +572,40 @@ function ($v) {
;
}
+ /**
+ * Define RichText Custom Styles Semantic Configuration.
+ *
+ * The configuration is available at:
+ *
+ * ezpublish:
+ * ezrichtext:
+ * custom_styles:
+ *
+ *
+ * @param \Symfony\Component\Config\Definition\Builder\NodeBuilder $ezRichTextNode
+ *
+ * @return \Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition
+ */
+ private function addCustomStylesSection(NodeBuilder $ezRichTextNode)
+ {
+ return $ezRichTextNode
+ ->arrayNode('custom_styles')
+ // workaround: take into account Custom Styles names when merging configs
+ ->useAttributeAsKey('style')
+ ->arrayPrototype()
+ ->children()
+ ->scalarNode('template')
+ ->defaultNull()
+ ->end()
+ ->scalarNode('inline')
+ ->defaultFalse()
+ ->end()
+ ->end()
+ ->end()
+ ->end()
+ ;
+ }
+
/**
* Defines configuration the images placeholder generation.
*
diff --git a/eZ/Bundle/EzPublishCoreBundle/DependencyInjection/Configuration/Parser/FieldType/RichText.php b/eZ/Bundle/EzPublishCoreBundle/DependencyInjection/Configuration/Parser/FieldType/RichText.php
index a45d04ea0ac..21003c38746 100644
--- a/eZ/Bundle/EzPublishCoreBundle/DependencyInjection/Configuration/Parser/FieldType/RichText.php
+++ b/eZ/Bundle/EzPublishCoreBundle/DependencyInjection/Configuration/Parser/FieldType/RichText.php
@@ -167,6 +167,13 @@ public function addFieldTypeSemanticConfig(NodeBuilder $nodeBuilder)
->info('List of RichText Custom Tags enabled for the current scope. The Custom Tags must be defined in ezpublish.ezrichtext.custom_tags Node.')
->scalarPrototype()->end()
->end();
+
+ // RichText Custom Styles configuration (list of Custom Styles enabled for current SiteAccess scope)
+ $nodeBuilder
+ ->arrayNode('custom_styles')
+ ->info('List of RichText Custom Styles enabled for the current scope. The Custom Styles must be defined in ezpublish.ezrichtext.custom_styles Node.')
+ ->scalarPrototype()->end()
+ ->end();
}
/**
@@ -217,6 +224,17 @@ public function mapConfig(array &$scopeSettings, $currentScope, ContextualizerIn
$scopeSettings['fieldtypes']['ezrichtext']['custom_tags']
);
}
+ if (isset($scopeSettings['fieldtypes']['ezrichtext']['custom_styles'])) {
+ $this->validateCustomStylesConfiguration(
+ $contextualizer->getContainer(),
+ $scopeSettings['fieldtypes']['ezrichtext']['custom_styles']
+ );
+ $contextualizer->setContextualParameter(
+ 'fieldtypes.ezrichtext.custom_styles',
+ $currentScope,
+ $scopeSettings['fieldtypes']['ezrichtext']['custom_styles']
+ );
+ }
if (isset($scopeSettings['fieldtypes']['ezrichtext']['tags'])) {
foreach ($scopeSettings['fieldtypes']['ezrichtext']['tags'] as $name => $tagSettings) {
@@ -269,6 +287,28 @@ private function validateCustomTagsConfiguration(
}
}
+ /**
+ * Validate SiteAccess-defined Custom Styles configuration against global one.
+ *
+ * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
+ * @param array $enabledCustomStyles List of Custom Styles enabled for the current scope/SiteAccess
+ */
+ private function validateCustomStylesConfiguration(
+ ContainerInterface $container,
+ array $enabledCustomStyles
+ ) {
+ $definedCustomStyles = array_keys(
+ $container->getParameter(EzPublishCoreExtension::RICHTEXT_CUSTOM_STYLES_PARAMETER)
+ );
+ foreach ($enabledCustomStyles as $customStyleName) {
+ if (!in_array($customStyleName, $definedCustomStyles)) {
+ throw new InvalidConfigurationException(
+ "Unknown RichText Custom Style '{$customStyleName}'"
+ );
+ }
+ }
+ }
+
/**
* Add BC setup for deprecated configuration.
*
diff --git a/eZ/Bundle/EzPublishCoreBundle/DependencyInjection/EzPublishCoreExtension.php b/eZ/Bundle/EzPublishCoreBundle/DependencyInjection/EzPublishCoreExtension.php
index 04f5a32a503..0ababa30901 100644
--- a/eZ/Bundle/EzPublishCoreBundle/DependencyInjection/EzPublishCoreExtension.php
+++ b/eZ/Bundle/EzPublishCoreBundle/DependencyInjection/EzPublishCoreExtension.php
@@ -27,6 +27,7 @@
class EzPublishCoreExtension extends Extension
{
+ const RICHTEXT_CUSTOM_STYLES_PARAMETER = 'ezplatform.ezrichtext.custom_styles';
const RICHTEXT_CUSTOM_TAGS_PARAMETER = 'ezplatform.ezrichtext.custom_tags';
/**
@@ -297,6 +298,12 @@ private function registerRichTextConfiguration(array $config, ContainerBuilder $
$config['ezrichtext']['custom_tags']
);
}
+ if (isset($config['ezrichtext']['custom_styles'])) {
+ $container->setParameter(
+ static::RICHTEXT_CUSTOM_STYLES_PARAMETER,
+ $config['ezrichtext']['custom_styles']
+ );
+ }
}
/**
diff --git a/eZ/Bundle/EzPublishCoreBundle/Resources/config/default_settings.yml b/eZ/Bundle/EzPublishCoreBundle/Resources/config/default_settings.yml
index 72ceb60c579..97112cb9c4b 100644
--- a/eZ/Bundle/EzPublishCoreBundle/Resources/config/default_settings.yml
+++ b/eZ/Bundle/EzPublishCoreBundle/Resources/config/default_settings.yml
@@ -26,6 +26,11 @@ parameters:
# Rich Text Custom Tags default scope (for SiteAccess) configuration
ezsettings.default.fieldtypes.ezrichtext.custom_tags: []
+ # Rich Text Custom Styles global configuration
+ ezplatform.ezrichtext.custom_styles: {}
+ # Rich Text Custom Styles default scope (for SiteAccess) configuration
+ ezsettings.default.fieldtypes.ezrichtext.custom_styles: []
+
# Image Asset mappings
ezsettings.default.fieldtypes.ezimageasset.mappings:
content_type_identifier: image
@@ -129,6 +134,13 @@ parameters:
ezsettings.default.fieldtypes.ezrichtext.tags.default_inline:
template: EzPublishCoreBundle:FieldType/RichText/tag:default_inline.html.twig
+ # RichText field type template style settings
+ # 'default' and 'default_inline' tag identifiers are reserved for fallback
+ ezsettings.default.fieldtypes.ezrichtext.styles.default:
+ template: EzPublishCoreBundle:FieldType/RichText/style:default.html.twig
+ ezsettings.default.fieldtypes.ezrichtext.styles.default_inline:
+ template: EzPublishCoreBundle:FieldType/RichText/style:default_inline.html.twig
+
# RichText field type embed settings
ezsettings.default.fieldtypes.ezrichtext.embed.content:
template: EzPublishCoreBundle:FieldType/RichText/embed:content.html.twig
diff --git a/eZ/Bundle/EzPublishCoreBundle/Resources/config/fieldtype_services.yml b/eZ/Bundle/EzPublishCoreBundle/Resources/config/fieldtype_services.yml
index ecec706a5cb..1bde162a026 100644
--- a/eZ/Bundle/EzPublishCoreBundle/Resources/config/fieldtype_services.yml
+++ b/eZ/Bundle/EzPublishCoreBundle/Resources/config/fieldtype_services.yml
@@ -21,9 +21,11 @@ parameters:
ezpublish.fieldType.ezrichtext.converter.link.class: eZ\Publish\Core\FieldType\RichText\Converter\Link
ezpublish.fieldType.ezrichtext.converter.embed.class: eZ\Publish\Core\FieldType\RichText\Converter\Render\Embed
ezpublish.fieldType.ezrichtext.converter.template.class: eZ\Publish\Core\FieldType\RichText\Converter\Render\Template
+ ezpublish.fieldType.ezrichtext.converter.style.class: eZ\Publish\Core\FieldType\RichText\Converter\Render\Style
ezpublish.fieldType.ezrichtext.embed_renderer.class: eZ\Publish\Core\MVC\Symfony\FieldType\RichText\EmbedRenderer
ezpublish.fieldType.ezrichtext.renderer.class: eZ\Publish\Core\MVC\Symfony\FieldType\RichText\Renderer
ezpublish.fieldType.ezrichtext.tag.namespace: fieldtypes.ezrichtext.tags
+ ezpublish.fieldType.ezrichtext.style.namespace: fieldtypes.ezrichtext.styles
ezpublish.fieldType.ezrichtext.embed.namespace: fieldtypes.ezrichtext.embed
ezpublish.fieldType.ezrichtext.converter.dispatcher.class: eZ\Publish\Core\FieldType\RichText\ConverterDispatcher
ezpublish.fieldType.ezrichtext.validator.xml.class: eZ\Publish\Core\FieldType\RichText\Validator
@@ -152,9 +154,19 @@ services:
- "@ezpublish.config.resolver"
- "@templating"
- "%ezpublish.fieldType.ezrichtext.tag.namespace%"
+ - "%ezpublish.fieldType.ezrichtext.style.namespace%"
- "%ezpublish.fieldType.ezrichtext.embed.namespace%"
- "@?logger"
- "%ezplatform.ezrichtext.custom_tags%"
+ - "%ezplatform.ezrichtext.custom_styles%"
+
+ ezpublish.fieldType.ezrichtext.converter.style:
+ class: "%ezpublish.fieldType.ezrichtext.converter.style.class%"
+ arguments:
+ - "@ezpublish.fieldType.ezrichtext.renderer"
+ - "@ezpublish.fieldType.ezrichtext.converter.output.xhtml5"
+ tags:
+ - {name: ezpublish.ezrichtext.converter.output.xhtml5, priority: 10}
ezpublish.fieldType.ezrichtext.converter.template:
class: "%ezpublish.fieldType.ezrichtext.converter.template.class%"
diff --git a/eZ/Bundle/EzPublishCoreBundle/Resources/views/FieldType/RichText/style/default.html.twig b/eZ/Bundle/EzPublishCoreBundle/Resources/views/FieldType/RichText/style/default.html.twig
new file mode 100644
index 00000000000..5a6b7813cee
--- /dev/null
+++ b/eZ/Bundle/EzPublishCoreBundle/Resources/views/FieldType/RichText/style/default.html.twig
@@ -0,0 +1 @@
+
{% spaceless %}{{ content|raw }}{% endspaceless %}
diff --git a/eZ/Bundle/EzPublishCoreBundle/Resources/views/FieldType/RichText/style/default_inline.html.twig b/eZ/Bundle/EzPublishCoreBundle/Resources/views/FieldType/RichText/style/default_inline.html.twig
new file mode 100644
index 00000000000..3ce95188ab4
--- /dev/null
+++ b/eZ/Bundle/EzPublishCoreBundle/Resources/views/FieldType/RichText/style/default_inline.html.twig
@@ -0,0 +1 @@
+{% spaceless %}{{ content|raw }}{% endspaceless %}
diff --git a/eZ/Publish/Core/FieldType/RichText/Converter/Render/Style.php b/eZ/Publish/Core/FieldType/RichText/Converter/Render/Style.php
new file mode 100644
index 00000000000..a673a00e61d
--- /dev/null
+++ b/eZ/Publish/Core/FieldType/RichText/Converter/Render/Style.php
@@ -0,0 +1,177 @@
+richTextConverter = $richTextConverter;
+ parent::__construct($renderer);
+ }
+
+ /**
+ * Injects rendered payloads into Custom Style elements.
+ *
+ * @param \DOMDocument $document
+ *
+ * @return \DOMDocument
+ */
+ public function convert(DOMDocument $document)
+ {
+ $xpath = new DOMXPath($document);
+ $xpath->registerNamespace('docbook', 'http://docbook.org/ns/docbook');
+ $xpathExpression = '//docbook:ezstyle | //docbook:ezstyleinline';
+
+ $styles = $xpath->query($xpathExpression);
+ /** @var \DOMElement[] $stylesSorted */
+ $stylesSorted = [];
+ $maxDepth = 0;
+
+ foreach ($styles as $style) {
+ $depth = $this->getNodeDepth($style);
+ if ($depth > $maxDepth) {
+ $maxDepth = $depth;
+ }
+ $stylesSorted[$depth][] = $style;
+ }
+
+ ksort($stylesSorted, SORT_NUMERIC);
+ foreach ($stylesSorted as $styles) {
+ foreach ($styles as $style) {
+ $this->processStyle($document, $style);
+ }
+ }
+
+ return $document;
+ }
+
+ /**
+ * Processes given template $style in a given $document.
+ *
+ * @param \DOMDocument $document
+ * @param \DOMElement $style
+ */
+ protected function processStyle(DOMDocument $document, DOMElement $style)
+ {
+ $content = null;
+ $styleName = $style->getAttribute('name');
+ $parameters = [
+ 'name' => $styleName,
+ 'content' => $this->saveNodeXML($style),
+ ];
+
+ if ($style->hasAttribute('ezxhtml:align')) {
+ $parameters['align'] = $style->getAttribute('ezxhtml:align');
+ }
+
+ $content = $this->renderer->renderStyle(
+ $styleName,
+ $parameters,
+ $style->localName === 'ezstyleinline'
+ );
+
+ if (isset($content)) {
+ // If current tag is wrapped inside another Custom Style tag we can't use CDATA section
+ // for its content as these can't be nested.
+ // CDATA section will be used only for content of root wrapping tag, content of tags
+ // inside it will be added as XML fragments.
+ if ($this->isWrapped($style)) {
+ $fragment = $document->createDocumentFragment();
+ $fragment->appendXML($content);
+ $style->parentNode->replaceChild($fragment, $style);
+ } else {
+ $payload = $document->createElement('ezpayload');
+ $payload->appendChild($document->createCDATASection($content));
+ $style->appendChild($payload);
+ }
+ }
+ }
+
+ /**
+ * Returns if the given $node is wrapped inside another template node.
+ *
+ * @param \DOMNode $node
+ *
+ * @return bool
+ */
+ protected function isWrapped(DomNode $node)
+ {
+ while ($node = $node->parentNode) {
+ if ($node->localName === 'ezstyle' || $node->localName === 'ezstyleinline') {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns depth of given $node in a DOMDocument.
+ *
+ * @param \DOMNode $node
+ *
+ * @return int
+ */
+ protected function getNodeDepth(DomNode $node)
+ {
+ // initial depth for top level elements (to avoid "ifs")
+ $depth = -2;
+
+ while ($node) {
+ ++$depth;
+ $node = $node->parentNode;
+ }
+
+ return $depth;
+ }
+
+ /**
+ * Returns XML fragment string for given $node.
+ *
+ * @param \DOMNode $node
+ *
+ * @return string
+ */
+ protected function saveNodeXML(DOMNode $node)
+ {
+ $innerDoc = new DOMDocument();
+
+ /** @var \DOMNode $child */
+ foreach ($node->childNodes as $child) {
+ $innerDoc->appendChild($innerDoc->importNode($child, true));
+ }
+
+ $convertedInnerDoc = $this->richTextConverter->convert($innerDoc);
+
+ return trim($convertedInnerDoc ? $convertedInnerDoc->saveHTML() : $innerDoc->saveHTML());
+ }
+}
diff --git a/eZ/Publish/Core/FieldType/RichText/RendererInterface.php b/eZ/Publish/Core/FieldType/RichText/RendererInterface.php
index eae9cebcead..d79f9ed109f 100644
--- a/eZ/Publish/Core/FieldType/RichText/RendererInterface.php
+++ b/eZ/Publish/Core/FieldType/RichText/RendererInterface.php
@@ -13,6 +13,17 @@
*/
interface RendererInterface
{
+ /**
+ * Renders template style.
+ *
+ * @param string $name
+ * @param array $parameters
+ * @param bool $isInline
+ *
+ * @return string
+ */
+ public function renderStyle($name, array $parameters, $isInline);
+
/**
* Renders template tag.
*
diff --git a/eZ/Publish/Core/FieldType/RichText/Resources/schemas/docbook/ezpublish.rng b/eZ/Publish/Core/FieldType/RichText/Resources/schemas/docbook/ezpublish.rng
index a107d59f598..010683ed056 100644
--- a/eZ/Publish/Core/FieldType/RichText/Resources/schemas/docbook/ezpublish.rng
+++ b/eZ/Publish/Core/FieldType/RichText/Resources/schemas/docbook/ezpublish.rng
@@ -610,6 +610,61 @@
+
+
+ eZ Publish custom styles
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ [A-Za-z][A-Za-z0-9_\-]*
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -777,6 +832,7 @@
+
@@ -796,6 +852,7 @@
+
diff --git a/eZ/Publish/Core/FieldType/RichText/Resources/stylesheets/docbook/xhtml5/edit/core.xsl b/eZ/Publish/Core/FieldType/RichText/Resources/stylesheets/docbook/xhtml5/edit/core.xsl
index 24981f3495c..400ebe3ce40 100644
--- a/eZ/Publish/Core/FieldType/RichText/Resources/stylesheets/docbook/xhtml5/edit/core.xsl
+++ b/eZ/Publish/Core/FieldType/RichText/Resources/stylesheets/docbook/xhtml5/edit/core.xsl
@@ -627,6 +627,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/eZ/Publish/Core/FieldType/RichText/Resources/stylesheets/docbook/xhtml5/output/core.xsl b/eZ/Publish/Core/FieldType/RichText/Resources/stylesheets/docbook/xhtml5/output/core.xsl
index bffecfeac64..08ef3bd7e86 100644
--- a/eZ/Publish/Core/FieldType/RichText/Resources/stylesheets/docbook/xhtml5/output/core.xsl
+++ b/eZ/Publish/Core/FieldType/RichText/Resources/stylesheets/docbook/xhtml5/output/core.xsl
@@ -505,6 +505,12 @@
+
+
+
+
+
+
diff --git a/eZ/Publish/Core/FieldType/RichText/Resources/stylesheets/xhtml5/edit/docbook.xsl b/eZ/Publish/Core/FieldType/RichText/Resources/stylesheets/xhtml5/edit/docbook.xsl
index c06992c6450..9d4689dd2fc 100644
--- a/eZ/Publish/Core/FieldType/RichText/Resources/stylesheets/xhtml5/edit/docbook.xsl
+++ b/eZ/Publish/Core/FieldType/RichText/Resources/stylesheets/xhtml5/edit/docbook.xsl
@@ -622,4 +622,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/eZ/Publish/Core/FieldType/Tests/RichText/Converter/Render/EmbedTest.php b/eZ/Publish/Core/FieldType/Tests/RichText/Converter/Render/EmbedTest.php
index 6f7a704ff4b..3eeae59c1b9 100644
--- a/eZ/Publish/Core/FieldType/Tests/RichText/Converter/Render/EmbedTest.php
+++ b/eZ/Publish/Core/FieldType/Tests/RichText/Converter/Render/EmbedTest.php
@@ -479,6 +479,7 @@ public function testConvert($xmlString, $expectedXmlString, array $errors, array
$this->loggerMock->expects($this->never())->method('error');
}
+ $this->rendererMock->expects($this->never())->method('renderStyle');
$this->rendererMock->expects($this->never())->method('renderTag');
if (!empty($renderParams)) {
diff --git a/eZ/Publish/Core/FieldType/Tests/RichText/Converter/Render/StyleTest.php b/eZ/Publish/Core/FieldType/Tests/RichText/Converter/Render/StyleTest.php
new file mode 100644
index 00000000000..33943e86a67
--- /dev/null
+++ b/eZ/Publish/Core/FieldType/Tests/RichText/Converter/Render/StyleTest.php
@@ -0,0 +1,155 @@
+rendererMock = $this->getRendererMock();
+ $this->converterMock = $this->getConverterMock();
+ parent::setUp();
+ }
+
+ public function providerForTestConvert()
+ {
+ return [
+ [
+ '
+',
+ '
+',
+ [
+ [
+ 'name' => 'style1',
+ 'is_inline' => false,
+ 'params' => [
+ 'name' => 'style1',
+ 'content' => 'style 1 content',
+ ],
+ ],
+ ],
+ ],
+ [
+ '
+
+ style 2 content
+ style 3 content
+',
+ '
+
+ style 2 content
+ style 3 content
+',
+ [
+ [
+ 'name' => 'style2',
+ 'is_inline' => false,
+ 'params' => [
+ 'name' => 'style2',
+ 'content' => 'style 2 content',
+ ],
+ ],
+ [
+ 'name' => 'style3',
+ 'is_inline' => true,
+ 'params' => [
+ 'name' => 'style3',
+ 'content' => 'style 3 content',
+ ],
+ ],
+ ],
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider providerForTestConvert
+ */
+ public function testConvert($xmlString, $expectedXmlString, array $renderParams)
+ {
+ $this->rendererMock->expects($this->never())->method('renderContentEmbed');
+ $this->rendererMock->expects($this->never())->method('renderLocationEmbed');
+ $this->rendererMock->expects($this->never())->method('renderTag');
+
+ if (!empty($renderParams)) {
+ foreach ($renderParams as $index => $params) {
+ $this->rendererMock
+ ->expects($this->at($index))
+ ->method('renderStyle')
+ ->with(
+ $params['name'],
+ $params['params'],
+ $params['is_inline']
+ )
+ ->will($this->returnValue($params['name']));
+ }
+ } else {
+ $this->rendererMock->expects($this->never())->method('renderStyle');
+ }
+
+ $document = new DOMDocument();
+ $document->preserveWhiteSpace = false;
+ $document->formatOutput = false;
+ $document->loadXML($xmlString);
+
+ $document = $this->getConverter()->convert($document);
+
+ $expectedDocument = new DOMDocument();
+ $expectedDocument->preserveWhiteSpace = false;
+ $expectedDocument->formatOutput = false;
+ $expectedDocument->loadXML($expectedXmlString);
+
+ $this->assertEquals($expectedDocument, $document);
+ }
+
+ protected function getConverter()
+ {
+ return new Style($this->rendererMock, $this->converterMock);
+ }
+
+ /**
+ * @return \PHPUnit\Framework\MockObject\MockObject
+ */
+ protected function getRendererMock()
+ {
+ return $this->createMock(RendererInterface::class);
+ }
+
+ /**
+ * @return \PHPUnit\Framework\MockObject\MockObject
+ */
+ protected function getConverterMock()
+ {
+ return $this->createMock(Converter::class);
+ }
+}
diff --git a/eZ/Publish/Core/FieldType/Tests/RichText/Converter/Render/TemplateTest.php b/eZ/Publish/Core/FieldType/Tests/RichText/Converter/Render/TemplateTest.php
index caf48a6c600..69656bb5241 100644
--- a/eZ/Publish/Core/FieldType/Tests/RichText/Converter/Render/TemplateTest.php
+++ b/eZ/Publish/Core/FieldType/Tests/RichText/Converter/Render/TemplateTest.php
@@ -273,6 +273,7 @@ public function testConvert($xmlString, $expectedXmlString, array $renderParams)
{
$this->rendererMock->expects($this->never())->method('renderContentEmbed');
$this->rendererMock->expects($this->never())->method('renderLocationEmbed');
+ $this->rendererMock->expects($this->never())->method('renderStyle');
if (!empty($renderParams)) {
foreach ($renderParams as $index => $params) {
diff --git a/eZ/Publish/Core/FieldType/Tests/RichText/Converter/Xslt/_fixtures/docbook/031-ezstyle.xml b/eZ/Publish/Core/FieldType/Tests/RichText/Converter/Xslt/_fixtures/docbook/031-ezstyle.xml
new file mode 100644
index 00000000000..730ced106f4
--- /dev/null
+++ b/eZ/Publish/Core/FieldType/Tests/RichText/Converter/Xslt/_fixtures/docbook/031-ezstyle.xml
@@ -0,0 +1,16 @@
+
+
+
+ Highlighted block with "left" align.
+
+
+ Highlighted block with "right" align.
+
+
+ Highlighted block with "center" align.
+
+
diff --git a/eZ/Publish/Core/FieldType/Tests/RichText/Converter/Xslt/_fixtures/docbook/032-ezstyleinline.xml b/eZ/Publish/Core/FieldType/Tests/RichText/Converter/Xslt/_fixtures/docbook/032-ezstyleinline.xml
new file mode 100644
index 00000000000..f001565cb17
--- /dev/null
+++ b/eZ/Publish/Core/FieldType/Tests/RichText/Converter/Xslt/_fixtures/docbook/032-ezstyleinline.xml
@@ -0,0 +1,8 @@
+
+
+ Some highlighted words for the otherwise unremarkable paragraph.
+
diff --git a/eZ/Publish/Core/FieldType/Tests/RichText/Converter/Xslt/_fixtures/xhtml5/edit/031-ezstyle.xml b/eZ/Publish/Core/FieldType/Tests/RichText/Converter/Xslt/_fixtures/xhtml5/edit/031-ezstyle.xml
new file mode 100644
index 00000000000..2c75a2b763f
--- /dev/null
+++ b/eZ/Publish/Core/FieldType/Tests/RichText/Converter/Xslt/_fixtures/xhtml5/edit/031-ezstyle.xml
@@ -0,0 +1,12 @@
+
+
+
+ Highlighted block with "left" align.
+
+
+ Highlighted block with "right" align.
+
+
+ Highlighted block with "center" align.
+
+
diff --git a/eZ/Publish/Core/FieldType/Tests/RichText/Converter/Xslt/_fixtures/xhtml5/edit/032-ezstyleinline.xml b/eZ/Publish/Core/FieldType/Tests/RichText/Converter/Xslt/_fixtures/xhtml5/edit/032-ezstyleinline.xml
new file mode 100644
index 00000000000..b6b41c35591
--- /dev/null
+++ b/eZ/Publish/Core/FieldType/Tests/RichText/Converter/Xslt/_fixtures/xhtml5/edit/032-ezstyleinline.xml
@@ -0,0 +1,4 @@
+
+
+ Some highlighted words for the otherwise unremarkable paragraph.
+
diff --git a/eZ/Publish/Core/FieldType/Tests/RichText/Converter/Xslt/_fixtures/xhtml5/output/031-ezstyle.xml b/eZ/Publish/Core/FieldType/Tests/RichText/Converter/Xslt/_fixtures/xhtml5/output/031-ezstyle.xml
new file mode 100644
index 00000000000..2426c437be1
--- /dev/null
+++ b/eZ/Publish/Core/FieldType/Tests/RichText/Converter/Xslt/_fixtures/xhtml5/output/031-ezstyle.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/eZ/Publish/Core/FieldType/Tests/RichText/Converter/Xslt/_fixtures/xhtml5/output/032-ezstyleinline.xml b/eZ/Publish/Core/FieldType/Tests/RichText/Converter/Xslt/_fixtures/xhtml5/output/032-ezstyleinline.xml
new file mode 100644
index 00000000000..f94fec45ae8
--- /dev/null
+++ b/eZ/Publish/Core/FieldType/Tests/RichText/Converter/Xslt/_fixtures/xhtml5/output/032-ezstyleinline.xml
@@ -0,0 +1,4 @@
+
+
+ Some for the otherwise unremarkable paragraph.
+
diff --git a/eZ/Publish/Core/MVC/Symfony/FieldType/RichText/Renderer.php b/eZ/Publish/Core/MVC/Symfony/FieldType/RichText/Renderer.php
index 4dc6e3c2198..d141df08901 100644
--- a/eZ/Publish/Core/MVC/Symfony/FieldType/RichText/Renderer.php
+++ b/eZ/Publish/Core/MVC/Symfony/FieldType/RichText/Renderer.php
@@ -12,6 +12,7 @@
use eZ\Publish\API\Repository\Values\Content\Content;
use eZ\Publish\Core\FieldType\RichText\RendererInterface;
use eZ\Publish\Core\MVC\ConfigResolverInterface;
+use Psr\Log\NullLogger;
use Symfony\Component\Templating\EngineInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use eZ\Publish\Core\MVC\Symfony\Security\Authorization\Attribute as AuthorizationAttribute;
@@ -44,6 +45,11 @@ class Renderer implements RendererInterface
*/
protected $tagConfigurationNamespace;
+ /**
+ * @var string
+ */
+ protected $styleConfigurationNamespace;
+
/**
* @var string
*/
@@ -69,15 +75,22 @@ class Renderer implements RendererInterface
*/
private $customTagsConfiguration;
+ /**
+ * @var array
+ */
+ private $customStylesConfiguration;
+
/**
* @param \eZ\Publish\API\Repository\Repository $repository
* @param \Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface $authorizationChecker
* @param \eZ\Publish\Core\MVC\ConfigResolverInterface $configResolver
* @param \Symfony\Component\Templating\EngineInterface $templateEngine
* @param string $tagConfigurationNamespace
+ * @param string $styleConfigurationNamespace
* @param string $embedConfigurationNamespace
* @param null|\Psr\Log\LoggerInterface $logger
* @param array $customTagsConfiguration
+ * @param array $customStylesConfiguration
*/
public function __construct(
Repository $repository,
@@ -85,40 +98,69 @@ public function __construct(
ConfigResolverInterface $configResolver,
EngineInterface $templateEngine,
$tagConfigurationNamespace,
+ $styleConfigurationNamespace,
$embedConfigurationNamespace,
LoggerInterface $logger = null,
- array $customTagsConfiguration = []
+ array $customTagsConfiguration = [],
+ array $customStylesConfiguration = []
) {
$this->repository = $repository;
$this->authorizationChecker = $authorizationChecker;
$this->configResolver = $configResolver;
$this->templateEngine = $templateEngine;
$this->tagConfigurationNamespace = $tagConfigurationNamespace;
+ $this->styleConfigurationNamespace = $styleConfigurationNamespace;
$this->embedConfigurationNamespace = $embedConfigurationNamespace;
- $this->logger = $logger;
+ $this->logger = $logger ?? new NullLogger();
$this->customTagsConfiguration = $customTagsConfiguration;
+ $this->customStylesConfiguration = $customStylesConfiguration;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function renderStyle($name, array $parameters, $isInline)
+ {
+ $templateName = $this->getStyleTemplateName($name, $isInline);
+
+ if ($templateName === null) {
+ $this->logger->error(
+ "Could not render template style '{$name}': no template configured"
+ );
+
+ return null;
+ }
+
+ if (!$this->templateEngine->exists($templateName)) {
+ $this->logger->error(
+ "Could not render template style '{$name}': template '{$templateName}' does not exists"
+ );
+
+ return null;
+ }
+
+ return $this->render($templateName, $parameters);
}
+ /**
+ * {@inheritdoc}
+ */
public function renderTag($name, array $parameters, $isInline)
{
$templateName = $this->getTagTemplateName($name, $isInline);
if ($templateName === null) {
- if (isset($this->logger)) {
- $this->logger->error(
- "Could not render template tag '{$name}': no template configured"
- );
- }
+ $this->logger->error(
+ "Could not render template tag '{$name}': no template configured"
+ );
return null;
}
if (!$this->templateEngine->exists($templateName)) {
- if (isset($this->logger)) {
- $this->logger->error(
- "Could not render template tag '{$name}': template '{$templateName}' does not exists"
- );
- }
+ $this->logger->error(
+ "Could not render template tag '{$name}': template '{$templateName}' does not exists"
+ );
return null;
}
@@ -126,6 +168,9 @@ public function renderTag($name, array $parameters, $isInline)
return $this->render($templateName, $parameters);
}
+ /**
+ * {@inheritdoc}
+ */
public function renderContentEmbed($contentId, $viewType, array $parameters, $isInline)
{
$isDenied = false;
@@ -139,31 +184,25 @@ function (Repository $repository) use ($contentId) {
);
if (!$content->contentInfo->mainLocationId) {
- if (isset($this->logger)) {
- $this->logger->error(
- "Could not render embedded resource: Content #{$contentId} is trashed."
- );
- }
+ $this->logger->error(
+ "Could not render embedded resource: Content #{$contentId} is trashed."
+ );
return null;
}
$this->checkContentPermissions($content);
} catch (AccessDeniedException $e) {
- if (isset($this->logger)) {
- $this->logger->error(
- "Could not render embedded resource: access denied to embed Content #{$contentId}"
- );
- }
+ $this->logger->error(
+ "Could not render embedded resource: access denied to embed Content #{$contentId}"
+ );
$isDenied = true;
} catch (Exception $e) {
if ($e instanceof NotFoundHttpException || $e instanceof NotFoundException) {
- if (isset($this->logger)) {
- $this->logger->error(
- "Could not render embedded resource: Content #{$contentId} not found"
- );
- }
+ $this->logger->error(
+ "Could not render embedded resource: Content #{$contentId} not found"
+ );
return null;
} else {
@@ -186,11 +225,9 @@ function (Repository $repository) use ($contentId) {
}
if (!$this->templateEngine->exists($templateName)) {
- if (isset($this->logger)) {
- $this->logger->error(
- "Could not render embedded resource: template '{$templateName}' does not exists"
- );
- }
+ $this->logger->error(
+ "Could not render embedded resource: template '{$templateName}' does not exists"
+ );
return null;
}
@@ -198,6 +235,9 @@ function (Repository $repository) use ($contentId) {
return $this->render($templateName, $parameters);
}
+ /**
+ * {@inheritdoc}
+ */
public function renderLocationEmbed($locationId, $viewType, array $parameters, $isInline)
{
$isDenied = false;
@@ -206,29 +246,23 @@ public function renderLocationEmbed($locationId, $viewType, array $parameters, $
$location = $this->checkLocation($locationId);
if ($location->invisible) {
- if (isset($this->logger)) {
- $this->logger->error(
- "Could not render embedded resource: Location #{$locationId} is not visible"
- );
- }
+ $this->logger->error(
+ "Could not render embedded resource: Location #{$locationId} is not visible"
+ );
return null;
}
} catch (AccessDeniedException $e) {
- if (isset($this->logger)) {
- $this->logger->error(
- "Could not render embedded resource: access denied to embed Location #{$locationId}"
- );
- }
+ $this->logger->error(
+ "Could not render embedded resource: access denied to embed Location #{$locationId}"
+ );
$isDenied = true;
} catch (Exception $e) {
if ($e instanceof NotFoundHttpException || $e instanceof NotFoundException) {
- if (isset($this->logger)) {
- $this->logger->error(
- "Could not render embedded resource: Location #{$locationId} not found"
- );
- }
+ $this->logger->error(
+ "Could not render embedded resource: Location #{$locationId} not found"
+ );
return null;
} else {
@@ -251,11 +285,9 @@ public function renderLocationEmbed($locationId, $viewType, array $parameters, $
}
if (!$this->templateEngine->exists($templateName)) {
- if (isset($this->logger)) {
- $this->logger->error(
- "Could not render embedded resource: template '{$templateName}' does not exists"
- );
- }
+ $this->logger->error(
+ "Could not render embedded resource: template '{$templateName}' does not exists"
+ );
return null;
}
@@ -279,6 +311,43 @@ protected function render($templateReference, array $parameters)
);
}
+ /**
+ * Returns configured template name for the given Custom Style identifier.
+ *
+ * @param string $identifier
+ * @param bool $isInline
+ *
+ * @return null|string
+ */
+ protected function getStyleTemplateName($identifier, $isInline)
+ {
+ if (!empty($this->customStylesConfiguration[$identifier]['template'])) {
+ return $this->customStylesConfiguration[$identifier]['template'];
+ }
+
+ $this->logger->warning(
+ "Template style '{$identifier}' configuration was not found"
+ );
+
+ if ($isInline) {
+ $configurationReference = $this->styleConfigurationNamespace . '.default_inline';
+ } else {
+ $configurationReference = $this->styleConfigurationNamespace . '.default';
+ }
+
+ if ($this->configResolver->hasParameter($configurationReference)) {
+ $configuration = $this->configResolver->getParameter($configurationReference);
+
+ return $configuration['template'];
+ }
+
+ $this->logger->warning(
+ "Template style '{$identifier}' default configuration was not found"
+ );
+
+ return null;
+ }
+
/**
* Returns configured template name for the given template tag identifier.
*
@@ -303,11 +372,9 @@ protected function getTagTemplateName($identifier, $isInline)
}
// End of BC layer --/
- if (isset($this->logger)) {
- $this->logger->warning(
- "Template tag '{$identifier}' configuration was not found"
- );
- }
+ $this->logger->warning(
+ "Template tag '{$identifier}' configuration was not found"
+ );
if ($isInline) {
$configurationReference = $this->tagConfigurationNamespace . '.default_inline';
@@ -321,11 +388,9 @@ protected function getTagTemplateName($identifier, $isInline)
return $configuration['template'];
}
- if (isset($this->logger)) {
- $this->logger->warning(
- "Template tag '{$identifier}' default configuration was not found"
- );
- }
+ $this->logger->warning(
+ "Template tag '{$identifier}' default configuration was not found"
+ );
return null;
}
@@ -363,11 +428,9 @@ protected function getEmbedTemplateName($resourceType, $isInline, $isDenied)
return $configuration['template'];
}
- if (isset($this->logger)) {
- $this->logger->warning(
- "Embed tag configuration '{$configurationReference}' was not found"
- );
- }
+ $this->logger->warning(
+ "Embed tag configuration '{$configurationReference}' was not found"
+ );
$configurationReference = $this->embedConfigurationNamespace;
@@ -383,11 +446,9 @@ protected function getEmbedTemplateName($resourceType, $isInline, $isDenied)
return $configuration['template'];
}
- if (isset($this->logger)) {
- $this->logger->warning(
- "Embed tag default configuration '{$configurationReference}' was not found"
- );
- }
+ $this->logger->warning(
+ "Embed tag default configuration '{$configurationReference}' was not found"
+ );
return null;
}
diff --git a/eZ/Publish/Core/MVC/Symfony/FieldType/Tests/RichText/RendererTest.php b/eZ/Publish/Core/MVC/Symfony/FieldType/Tests/RichText/RendererTest.php
index 44dde2aa076..dc10ec11d5e 100644
--- a/eZ/Publish/Core/MVC/Symfony/FieldType/Tests/RichText/RendererTest.php
+++ b/eZ/Publish/Core/MVC/Symfony/FieldType/Tests/RichText/RendererTest.php
@@ -1402,6 +1402,7 @@ protected function getMockedRenderer(array $methods = array())
$this->configResolverMock,
$this->templateEngineMock,
'test.name.space.tag',
+ 'test.name.space.style',
'test.name.space.embed',
$this->loggerMock,
)