<?php
namespace Cms\WidgetBundle\Service\Twig;
use Cms\AssetsBundle\Service\AssetCatalog;
use Cms\ContentBundle\Service\FrontendRenderer;
use Cms\CoreBundle\Service\SceneRenderer;
use Cms\CoreBundle\Service\Transcoding\TranscodedObject;
use Cms\CoreBundle\Service\Transcoding\Transcoder;
use Cms\FileBundle\Service\FileManager;
use Cms\FrontendBundle\Model\FrontendGlobals;
use Cms\ThemeBundle\Service\Twig\Loader\DatabaseLoader;
use Cms\WidgetBundle\Model\Widget;
use Cms\WidgetBundle\Model\WidgetContainer;
use Cms\WidgetBundle\Model\WidgetObject;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
/**
* Provides Twig helpers to work with widgets easily inside of Twig files.
*
* Class WidgetExtension
* @package Cms\WidgetBundle\Service\Twig
*/
final class WidgetExtension extends AbstractExtension
{
/**
* @var FileManager
*/
private $fileManager;
/**
* @var AssetCatalog
*/
private $assetCatalog;
/**
* @param Transcoder $transcoder
* @param SceneRenderer $sceneRenderer
* @param FileManager $fileManager
* @param AssetCatalog $assetCatalog
*/
public function __construct(Transcoder $transcoder, SceneRenderer $sceneRenderer, FileManager $fileManager, AssetCatalog $assetCatalog)
{
$this->fileManager = $fileManager;
$this->assetCatalog = $assetCatalog;
}
/**
* {@inheritdoc}
*/
public function getFunctions(): array
{
$functions = array(
new TwigFunction(
'cms_widgetChildren',
array($this, 'functionWidgetChildren'),
array(
'needs_context' => true,
'is_safe' => array(
'all',
),
)
),
new TwigFunction(
'cms_widgetContainer',
array($this, 'functionWidgetContainer'),
array(
'is_safe' => array(
'all',
),
)
),
new TwigFunction(
'cms_widgetTrigger',
array($this, 'functionWidgetTrigger'),
array(
'is_safe' => array(
'all',
),
)
),
new TwigFunction(
'cms_widgetRender',
array($this, 'functionRenderWidget'),
array(
'needs_context' => true,
'is_safe' => array(
'all',
),
)
),
new TwigFunction(
'cms_widgetsRender',
array($this, 'functionRenderWidgets'),
array(
'needs_context' => true,
'is_safe' => array(
'all',
),
)
),
new TwigFunction(
'cms_widgetsBuildInclude',
array($this, 'cms_widgetsBuildInclude'),
array(
'needs_context' => true,
'is_safe' => array(
'all',
),
)
),
new TwigFunction(
'cms_widgetsBaseInclude',
array($this, 'cms_widgetsBaseInclude'),
array(
'needs_context' => true,
'is_safe' => array(
'all',
),
)
),
);
return $functions;
}
public function cms_widgetsBaseInclude(array $context, $include)
{
// we should also have access to globals
if ( ! isset($context['_globals'])) {
throw new \Exception();
}
$globals = $context['_globals'];
if ( ! $globals instanceof FrontendGlobals) {
throw new \Exception();
}
// return the determined include path
// TODO: make flexible base
return sprintf(
'@package$/%s',
ltrim($include, '/')
);
}
/**
* @param array $context
* @param string $include
* @return string
* @throws \Exception
*/
public function cms_widgetsBuildInclude(array $context, $include)
{
// in the context, we should have a _loader; this function should only be used in widget rendering contexts
if ( ! isset($context['_loader'])) {
throw new \Exception();
}
$loader = $context['_loader'];
if ( ! $loader instanceof DatabaseLoader) {
throw new \Exception();
}
// we should also have access to globals
if ( ! isset($context['_globals'])) {
throw new \Exception();
}
$globals = $context['_globals'];
if ( ! $globals instanceof FrontendGlobals) {
throw new \Exception();
}
// return the determined include path
return $loader->determine(
$globals->getTheme(),
$include
);
}
/**
* @param Widget $handler
* @param array $options
* @param string $id
* @param array $styles
* @param array $scripts
* @return string
* @throws \Cms\CoreBundle\Service\Transcoding\TranscodingException
*/
public function functionWidgetTrigger(Widget $handler, array $options, $id, array $styles = [], array $scripts = [])
{
return json_encode(
array(
'styles' => array_merge(
$this->assetCatalog->expandAssets($handler->getStyles()),
$styles
),
'scripts' => array_merge(
$this->assetCatalog->expandAssets($handler->getScripts()),
$scripts
),
'plugin' => ($handler->hasPlugin()) ? $handler->getKey() : null,
'pluginId' => $id,
'pluginOptions' => (object) $options,
),
JSON_PRETTY_PRINT
);
}
/**
* @param array $context
* @param TranscodedObject $widget
* @return string
* @throws \Exception
*/
public function functionRenderWidget(array $context, TranscodedObject $widget)
{
/** @var FrontendRenderer $renderer */
$renderer = $context['_renderer'];
if ( ! $widget instanceof WidgetObject) {
throw new \Exception();
}
return $renderer->renderWidget($widget);
}
/**
* @param array $context
* @param array $widgets
* @return string
* @throws \Exception
*/
public function functionRenderWidgets(array $context, array $widgets)
{
/** @var FrontendRenderer $renderer */
$renderer = $context['_renderer'];
$output = '';
foreach ($widgets as $widget) {
if ( ! $widget instanceof WidgetObject) {
throw new \Exception();
}
$output .= $renderer->renderWidget($widget);
}
return $output;
}
/**
* @param array $context
* @param TranscodedObject $widget
* @return string
* @throws \Exception
*/
public function functionWidgetChildren(array $context, TranscodedObject $widget)
{
/** @var FrontendRenderer $renderer */
$renderer = $context['_renderer'];
/** @var WidgetObject $widget */
$children = $widget->getChildren();
if ($children === null || (is_array($children) && count($children) === 0)) {
return '';
}
return $renderer->renderWidgets($children);
}
/**
* @param WidgetContainer $container
* @param string $defaultId
* @return string
* @throws \Exception
*/
public function functionWidgetContainer(WidgetContainer $container = null, $defaultId)
{
// holders for styles and classes we need
$id = '';
$styles = [];
$classes = [];
// do this part only if the container is not null
if ($container !== null) {
// handle id
if ( ! empty($container->getHtmlId())) {
$id = $container->getHtmlId();
}
// custom css classes
if ( ! empty($container->getCustomClasses())) {
$classes = array_merge(
$classes,
explode(' ', $container->getCustomClasses())
);
}
// background color
if ( ! empty($container->getBackgroundColor())) {
$classes[] = 'bg-' . $container->getBackgroundColor();
}
// background image
if ( ! empty($container->getBackgroundImage())) {
$styles['background-image'] = sprintf(
'url(\'%s\')',
$this->fileManager->identUrl($container->getBackgroundImage())
);
$styles['background-size'] = $container->getBackgroundImageSize();
$styles['background-repeat'] = $container->getBackgroundImageRepeat();
$styles['background-position'] = $container->getBackgroundImagePosition();
}
// border width
if ( ! empty($container->getBorderWidth())) {
$classes[] = 'b-' . $container->getBorderWidth();
}
// border color
if ( ! empty($container->getBorderColor())) {
$classes[] = 'b-' . $container->getBorderColor();
}
// handle border styles
if ($container->getBorderStyleAdvanced() !== true) {
if ( ! empty($container->getBorderStyle())) {
$classes[] = 'b-' . $container->getBorderStyle();
}
} else {
if ( ! empty($container->getBorderStyleTop())) {
$classes[] = 'bt-' . $container->getBorderStyleTop();
}
if ( ! empty($container->getBorderStyleBottom())) {
$classes[] = 'bb-' . $container->getBorderStyleBottom();
}
if ( ! empty($container->getBorderStyleLeft())) {
$classes[] = 'bl-' . $container->getBorderStyleLeft();
}
if ( ! empty($container->getBorderStyleRight())) {
$classes[] = 'br-' . $container->getBorderStyleRight();
}
}
// handle padding
if ($container->getPaddingAdvanced() !== true) {
if ( ! empty($container->getPadding())) {
$classes[] = 'p-' . $container->getPadding();
}
} else {
if ( ! empty($container->getPaddingTop())) {
$classes[] = 'pt-' . $container->getPaddingTop();
}
if ( ! empty($container->getPaddingBottom())) {
$classes[] = 'pb-' . $container->getPaddingBottom();
}
if ( ! empty($container->getPaddingLeft())) {
$classes[] = 'pl-' . $container->getPaddingLeft();
}
if ( ! empty($container->getPaddingRight())) {
$classes[] = 'pr-' . $container->getPaddingRight();
}
}
// handle margin
if ($container->getMarginAdvanced() !== true) {
if ( ! empty($container->getMargin())) {
$classes[] = 'm-' . $container->getMargin();
}
} else {
if ( ! empty($container->getMarginTop())) {
$classes[] = 'mt-' . $container->getMarginTop();
}
if ( ! empty($container->getMarginBottom())) {
$classes[] = 'mb-' . $container->getMarginBottom();
}
if ( ! empty($container->getMarginLeft())) {
$classes[] = 'ml-' . $container->getMarginLeft();
}
if ( ! empty($container->getMarginRight())) {
$classes[] = 'mr-' . $container->getMarginRight();
}
}
}
// default id
if (empty($id)) {
$id = $defaultId;
}
// compile final result
$result = [];
// handle id
if ( ! empty($id)) {
$result[] = 'id="'.trim(trim($id, '#')).'"';
}
// handle classes
if (count($classes) > 0) {
$result[] = 'class="'.trim(implode(' ', $classes)).'"';
}
// handle inline styles
if (count($styles) > 0) {
$result[] = 'style="'.trim(implode(' ', array_map(
function($v, $k)
{
return $k.': '.$v.';';
},
$styles,
array_keys($styles)
))).'"';
}
// send back container options
return trim(implode(' ', $result));
}
}