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 [ + [ + ' +
+ style 1 content +
', + ' +
+ style 1 content +
', + [ + [ + '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, )