This commit is contained in:
Xes
2025-08-14 22:41:49 +02:00
parent 2de81ccc46
commit 8ce45119b6
39774 changed files with 4309466 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2010-2016 Thomas Rabaix
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,60 @@
# DO NOT EDIT THIS FILE!
#
# It's auto-generated by sonata-project/dev-kit package.
all:
@echo "Please choose a task."
.PHONY: all
lint: lint-composer lint-yaml lint-composer lint-xml lint-php
.PHONY: lint
lint-composer:
composer validate
.PHONY: lint-composer
lint-yaml:
find . -name '*.yml' -not -path './vendor/*' -not -path './src/Resources/public/vendor/*' | xargs yaml-lint
.PHONY: lint-yaml
lint-xml:
find . \( -name '*.xml' -or -name '*.xliff' \) \
-not -path './vendor/*' \
-not -path './src/Resources/public/vendor/*' \
| while read xmlFile; \
do \
XMLLINT_INDENT=' ' xmllint --encode UTF-8 --format "$$xmlFile"|diff - "$$xmlFile"; \
if [ $$? -ne 0 ] ;then exit 1; fi; \
done
.PHONY: lint-xml
lint-php:
php-cs-fixer fix --ansi --verbose --diff --dry-run
.PHONY: lint-php
cs-fix: cs-fix-php cs-fix-xml
.PHONY: cs-fix
cs-fix-php:
php-cs-fixer fix --verbose
.PHONY: cs-fix-php
cs-fix-xml:
find . \( -name '*.xml' -or -name '*.xliff' \) \
-not -path './vendor/*' \
-not -path './src/Resources/public/vendor/*' \
| while read xmlFile; \
do \
XMLLINT_INDENT=' ' xmllint --encode UTF-8 --format "$$xmlFile" --output "$$xmlFile"; \
done
.PHONY: cs-fix-xml
test:
phpunit -c phpunit.xml.dist --coverage-clover build/logs/clover.xml
.PHONY: test
docs:
cd docs && sphinx-build -W -b html -d _build/doctrees . _build/html
.PHONY: docs

View File

@@ -0,0 +1,70 @@
{
"name": "sonata-project/block-bundle",
"type": "symfony-bundle",
"description": "Symfony SonataBlockBundle",
"keywords": [
"sonata",
"block"
],
"homepage": "https://sonata-project.org/bundles/block",
"license": "MIT",
"authors": [
{
"name": "Thomas Rabaix",
"email": "thomas.rabaix@sonata-project.org",
"homepage": "https://sonata-project.org"
},
{
"name": "Sonata Community",
"homepage": "https://github.com/sonata-project/SonataBlockBundle/contributors"
}
],
"require": {
"php": "^5.6 || ^7.0",
"doctrine/common": "^2.3",
"sonata-project/cache": "^1.0 || ^2.0",
"sonata-project/core-bundle": "^3.4",
"symfony/asset": "^2.8 || ^3.2 || ^4.0",
"symfony/form": "^2.8 || ^3.2 || ^4.0",
"symfony/http-kernel": "^2.8 || ^3.2 || ^4.0",
"symfony/templating": "^2.8 || ^3.2 || ^4.0",
"symfony/twig-bundle": "^2.8 || ^3.2 || ^4.0",
"twig/twig": "^1.34 || ^2.0"
},
"conflict": {
"jms/di-extra-bundle": "<1.7.0"
},
"require-dev": {
"jms/di-extra-bundle": "^1.7",
"knplabs/knp-menu-bundle": "^2.0",
"sonata-project/admin-bundle": "^3.22",
"symfony/phpunit-bridge": "^4.0",
"symfony/stopwatch": "^2.8 || ^3.2 || ^4.0"
},
"suggest": {
"jms/di-extra-bundle": "Annotations for Block definition",
"knplabs/knp-menu-bundle": "^2.0",
"sonata-project/cache-bundle": "^3.0"
},
"config": {
"sort-packages": true
},
"extra": {
"branch-alias": {
"dev-master": "3.x-dev"
}
},
"autoload": {
"psr-4": {
"Sonata\\BlockBundle\\": "src/"
},
"files": [
"src/Resources/stubs/symfony2.php"
]
},
"autoload-dev": {
"psr-4": {
"Sonata\\BlockBundle\\Tests\\": "tests/"
}
}
}

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
bootstrap="tests/bootstrap.php"
>
<testsuites>
<testsuite name="SonataBlockBundle Test Suite">
<directory suffix="Test.php">./tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">./src/</directory>
</whitelist>
</filter>
<php>
<ini name="precision" value="8"/>
</php>
</phpunit>

View File

@@ -0,0 +1,45 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Annotation;
use JMS\DiExtraBundle\Annotation\MetadataProcessorInterface;
use JMS\DiExtraBundle\Metadata\ClassMetadata;
use Symfony\Component\DependencyInjection\Reference;
/**
* Use annotations to define block classes.
*
* @Annotation
* @Target("CLASS")
*/
class Block implements MetadataProcessorInterface
{
/**
* Service id - autogenerated per default.
*
* @var string
*/
public $id;
/**
* @param ClassMetadata $metadata
*/
public function processMetadata(ClassMetadata $metadata)
{
if (!empty($this->id)) {
$metadata->id = $this->id;
}
$metadata->tags['sonata.block'][] = [];
$metadata->arguments = [$this->id, new Reference('templating')];
}
}

View File

@@ -0,0 +1,30 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Block;
@trigger_error(
'The '.__NAMESPACE__.'\AbstractBlockService class is deprecated since 3.2 '.
'and will be removed with the 4.0 release.'.
'Use '.__NAMESPACE__.'\Service\AbstractBlockService instead.',
E_USER_DEPRECATED
);
/**
* NEXT_MAJOR: remove this class.
*
* @author Sullivan Senechal <soullivaneuh@gmail.com>
*
* @deprecated since 3.2, to be removed with 4.0
*/
abstract class AbstractBlockService extends \Sonata\BlockBundle\Block\Service\AbstractBlockService
{
}

View File

@@ -0,0 +1,34 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Block;
use Sonata\BlockBundle\Block\Service\AbstractAdminBlockService;
@trigger_error(
'The '.__NAMESPACE__.'\BaseBlockService class is deprecated since 3.2 '.
'and will be removed with the 4.0 release.'.
'Use '.__NAMESPACE__.'\Service\AbstractBlockService instead.',
E_USER_DEPRECATED
);
/**
* BaseBlockService.
*
* NEXT_MAJOR: remove this class.
*
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* @deprecated since 3.2, to be removed with 4.0
*/
abstract class BaseBlockService extends AbstractAdminBlockService implements BlockAdminServiceInterface
{
}

View File

@@ -0,0 +1,30 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Block;
use Sonata\BlockBundle\Block\Service\AdminBlockServiceInterface;
@trigger_error(
'The '.__NAMESPACE__.'BlockAdminServiceInterface interface is deprecated since 3.2 '.
'and will be removed with the 4.0 release. '.
'Use '.__NAMESPACE__.'\Service\AdminBlockServiceInterface instead.',
E_USER_DEPRECATED
);
/**
* NEXT_MAJOR: remove this interface.
*
* @deprecated since 3.2, to be removed with 4.0
*/
interface BlockAdminServiceInterface extends AdminBlockServiceInterface
{
}

View File

@@ -0,0 +1,87 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Block;
use Sonata\BlockBundle\Model\BlockInterface;
class BlockContext implements BlockContextInterface
{
/**
* @var BlockInterface
*/
protected $block;
/**
* @var array
*/
protected $settings;
/**
* @param BlockInterface $block
* @param array $settings
*/
public function __construct(BlockInterface $block, array $settings = [])
{
$this->block = $block;
$this->settings = $settings;
}
/**
* {@inheritdoc}
*/
public function getBlock()
{
return $this->block;
}
/**
* {@inheritdoc}
*/
public function getSettings()
{
return $this->settings;
}
/**
* {@inheritdoc}
*/
public function getSetting($name)
{
if (!array_key_exists($name, $this->settings)) {
throw new \RuntimeException(sprintf('Unable to find the option `%s` (%s) - define the option in the related BlockServiceInterface', $name, $this->block->getType()));
}
return $this->settings[$name];
}
/**
* {@inheritdoc}
*/
public function setSetting($name, $value)
{
if (!array_key_exists($name, $this->settings)) {
throw new \RuntimeException(sprintf('It\'s not possible add non existing setting `%s`.', $name));
}
$this->settings[$name] = $value;
return $this;
}
/**
* {@inheritdoc}
*/
public function getTemplate()
{
return $this->getSetting('template');
}
}

View File

@@ -0,0 +1,47 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Block;
use Sonata\BlockBundle\Model\BlockInterface;
interface BlockContextInterface
{
/**
* @return BlockInterface
*/
public function getBlock();
/**
* @return array
*/
public function getSettings();
/**
* @param string $name
*
* @return mixed
*/
public function getSetting($name);
/**
* @param string $name
* @param mixed $value
*
* @return BlockContextInterface
*/
public function setSetting($name, $value);
/**
* @return string
*/
public function getTemplate();
}

View File

@@ -0,0 +1,298 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Block;
use Doctrine\Common\Util\ClassUtils;
use Psr\Log\LoggerInterface;
use Sonata\BlockBundle\Model\BlockInterface;
use Symfony\Component\OptionsResolver\Exception\ExceptionInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class BlockContextManager implements BlockContextManagerInterface
{
/**
* @var BlockLoaderInterface
*/
protected $blockLoader;
/**
* @var BlockServiceManagerInterface
*/
protected $blockService;
/**
* @var array
*/
protected $settingsByType;
/**
* @var array
*/
protected $settingsByClass;
/**
* @var array
*/
protected $cacheBlocks;
/**
* @var LoggerInterface
*/
protected $logger;
/**
* Used for deprecation check on {@link resolve} method.
* To be removed in 4.0 with BC system.
*
* @var array
*/
private $reflectionCache;
/**
* @param BlockLoaderInterface $blockLoader
* @param BlockServiceManagerInterface $blockService
* @param array $cacheBlocks
* @param LoggerInterface|null $logger
*/
public function __construct(BlockLoaderInterface $blockLoader, BlockServiceManagerInterface $blockService,
array $cacheBlocks = [], LoggerInterface $logger = null
) {
$this->blockLoader = $blockLoader;
$this->blockService = $blockService;
$this->cacheBlocks = $cacheBlocks;
$this->logger = $logger;
$this->reflectionCache = [];
}
/**
* {@inheritdoc}
*/
public function addSettingsByType($type, array $settings, $replace = false)
{
$typeSettings = isset($this->settingsByType[$type]) ? $this->settingsByType[$type] : [];
if ($replace) {
$this->settingsByType[$type] = array_merge($typeSettings, $settings);
} else {
$this->settingsByType[$type] = array_merge($settings, $typeSettings);
}
}
/**
* {@inheritdoc}
*/
public function addSettingsByClass($class, array $settings, $replace = false)
{
$classSettings = isset($this->settingsByClass[$class]) ? $this->settingsByClass[$class] : [];
if ($replace) {
$this->settingsByClass[$class] = array_merge($classSettings, $settings);
} else {
$this->settingsByClass[$class] = array_merge($settings, $classSettings);
}
}
/**
* Check if a given block type exists.
*
* @param string $type Block type to check for
*
* @return bool
*/
public function exists($type)
{
return $this->blockLoader->exists($type);
}
/**
* {@inheritdoc}
*/
public function get($meta, array $settings = [])
{
if (!$meta instanceof BlockInterface) {
$block = $this->blockLoader->load($meta);
if (is_array($meta) && isset($meta['settings'])) {
// merge user settings
$settings = array_merge($meta['settings'], $settings);
}
} else {
$block = $meta;
}
if (!$block instanceof BlockInterface) {
return false;
}
$originalSettings = $settings;
try {
$settings = $this->resolve($block, array_merge($block->getSettings(), $settings));
} catch (ExceptionInterface $e) {
if ($this->logger) {
$this->logger->error(sprintf(
'[cms::blockContext] block.id=%s - error while resolving options - %s',
$block->getId(),
$e->getMessage()
));
}
$settings = $this->resolve($block, $settings);
}
$blockContext = new BlockContext($block, $settings);
$this->setDefaultExtraCacheKeys($blockContext, $originalSettings);
return $blockContext;
}
/**
* NEXT_MAJOR: remove this method.
*
* @param OptionsResolverInterface $optionsResolver
* @param BlockInterface $block
*
* @deprecated since version 2.3, to be renamed in 4.0.
* Use the method configureSettings instead
*/
protected function setDefaultSettings(OptionsResolverInterface $optionsResolver, BlockInterface $block)
{
if (__CLASS__ !== get_called_class()) {
@trigger_error(
'The '.__METHOD__.' is deprecated since version 2.3, to be renamed in 4.0.'
.' Use '.__CLASS__.'::configureSettings instead.',
E_USER_DEPRECATED
);
}
$this->configureSettings($optionsResolver, $block);
}
protected function configureSettings(OptionsResolver $optionsResolver, BlockInterface $block)
{
// defaults for all blocks
$optionsResolver->setDefaults([
'use_cache' => true,
'extra_cache_keys' => [],
'attr' => [],
'template' => false,
'ttl' => (int) $block->getTtl(),
]);
$optionsResolver
->addAllowedTypes('use_cache', 'bool')
->addAllowedTypes('extra_cache_keys', 'array')
->addAllowedTypes('attr', 'array')
->addAllowedTypes('ttl', 'int')
->addAllowedTypes('template', ['string', 'bool'])
;
// add type and class settings for block
$class = ClassUtils::getClass($block);
$settingsByType = isset($this->settingsByType[$block->getType()]) ? $this->settingsByType[$block->getType()] : [];
$settingsByClass = isset($this->settingsByClass[$class]) ? $this->settingsByClass[$class] : [];
$optionsResolver->setDefaults(array_merge($settingsByType, $settingsByClass));
}
/**
* Adds context settings, to be able to rebuild a block context, to the
* extra_cache_keys.
*
* @param BlockContextInterface $blockContext
* @param array $settings
*/
protected function setDefaultExtraCacheKeys(BlockContextInterface $blockContext, array $settings)
{
if (!$blockContext->getSetting('use_cache') || $blockContext->getSetting('ttl') <= 0) {
return;
}
$block = $blockContext->getBlock();
// type by block class
$class = ClassUtils::getClass($block);
$cacheServiceId = isset($this->cacheBlocks['by_class'][$class]) ? $this->cacheBlocks['by_class'][$class] : false;
// type by block service
if (!$cacheServiceId) {
$cacheServiceId = isset($this->cacheBlocks['by_type'][$block->getType()]) ? $this->cacheBlocks['by_type'][$block->getType()] : false;
}
if (!$cacheServiceId) {
// no context cache needed
return;
}
// do not add cache settings to extra_cache_keys
unset($settings['use_cache'], $settings['extra_cache_keys'], $settings['ttl']);
$extraCacheKeys = $blockContext->getSetting('extra_cache_keys');
// add context settings to extra_cache_keys
if (!isset($extraCacheKeys[self::CACHE_KEY])) {
$extraCacheKeys[self::CACHE_KEY] = $settings;
$blockContext->setSetting('extra_cache_keys', $extraCacheKeys);
}
}
/**
* @param BlockInterface $block
* @param array $settings
*
* @return array
*/
private function resolve(BlockInterface $block, $settings)
{
$optionsResolver = new \Sonata\BlockBundle\Util\OptionsResolver();
$this->configureSettings($optionsResolver, $block);
$service = $this->blockService->get($block);
/* use new interface method whenever possible */
if (method_exists($service, 'configureSettings')) {
$service->configureSettings($optionsResolver);
} else {
$service->setDefaultSettings($optionsResolver);
}
// Caching method reflection
$serviceClass = get_class($service);
if (!isset($this->reflectionCache[$serviceClass])) {
$reflector = new \ReflectionMethod($service, 'setDefaultSettings');
$isOldOverwritten = 'Sonata\BlockBundle\Block\AbstractBlockService' !== $reflector->getDeclaringClass()->getName();
// Prevention for service classes implementing directly the interface and not extends the new base class
if (!method_exists($service, 'configureSettings')) {
$isNewOverwritten = false;
} else {
$reflector = new \ReflectionMethod($service, 'configureSettings');
$isNewOverwritten = 'Sonata\BlockBundle\Block\AbstractBlockService' !== $reflector->getDeclaringClass()->getName();
}
$this->reflectionCache[$serviceClass] = [
'isOldOverwritten' => $isOldOverwritten,
'isNewOverwritten' => $isNewOverwritten,
];
}
if ($this->reflectionCache[$serviceClass]['isOldOverwritten'] && !$this->reflectionCache[$serviceClass]['isNewOverwritten']) {
@trigger_error(
'The Sonata\BlockBundle\Block\BlockServiceInterface::setDefaultSettings() method is deprecated'
.' since version 2.3 and will be removed in 4.0. Use configureSettings() instead.'
.' This method will be added to the BlockServiceInterface with SonataBlockBundle 4.0.',
E_USER_DEPRECATED
);
}
return $optionsResolver->resolve($settings);
}
}

View File

@@ -0,0 +1,50 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Block;
use Sonata\BlockBundle\Exception\BlockOptionsException;
/**
* Interface BlockContextManagerInterface.
*/
interface BlockContextManagerInterface
{
const CACHE_KEY = 'context';
/**
* Add settings for a block service.
*
* @param string $type block service
* @param array $settings
* @param bool $replace replace existing settings
*/
public function addSettingsByType($type, array $settings, $replace = false);
/**
* Add settings for a block class.
*
* @param string $class block class
* @param array $settings
* @param bool $replace replace existing settings
*/
public function addSettingsByClass($class, array $settings, $replace = false);
/**
* @param mixed $meta Data send to the loader to load a block, can be anything...
* @param array $settings
*
* @throws BlockOptionsException
*
* @return BlockContextInterface|false
*/
public function get($meta, array $settings = []);
}

View File

@@ -0,0 +1,70 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Block;
use Sonata\BlockBundle\Exception\BlockNotFoundException;
class BlockLoaderChain implements BlockLoaderInterface
{
/**
* @var BlockLoaderInterface[]
*/
protected $loaders;
/**
* @param BlockLoaderInterface[] $loaders
*/
public function __construct(array $loaders)
{
$this->loaders = $loaders;
}
/**
* Check if a given block type exists.
*
* @param string $type Block type to check for
*
* @return bool
*/
public function exists($type)
{
foreach ($this->loaders as $loader) {
if ($loader->exists($type)) {
return true;
}
}
return false;
}
/**
* {@inheritdoc}
*/
public function load($block)
{
foreach ($this->loaders as $loader) {
if ($loader->support($block)) {
return $loader->load($block);
}
}
throw new BlockNotFoundException();
}
/**
* {@inheritdoc}
*/
public function support($name)
{
return true;
}
}

View File

@@ -0,0 +1,34 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Block;
use Sonata\BlockBundle\Exception\BlockNotFoundException;
use Sonata\BlockBundle\Model\BlockInterface;
interface BlockLoaderInterface
{
/**
* @param mixed $name
*
* @throws BlockNotFoundException if no block with that name is found
*
* @return BlockInterface
*/
public function load($name);
/**
* @param mixed $name
*
* @return bool
*/
public function support($name);
}

View File

@@ -0,0 +1,162 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Block;
use Psr\Log\LoggerInterface;
use Sonata\BlockBundle\Exception\Strategy\StrategyManagerInterface;
use Symfony\Component\HttpFoundation\Response;
/**
* Handles the execution and rendering of a block.
*
* This function render a block and make sure the cacheable information are correctly retrieved
* and set to the upper response (container can have child blocks, so the smallest ttl from a child
* must be used in the container).
*/
class BlockRenderer implements BlockRendererInterface
{
/**
* @var BlockServiceManagerInterface
*/
protected $blockServiceManager;
/**
* @var StrategyManagerInterface
*/
protected $exceptionStrategyManager;
/**
* @var LoggerInterface|null
*/
protected $logger;
/**
* @var bool
*/
protected $debug;
/**
* This property hold the last response available from the child or sibling block
* The cacheable attributes must be cascaded to the parent.
*
* @var Response|null
*/
private $lastResponse;
/**
* @param BlockServiceManagerInterface $blockServiceManager Block service manager
* @param StrategyManagerInterface $exceptionStrategyManager Exception strategy manager
* @param LoggerInterface $logger Logger class
* @param bool $debug Whether in debug mode or not
*/
public function __construct(BlockServiceManagerInterface $blockServiceManager, StrategyManagerInterface $exceptionStrategyManager, LoggerInterface $logger = null, $debug = false)
{
$this->blockServiceManager = $blockServiceManager;
$this->exceptionStrategyManager = $exceptionStrategyManager;
$this->logger = $logger;
$this->debug = $debug;
}
/**
* {@inheritdoc}
*/
public function render(BlockContextInterface $blockContext, Response $response = null)
{
$block = $blockContext->getBlock();
if ($this->logger) {
$this->logger->info(sprintf('[cms::renderBlock] block.id=%d, block.type=%s ', $block->getId(), $block->getType()));
}
try {
$service = $this->blockServiceManager->get($block);
$service->load($block);
$response = $service->execute($blockContext, $this->createResponse($blockContext, $response));
if (!$response instanceof Response) {
$response = null;
throw new \RuntimeException('A block service must return a Response object');
}
$response = $this->addMetaInformation($response, $blockContext, $service);
} catch (\Exception $exception) {
if ($this->logger) {
$this->logger->error(sprintf(
'[cms::renderBlock] block.id=%d - error while rendering block - %s',
$block->getId(),
$exception->getMessage()
), compact('exception'));
}
// reseting the state object
$this->lastResponse = null;
$response = $this->exceptionStrategyManager->handleException($exception, $blockContext->getBlock(), $response);
}
return $response;
}
/**
* @param BlockContextInterface $blockContext
* @param Response $response
*
* @return Response
*/
protected function createResponse(BlockContextInterface $blockContext, Response $response = null)
{
if (null === $response) {
$response = new Response();
}
// set the ttl from the block instance, this can be changed by the BlockService
if (($ttl = $blockContext->getBlock()->getTtl()) > 0) {
$response->setTtl($ttl);
}
return $response;
}
/**
* This method is responsible to cascade ttl to the parent block.
*
* @param Response $response
* @param BlockContextInterface $blockContext
* @param BlockServiceInterface $service
*
* @return Response
*/
protected function addMetaInformation(Response $response, BlockContextInterface $blockContext, BlockServiceInterface $service)
{
// a response exists, use it
if ($this->lastResponse && $this->lastResponse->isCacheable()) {
$response->setTtl($this->lastResponse->getTtl());
$response->setPublic();
} elseif ($this->lastResponse) { // not cacheable
$response->setPrivate();
$response->setTtl(0);
$response->headers->removeCacheControlDirective('s-maxage');
$response->headers->removeCacheControlDirective('maxage');
}
// no more children available in the stack, reseting the state object
if (!$blockContext->getBlock()->hasParent()) {
$this->lastResponse = null;
} else { // contains a parent so storing the response
$this->lastResponse = $response;
}
return $response;
}
}

View File

@@ -0,0 +1,25 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Block;
use Symfony\Component\HttpFoundation\Response;
interface BlockRendererInterface
{
/**
* @param BlockContextInterface $name
* @param null|Response $response
*
* @return Response
*/
public function render(BlockContextInterface $name, Response $response = null);
}

View File

@@ -0,0 +1,78 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Block;
use Sonata\BlockBundle\Model\BlockInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
/**
* Interface BlockServiceInterface.
*
* NEXT_MAJOR: remove this interface.
*
* @deprecated since 3.2, to be removed with 4.0
*/
interface BlockServiceInterface
{
/**
* @param BlockContextInterface $blockContext
* @param Response $response
*
* @return Response
*/
public function execute(BlockContextInterface $blockContext, Response $response = null);
/**
* @return string
*/
public function getName();
/**
* Define the default options for the block.
*
* NEXT_MAJOR: rename this method.
*
* @param OptionsResolverInterface $resolver
*
* @deprecated since version 2.3, to be renamed in 4.0.
* Use the method configureSettings instead.
* This method will be added to the BlockServiceInterface with SonataBlockBundle 4.0
*/
public function setDefaultSettings(OptionsResolverInterface $resolver);
/**
* @param BlockInterface $block
*/
public function load(BlockInterface $block);
/**
* @param string $media
*
* @return array
*/
public function getJavascripts($media);
/**
* @param string $media
*
* @return array
*/
public function getStylesheets($media);
/**
* @param BlockInterface $block
*
* @return array
*/
public function getCacheKeys(BlockInterface $block);
}

View File

@@ -0,0 +1,229 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Block;
use Psr\Log\LoggerInterface;
use Sonata\BlockBundle\Model\BlockInterface;
use Sonata\CoreBundle\Validator\ErrorElement;
use Symfony\Component\DependencyInjection\ContainerInterface;
class BlockServiceManager implements BlockServiceManagerInterface
{
/**
* @var array
*/
protected $services;
/**
* @var ContainerInterface
*/
protected $container;
/**
* @var bool
*/
protected $inValidate;
/**
* @var array
*/
protected $contexts;
/**
* @param ContainerInterface $container
* @param mixed $debug
* @param null|LoggerInterface $logger
*/
public function __construct(ContainerInterface $container, $debug, LoggerInterface $logger = null)
{
$this->services = [];
$this->contexts = [];
$this->container = $container;
}
/**
* {@inheritdoc}
*/
public function get(BlockInterface $block)
{
$this->load($block->getType());
return $this->services[$block->getType()];
}
/**
* {@inheritdoc}
*/
public function getService($id)
{
return $this->load($id);
}
/**
* {@inheritdoc}
*/
public function has($id)
{
return isset($this->services[$id]) ? true : false;
}
/**
* {@inheritdoc}
*/
public function add($name, $service, $contexts = [])
{
$this->services[$name] = $service;
foreach ($contexts as $context) {
if (!array_key_exists($context, $this->contexts)) {
$this->contexts[$context] = [];
}
$this->contexts[$context][] = $name;
}
}
/**
* {@inheritdoc}
*/
public function setServices(array $blockServices)
{
foreach ($blockServices as $name => $service) {
$this->add($name, $service);
}
}
/**
* {@inheritdoc}
*/
public function getServices()
{
foreach ($this->services as $name => $id) {
if (is_string($id)) {
$this->load($id);
}
}
return $this->sortServices($this->services);
}
/**
* {@inheritdoc}
*/
public function getServicesByContext($context, $includeContainers = true)
{
if (!array_key_exists($context, $this->contexts)) {
return [];
}
$services = [];
$containers = $this->container->getParameter('sonata.block.container.types');
foreach ($this->contexts[$context] as $name) {
if (!$includeContainers && in_array($name, $containers)) {
continue;
}
$services[$name] = $this->getService($name);
}
return $this->sortServices($services);
}
/**
* {@inheritdoc}
*/
public function getLoadedServices()
{
$services = [];
foreach ($this->services as $service) {
if (!$service instanceof BlockServiceInterface) {
continue;
}
$services[] = $service;
}
return $services;
}
/**
* @todo: this function should be remove into a proper statefull object
*
* {@inheritdoc}
*/
public function validate(ErrorElement $errorElement, BlockInterface $block)
{
if (!$block->getId() && !$block->getType()) {
return;
}
if ($this->inValidate) {
return;
}
// As block can be nested, we only need to validate the main block, no the children
try {
$this->inValidate = true;
$this->get($block)->validateBlock($errorElement, $block);
$this->inValidate = false;
} catch (\Exception $e) {
$this->inValidate = false;
}
}
/**
* @param string $type
*
* @throws \RuntimeException
*
* @return BlockServiceInterface
*/
private function load($type)
{
if (!$this->has($type)) {
throw new \RuntimeException(sprintf('The block service `%s` does not exist', $type));
}
if (!$this->services[$type] instanceof BlockServiceInterface) {
$this->services[$type] = $this->container->get($type);
}
if (!$this->services[$type] instanceof BlockServiceInterface) {
throw new \RuntimeException(sprintf('The service %s does not implement BlockServiceInterface', $type));
}
return $this->services[$type];
}
/**
* Sort alphabetically services.
*
* @param array $services
*
* @return array
*/
private function sortServices($services)
{
uasort($services, function ($a, $b) {
if ($a->getName() == $b->getName()) {
return 0;
}
return ($a->getName() < $b->getName()) ? -1 : 1;
});
return $services;
}
}

View File

@@ -0,0 +1,85 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Block;
use Sonata\BlockBundle\Model\BlockInterface;
use Sonata\CoreBundle\Validator\ErrorElement;
interface BlockServiceManagerInterface
{
/**
* @param string $name
* @param string $service
* @param array $contexts
*/
public function add($name, $service, $contexts = []);
/**
* Return the block service linked to the link.
*
* @param BlockInterface $block
*
* @return BlockServiceInterface
*/
public function get(BlockInterface $block);
/**
* NEXT_MAJOR: remove this method.
*
* @deprecated will be removed in 2.4, use the add method instead
*
* @param array $blockServices
*/
public function setServices(array $blockServices);
/**
* @return array
*/
public function getServices();
/**
* @param string $name
* @param bool $includeContainers
*
* @return array
*/
public function getServicesByContext($name, $includeContainers = true);
/**
* @param string $name
*
* @return bool
*/
public function has($name);
/**
* @param string $name
*
* @return BlockServiceInterface
*/
public function getService($name);
/**
* NEXT_MAJOR: remove this method.
*
* @deprecated will be removed in 2.4
*
* @return array
*/
public function getLoadedServices();
/**
* @param ErrorElement $errorElement
* @param BlockInterface $block
*/
public function validate(ErrorElement $errorElement, BlockInterface $block);
}

View File

@@ -0,0 +1,82 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Block\Loader;
use Sonata\BlockBundle\Block\BlockLoaderInterface;
use Sonata\BlockBundle\Model\Block;
class ServiceLoader implements BlockLoaderInterface
{
/**
* @var string[]
*/
protected $types;
/**
* @param string[] $types
*/
public function __construct(array $types)
{
$this->types = $types;
}
/**
* Check if a given block type exists.
*
* @param string $type Block type to check for
*
* @return bool
*/
public function exists($type)
{
return in_array($type, $this->types, true);
}
/**
* {@inheritdoc}
*/
public function load($configuration)
{
if (!in_array($configuration['type'], $this->types)) {
throw new \RuntimeException(sprintf(
'The block type "%s" does not exist',
$configuration['type']
));
}
$block = new Block();
$block->setId(uniqid());
$block->setType($configuration['type']);
$block->setEnabled(true);
$block->setCreatedAt(new \DateTime());
$block->setUpdatedAt(new \DateTime());
$block->setSettings(isset($configuration['settings']) ? $configuration['settings'] : []);
return $block;
}
/**
* {@inheritdoc}
*/
public function support($configuration)
{
if (!is_array($configuration)) {
return false;
}
if (!isset($configuration['type'])) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,106 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Block\Service;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\BlockBundle\Model\BlockInterface;
use Sonata\CoreBundle\Model\Metadata;
use Sonata\CoreBundle\Validator\ErrorElement;
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
/**
* @author Christian Gripp <mail@core23.de>
*/
abstract class AbstractAdminBlockService extends AbstractBlockService implements AdminBlockServiceInterface
{
/**
* @param string $name
* @param EngineInterface $templating
*/
public function __construct($name, EngineInterface $templating)
{
parent::__construct($name, $templating);
}
/**
* {@inheritdoc}
*/
public function buildCreateForm(FormMapper $formMapper, BlockInterface $block)
{
$this->buildEditForm($formMapper, $block);
}
/**
* @param BlockInterface $block
*/
public function prePersist(BlockInterface $block)
{
}
/**
* @param BlockInterface $block
*/
public function postPersist(BlockInterface $block)
{
}
/**
* @param BlockInterface $block
*/
public function preUpdate(BlockInterface $block)
{
}
/**
* @param BlockInterface $block
*/
public function postUpdate(BlockInterface $block)
{
}
/**
* @param BlockInterface $block
*/
public function preRemove(BlockInterface $block)
{
}
/**
* @param BlockInterface $block
*/
public function postRemove(BlockInterface $block)
{
}
/**
* {@inheritdoc}
*/
public function buildEditForm(FormMapper $form, BlockInterface $block)
{
}
/**
* @param ErrorElement $errorElement
* @param BlockInterface $block
*/
public function validateBlock(ErrorElement $errorElement, BlockInterface $block)
{
}
/**
* {@inheritdoc}
*/
public function getBlockMetadata($code = null)
{
return new Metadata($this->getName(), (null !== $code ? $code : $this->getName()), false, 'SonataBlockBundle', ['class' => 'fa fa-file']);
}
}

View File

@@ -0,0 +1,162 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Block\Service;
use Sonata\BlockBundle\Block\BlockContextInterface;
use Sonata\BlockBundle\Model\BlockInterface;
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
/**
* @author Sullivan Senechal <soullivaneuh@gmail.com>
*/
abstract class AbstractBlockService implements BlockServiceInterface
{
/**
* @var string|null
*/
protected $name;
/**
* @var EngineInterface|null
*/
protected $templating;
/**
* @param string $name
* @param EngineInterface $templating
*/
public function __construct($name = null, EngineInterface $templating = null)
{
if (null === $name || null === $templating) {
@trigger_error(
'The $name and $templating parameters will be required fields with the 4.0 release.',
E_USER_DEPRECATED
);
}
$this->name = $name;
$this->templating = $templating;
}
/**
* Returns a Response object than can be cacheable.
*
* @param string $view
* @param array $parameters
* @param Response $response
*
* @return Response
*/
public function renderResponse($view, array $parameters = [], Response $response = null)
{
return $this->getTemplating()->renderResponse($view, $parameters, $response);
}
/**
* Returns a Response object that cannot be cacheable, this must be used if the Response is related to the user.
* A good solution to make the page cacheable is to configure the block to be cached with javascript ...
*
* @param string $view
* @param array $parameters
* @param Response $response
*
* @return Response
*/
public function renderPrivateResponse($view, array $parameters = [], Response $response = null)
{
return $this->renderResponse($view, $parameters, $response)
->setTtl(0)
->setPrivate()
;
}
/**
* {@inheritdoc}
*/
public function setDefaultSettings(OptionsResolverInterface $resolver)
{
$this->configureSettings($resolver);
}
/**
* Define the default options for the block.
*
* @param OptionsResolver $resolver
*/
public function configureSettings(OptionsResolver $resolver)
{
}
/**
* {@inheritdoc}
*/
public function getCacheKeys(BlockInterface $block)
{
return [
'block_id' => $block->getId(),
'updated_at' => $block->getUpdatedAt() ? $block->getUpdatedAt()->format('U') : strtotime('now'),
];
}
/**
* {@inheritdoc}
*/
public function load(BlockInterface $block)
{
}
/**
* {@inheritdoc}
*/
public function getJavascripts($media)
{
return [];
}
/**
* {@inheritdoc}
*/
public function getStylesheets($media)
{
return [];
}
/**
* {@inheritdoc}
*/
public function execute(BlockContextInterface $blockContext, Response $response = null)
{
return $this->renderResponse($blockContext->getTemplate(), [
'block_context' => $blockContext,
'block' => $blockContext->getBlock(),
], $response);
}
/**
* {@inheritdoc}
*/
public function getName()
{
return $this->name;
}
/**
* {@inheritdoc}
*/
public function getTemplating()
{
return $this->templating;
}
}

View File

@@ -0,0 +1,48 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Block\Service;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\BlockBundle\Model\BlockInterface;
use Sonata\CoreBundle\Model\MetadataInterface;
use Sonata\CoreBundle\Validator\ErrorElement;
/**
* @author Christian Gripp <mail@core23.de>
*/
interface AdminBlockServiceInterface extends BlockServiceInterface
{
/**
* @param FormMapper $form
* @param BlockInterface $block
*/
public function buildEditForm(FormMapper $form, BlockInterface $block);
/**
* @param FormMapper $form
* @param BlockInterface $block
*/
public function buildCreateForm(FormMapper $form, BlockInterface $block);
/**
* @param ErrorElement $errorElement
* @param BlockInterface $block
*/
public function validateBlock(ErrorElement $errorElement, BlockInterface $block);
/**
* @param string|null $code
*
* @return MetadataInterface
*/
public function getBlockMetadata($code = null);
}

View File

@@ -0,0 +1,19 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Block\Service;
/**
* @author Christian Gripp <mail@core23.de>
*/
interface BlockServiceInterface extends \Sonata\BlockBundle\Block\BlockServiceInterface
{
}

View File

@@ -0,0 +1,124 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Block\Service;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\BlockBundle\Block\BlockContextInterface;
use Sonata\BlockBundle\Form\Type\ContainerTemplateType;
use Sonata\BlockBundle\Model\BlockInterface;
use Sonata\CoreBundle\Form\Type\ImmutableArrayType;
use Sonata\CoreBundle\Model\Metadata;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* Render children pages.
*
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
class ContainerBlockService extends AbstractAdminBlockService
{
/**
* {@inheritdoc}
*/
public function buildEditForm(FormMapper $formMapper, BlockInterface $block)
{
$formMapper->add('enabled');
$formMapper->add('settings', ImmutableArrayType::class, [
'keys' => [
['code', TextType::class, [
'required' => false,
'label' => 'form.label_code',
]],
['layout', TextareaType::class, [
'label' => 'form.label_layout',
]],
['class', TextType::class, [
'required' => false,
'label' => 'form.label_class',
]],
['template', ContainerTemplateType::class, [
'label' => 'form.label_template',
]],
],
'translation_domain' => 'SonataBlockBundle',
]);
$formMapper->add('children', 'sonata_type_collection', [], [
'admin_code' => 'sonata.page.admin.block',
'edit' => 'inline',
'inline' => 'table',
'sortable' => 'position',
]);
}
/**
* {@inheritdoc}
*/
public function execute(BlockContextInterface $blockContext, Response $response = null)
{
return $this->renderResponse($blockContext->getTemplate(), [
'block' => $blockContext->getBlock(),
'decorator' => $this->getDecorator($blockContext->getSetting('layout')),
'settings' => $blockContext->getSettings(),
], $response);
}
/**
* {@inheritdoc}
*/
public function configureSettings(OptionsResolver $resolver)
{
$resolver->setDefaults([
'code' => '',
'layout' => '{{ CONTENT }}',
'class' => '',
'template' => '@SonataBlock/Block/block_container.html.twig',
]);
}
/**
* {@inheritdoc}
*/
public function getBlockMetadata($code = null)
{
return new Metadata($this->getName(), (null !== $code ? $code : $this->getName()), false, 'SonataBlockBundle', [
'class' => 'fa fa-square-o',
]);
}
/**
* Returns a decorator object/array from the container layout setting.
*
* @param string $layout
*
* @return array
*/
protected function getDecorator($layout)
{
$key = '{{ CONTENT }}';
if (false === strpos($layout, $key)) {
return [];
}
$segments = explode($key, $layout);
$decorator = [
'pre' => isset($segments[0]) ? $segments[0] : '',
'post' => isset($segments[1]) ? $segments[1] : '',
];
return $decorator;
}
}

View File

@@ -0,0 +1,49 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Block\Service;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\BlockBundle\Block\BlockContextInterface;
use Sonata\BlockBundle\Model\BlockInterface;
use Sonata\CoreBundle\Validator\ErrorElement;
use Symfony\Component\HttpFoundation\Response;
class EmptyBlockService extends AbstractBlockService
{
// NEXT_MAJOR: Remove this method
/**
* {@inheritdoc}
*/
public function buildEditForm(FormMapper $form, BlockInterface $block)
{
throw new \RuntimeException('Not used, this block renders an empty result if no block document can be found');
}
// NEXT_MAJOR: Remove this method
/**
* {@inheritdoc}
*/
public function validateBlock(ErrorElement $errorElement, BlockInterface $block)
{
throw new \RuntimeException('Not used, this block renders an empty result if no block document can be found');
}
/**
* {@inheritdoc}
*/
public function execute(BlockContextInterface $blockContext, Response $response = null)
{
return new Response();
}
}

View File

@@ -0,0 +1,263 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Block\Service;
use Knp\Menu\ItemInterface;
use Knp\Menu\Provider\MenuProviderInterface;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\BlockBundle\Block\BlockContextInterface;
use Sonata\BlockBundle\Menu\MenuRegistry;
use Sonata\BlockBundle\Menu\MenuRegistryInterface;
use Sonata\BlockBundle\Model\BlockInterface;
use Sonata\CoreBundle\Form\Type\ImmutableArrayType;
use Sonata\CoreBundle\Model\Metadata;
use Sonata\CoreBundle\Validator\ErrorElement;
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormTypeInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* @author Hugo Briand <briand@ekino.com>
*/
class MenuBlockService extends AbstractAdminBlockService
{
/**
* @var MenuProviderInterface
*/
protected $menuProvider;
/**
* NEXT_MAJOR: remove property.
*
* @var array
*
* @deprecated since 3.3, to be removed in 4.0
*/
protected $menus;
/**
* @var MenuRegistryInterface
*/
protected $menuRegistry;
/**
* @param string $name
* @param EngineInterface $templating
* @param MenuProviderInterface $menuProvider
* @param MenuRegistryInterface|null $menuRegistry
*/
public function __construct($name, EngineInterface $templating, MenuProviderInterface $menuProvider, $menuRegistry = null)
{
parent::__construct($name, $templating);
$this->menuProvider = $menuProvider;
if ($menuRegistry instanceof MenuRegistryInterface) {
$this->menuRegistry = $menuRegistry;
} elseif (null === $menuRegistry) {
$this->menuRegistry = new MenuRegistry();
} elseif (is_array($menuRegistry)) { //NEXT_MAJOR: Remove this case
@trigger_error(
'Initializing '.__CLASS__.' with an array parameter is deprecated since 3.3 and will be removed in 4.0.',
E_USER_DEPRECATED
);
$this->menuRegistry = new MenuRegistry();
foreach ($menuRegistry as $menu) {
$this->menuRegistry->add($menu);
}
} else {
throw new \InvalidArgumentException(sprintf(
'MenuRegistry must be either null or instance of %s',
MenuRegistryInterface::class
));
}
}
/**
* {@inheritdoc}
*/
public function execute(BlockContextInterface $blockContext, Response $response = null)
{
$responseSettings = [
'menu' => $this->getMenu($blockContext),
'menu_options' => $this->getMenuOptions($blockContext->getSettings()),
'block' => $blockContext->getBlock(),
'context' => $blockContext,
];
if ('private' === $blockContext->getSetting('cache_policy')) {
return $this->renderPrivateResponse($blockContext->getTemplate(), $responseSettings, $response);
}
return $this->renderResponse($blockContext->getTemplate(), $responseSettings, $response);
}
/**
* {@inheritdoc}
*/
public function buildEditForm(FormMapper $form, BlockInterface $block)
{
$form->add('settings', ImmutableArrayType::class, [
'keys' => $this->getFormSettingsKeys(),
'translation_domain' => 'SonataBlockBundle',
]);
}
/**
* {@inheritdoc}
*/
public function validateBlock(ErrorElement $errorElement, BlockInterface $block)
{
if (($name = $block->getSetting('menu_name')) && '' !== $name && !$this->menuProvider->has($name)) {
// If we specified a menu_name, check that it exists
$errorElement->with('menu_name')
->addViolation('sonata.block.menu.not_existing', ['%name%' => $name])
->end();
}
}
/**
* {@inheritdoc}
*/
public function configureSettings(OptionsResolver $resolver)
{
$resolver->setDefaults([
'title' => $this->getName(),
'cache_policy' => 'public',
'template' => '@SonataBlock/Block/block_core_menu.html.twig',
'menu_name' => '',
'safe_labels' => false,
'current_class' => 'active',
'first_class' => false,
'last_class' => false,
'current_uri' => null,
'menu_class' => 'list-group',
'children_class' => 'list-group-item',
'menu_template' => null,
]);
}
/**
* {@inheritdoc}
*/
public function getBlockMetadata($code = null)
{
return new Metadata($this->getName(), (null !== $code ? $code : $this->getName()), false, 'SonataBlockBundle', [
'class' => 'fa fa-bars',
]);
}
/**
* @return array
*/
protected function getFormSettingsKeys()
{
$choiceOptions = [
'required' => false,
'label' => 'form.label_url',
'choice_translation_domain' => 'SonataBlockBundle',
];
// choice_as_value options is not needed in SF 3.0+
if (method_exists(FormTypeInterface::class, 'setDefaultOptions')) {
$choiceOptions['choices_as_values'] = true;
}
$choiceOptions['choices'] = array_flip($this->menuRegistry->getAliasNames());
return [
['title', TextType::class, [
'required' => false,
'label' => 'form.label_title',
]],
['cache_policy', ChoiceType::class, [
'label' => 'form.label_cache_policy',
'choices' => ['public', 'private'],
]],
['menu_name', ChoiceType::class, $choiceOptions],
['safe_labels', CheckboxType::class, [
'required' => false,
'label' => 'form.label_safe_labels',
]],
['current_class', TextType::class, [
'required' => false,
'label' => 'form.label_current_class',
]],
['first_class', TextType::class, [
'required' => false,
'label' => 'form.label_first_class',
]],
['last_class', TextType::class, [
'required' => false,
'label' => 'form.label_last_class',
]],
['menu_class', TextType::class, [
'required' => false,
'label' => 'form.label_menu_class',
]],
['children_class', TextType::class, [
'required' => false,
'label' => 'form.label_children_class',
]],
['menu_template', TextType::class, [
'required' => false,
'label' => 'form.label_menu_template',
]],
];
}
/**
* Gets the menu to render.
*
* @param BlockContextInterface $blockContext
*
* @return ItemInterface|string
*/
protected function getMenu(BlockContextInterface $blockContext)
{
$settings = $blockContext->getSettings();
return $settings['menu_name'];
}
/**
* Replaces setting keys with knp menu item options keys.
*
* @param array $settings
*
* @return array
*/
protected function getMenuOptions(array $settings)
{
$mapping = [
'current_class' => 'currentClass',
'first_class' => 'firstClass',
'last_class' => 'lastClass',
'safe_labels' => 'allow_safe_labels',
'menu_template' => 'template',
];
$options = [];
foreach ($settings as $key => $value) {
if (array_key_exists($key, $mapping) && null !== $value) {
$options[$mapping[$key]] = $value;
}
}
return $options;
}
}

View File

@@ -0,0 +1,141 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Block\Service;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\BlockBundle\Block\BlockContextInterface;
use Sonata\BlockBundle\Model\BlockInterface;
use Sonata\CoreBundle\Form\Type\ImmutableArrayType;
use Sonata\CoreBundle\Model\Metadata;
use Sonata\CoreBundle\Validator\ErrorElement;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\UrlType;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
class RssBlockService extends AbstractAdminBlockService
{
/**
* {@inheritdoc}
*/
public function configureSettings(OptionsResolver $resolver)
{
$resolver->setDefaults([
'url' => false,
'title' => null,
'translation_domain' => null,
'icon' => 'fa fa-rss-square',
'class' => null,
'template' => '@SonataBlock/Block/block_core_rss.html.twig',
]);
}
/**
* {@inheritdoc}
*/
public function buildEditForm(FormMapper $formMapper, BlockInterface $block)
{
$formMapper->add('settings', ImmutableArrayType::class, [
'keys' => [
['url', UrlType::class, [
'required' => false,
'label' => 'form.label_url',
]],
['title', TextType::class, [
'label' => 'form.label_title',
'required' => false,
]],
['translation_domain', TextType::class, [
'label' => 'form.label_translation_domain',
'required' => false,
]],
['icon', TextType::class, [
'label' => 'form.label_icon',
'required' => false,
]],
['class', TextType::class, [
'label' => 'form.label_class',
'required' => false,
]],
],
'translation_domain' => 'SonataBlockBundle',
]);
}
/**
* {@inheritdoc}
*/
public function validateBlock(ErrorElement $errorElement, BlockInterface $block)
{
$errorElement
->with('settings[url]')
->assertNotNull([])
->assertNotBlank()
->end()
->with('settings[title]')
->assertNotNull([])
->assertNotBlank()
->assertLength(['max' => 50])
->end();
}
/**
* {@inheritdoc}
*/
public function execute(BlockContextInterface $blockContext, Response $response = null)
{
// merge settings
$settings = $blockContext->getSettings();
$feeds = false;
if ($settings['url']) {
$options = [
'http' => [
'user_agent' => 'Sonata/RSS Reader',
'timeout' => 2,
],
];
// retrieve contents with a specific stream context to avoid php errors
$content = @file_get_contents($settings['url'], false, stream_context_create($options));
if ($content) {
// generate a simple xml element
try {
$feeds = new \SimpleXMLElement($content);
$feeds = $feeds->channel->item;
} catch (\Exception $e) {
// silently fail error
}
}
}
return $this->renderResponse($blockContext->getTemplate(), [
'feeds' => $feeds,
'block' => $blockContext->getBlock(),
'settings' => $settings,
], $response);
}
/**
* {@inheritdoc}
*/
public function getBlockMetadata($code = null)
{
return new Metadata($this->getName(), (null !== $code ? $code : $this->getName()), false, 'SonataBlockBundle', [
'class' => 'fa fa-rss-square',
]);
}
}

View File

@@ -0,0 +1,72 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Block\Service;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\BlockBundle\Block\BlockContextInterface;
use Sonata\BlockBundle\Model\BlockInterface;
use Sonata\CoreBundle\Form\Type\ImmutableArrayType;
use Sonata\CoreBundle\Model\Metadata;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
class TemplateBlockService extends AbstractAdminBlockService
{
/**
* {@inheritdoc}
*/
public function execute(BlockContextInterface $blockContext, Response $response = null)
{
return $this->renderResponse($blockContext->getTemplate(), [
'block' => $blockContext->getBlock(),
'settings' => $blockContext->getSettings(),
], $response);
}
/**
* {@inheritdoc}
*/
public function buildEditForm(FormMapper $formMapper, BlockInterface $block)
{
$formMapper->add('settings', ImmutableArrayType::class, [
'keys' => [
['template', null, [
'label' => 'form.label_template',
]],
],
'translation_domain' => 'SonataBlockBundle',
]);
}
/**
* {@inheritdoc}
*/
public function configureSettings(OptionsResolver $resolver)
{
$resolver->setDefaults([
'template' => '@SonataBlock/Block/block_template.html.twig',
]);
}
/**
* {@inheritdoc}
*/
public function getBlockMetadata($code = null)
{
return new Metadata($this->getName(), (null !== $code ? $code : $this->getName()), false, 'SonataBlockBundle', [
'class' => 'fa fa-code',
]);
}
}

View File

@@ -0,0 +1,74 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Block\Service;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\BlockBundle\Block\BlockContextInterface;
use Sonata\BlockBundle\Model\BlockInterface;
use Sonata\CoreBundle\Form\Type\ImmutableArrayType;
use Sonata\CoreBundle\Model\Metadata;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
class TextBlockService extends AbstractAdminBlockService
{
/**
* {@inheritdoc}
*/
public function execute(BlockContextInterface $blockContext, Response $response = null)
{
return $this->renderResponse($blockContext->getTemplate(), [
'block' => $blockContext->getBlock(),
'settings' => $blockContext->getSettings(),
], $response);
}
/**
* {@inheritdoc}
*/
public function buildEditForm(FormMapper $formMapper, BlockInterface $block)
{
$formMapper->add('settings', ImmutableArrayType::class, [
'keys' => [
['content', TextareaType::class, [
'label' => 'form.label_content',
]],
],
'translation_domain' => 'SonataBlockBundle',
]);
}
/**
* {@inheritdoc}
*/
public function configureSettings(OptionsResolver $resolver)
{
$resolver->setDefaults([
'content' => 'Insert your custom content here',
'template' => '@SonataBlock/Block/block_core_text.html.twig',
]);
}
/**
* {@inheritdoc}
*/
public function getBlockMetadata($code = null)
{
return new Metadata($this->getName(), (null !== $code ? $code : $this->getName()), false, 'SonataBlockBundle', [
'class' => 'fa fa-file-text-o',
]);
}
}

View File

@@ -0,0 +1,68 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Cache;
use Sonata\BlockBundle\Block\BlockContextInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
class HttpCacheHandler implements HttpCacheHandlerInterface
{
/**
* @var int|null
*/
protected $currentTtl = null;
/**
* {@inheritdoc}
*/
public function alterResponse(Response $response)
{
if (!$response->isCacheable()) {
// the controller flags the response as private so we keep it private!
return;
}
// no block has been rendered
if (null === $this->currentTtl) {
return;
}
// a block has a lower ttl that the current response, so we update the ttl to match
// the one provided in the block
if ($this->currentTtl < $response->getTtl()) {
$response->setTtl($this->currentTtl);
}
}
/**
* {@inheritdoc}
*/
public function updateMetadata(Response $response, BlockContextInterface $blockContext = null)
{
if (null === $this->currentTtl) {
$this->currentTtl = $response->getTtl();
}
if (null !== $response->isCacheable() && $response->getTtl() < $this->currentTtl) {
$this->currentTtl = $response->getTtl();
}
}
/**
* {@inheritdoc}
*/
public function onKernelResponse(FilterResponseEvent $event)
{
$this->alterResponse($event->getResponse());
}
}

View File

@@ -0,0 +1,45 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Cache;
use Sonata\BlockBundle\Block\BlockContextInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
interface HttpCacheHandlerInterface
{
/**
* Add valid http cache information.
*
* The Response object is the final object returned to the client
*
* @param Response $response
*/
public function alterResponse(Response $response);
/**
* This function can update a state to store the final ttl used for the page
* The response object point to the Response generated by a block service and
* not the final response rendered to the client.
*
* @param Response $response
* @param BlockContextInterface $blockContext
*/
public function updateMetadata(Response $response, BlockContextInterface $blockContext = null);
/**
* @param FilterResponseEvent $event
*
* @return mixed
*/
public function onKernelResponse(FilterResponseEvent $event);
}

View File

@@ -0,0 +1,40 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Cache;
use Sonata\BlockBundle\Block\BlockContextInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
class NoopHttpCacheHandler implements HttpCacheHandlerInterface
{
/**
* {@inheritdoc}
*/
public function alterResponse(Response $response)
{
}
/**
* {@inheritdoc}
*/
public function updateMetadata(Response $response, BlockContextInterface $blockContext = null)
{
}
/**
* {@inheritdoc}
*/
public function onKernelResponse(FilterResponseEvent $event)
{
}
}

View File

@@ -0,0 +1,26 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Command;
use Sonata\BlockBundle\Block\BlockServiceManagerInterface;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
abstract class BaseCommand extends ContainerAwareCommand
{
/**
* @return BlockServiceManagerInterface
*/
public function getBlockServiceManager()
{
return $this->getContainer()->get('sonata.block.manager');
}
}

View File

@@ -0,0 +1,65 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class DebugBlocksCommand extends BaseCommand
{
/**
* {@inheritdoc}
*/
public function configure()
{
$this->setName('sonata:block:debug');
$this->setDescription('Debug all blocks available, show default settings of each block');
$this->addOption('context', 'c', InputOption::VALUE_REQUIRED, 'display service for the specified context');
}
/**
* {@inheritdoc}
*/
public function execute(InputInterface $input, OutputInterface $output)
{
if ($input->getOption('context')) {
$services = $this->getBlockServiceManager()->getServicesByContext($input->getOption('context'));
} else {
$services = $this->getBlockServiceManager()->getServices();
}
foreach ($services as $code => $service) {
$resolver = new OptionsResolver();
// NEXT_MAJOR: Remove this check
if (method_exists($service, 'configureSettings')) {
$service->configureSettings($resolver);
} else {
$service->setDefaultSettings($resolver);
}
$settings = $resolver->resolve();
$output->writeln('');
$output->writeln(sprintf('<info>>> %s</info> (<comment>%s</comment>)', $service->getName(), $code));
foreach ($settings as $key => $val) {
$output->writeln(sprintf(' %-30s%s', $key, json_encode($val)));
}
}
$output->writeln('done!');
}
}

View File

@@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* GlobalVariablesCompilerPass.
*
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
class GlobalVariablesCompilerPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
$container->getDefinition('twig')
->addMethodCall('addGlobal', ['sonata_block', new Reference('sonata.block.twig.global')]);
}
}

View File

@@ -0,0 +1,102 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* Link the block service to the Page Manager.
*
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
class TweakCompilerPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
$manager = $container->getDefinition('sonata.block.manager');
$registry = $container->getDefinition('sonata.block.menu.registry');
$parameters = $container->getParameter('sonata_block.blocks');
foreach ($container->findTaggedServiceIds('sonata.block') as $id => $tags) {
$definition = $container->getDefinition($id);
$definition->setPublic(true);
$arguments = $definition->getArguments();
// Replace empty block id with service id
if (empty($arguments) || 0 == strlen($arguments[0])) {
// NEXT_MAJOR: Remove the condition when Symfony 2.8 support will be dropped.
if (method_exists($definition, 'setArgument')) {
$definition->setArgument(0, $id);
} else {
$definition->replaceArgument(0, $id);
}
} elseif ($id != $arguments[0] && 0 !== strpos(
$container->getParameterBag()->resolveValue($definition->getClass()),
'Sonata\\BlockBundle\\Block\\Service\\'
)) {
// NEXT_MAJOR: Remove deprecation notice
@trigger_error(
sprintf('Using service id %s different from block id %s is deprecated since 3.3 and will be removed in 4.0.', $id, $arguments[0]),
E_USER_DEPRECATED
);
}
$manager->addMethodCall('add', [$id, $id, isset($parameters[$id]) ? $parameters[$id]['contexts'] : []]);
}
foreach ($container->findTaggedServiceIds('knp_menu.menu') as $id => $tags) {
foreach ($tags as $attributes) {
if (empty($attributes['alias'])) {
throw new \InvalidArgumentException(sprintf('The alias is not defined in the "knp_menu.menu" tag for the service "%s"', $id));
}
$registry->addMethodCall('add', [$attributes['alias']]);
}
}
$services = [];
foreach ($container->findTaggedServiceIds('sonata.block.loader') as $id => $tags) {
$services[] = new Reference($id);
}
$container->getDefinition('sonata.block.loader.chain')->replaceArgument(0, $services);
$this->applyContext($container);
}
/**
* Apply configurations to the context manager.
*
* @param ContainerBuilder $container
*/
public function applyContext(ContainerBuilder $container)
{
$definition = $container->findDefinition('sonata.block.context_manager');
foreach ($container->getParameter('sonata_block.blocks') as $service => $settings) {
if (count($settings['settings']) > 0) {
$definition->addMethodCall('addSettingsByType', [$service, $settings['settings'], true]);
}
}
foreach ($container->getParameter('sonata_block.blocks_by_class') as $class => $settings) {
if (count($settings['settings']) > 0) {
$definition->addMethodCall('addSettingsByClass', [$class, $settings['settings'], true]);
}
}
}
}

View File

@@ -0,0 +1,246 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* This is the class that validates and merges configuration from your app/config files.
*
* To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html#cookbook-bundles-extension-config-class}
*/
class Configuration implements ConfigurationInterface
{
/**
* @var array
*/
protected $defaultContainerTemplates;
/**
* @param array $defaultContainerTemplates
*/
public function __construct(array $defaultContainerTemplates)
{
$this->defaultContainerTemplates = $defaultContainerTemplates;
}
/**
* {@inheritdoc}
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$node = $treeBuilder->root('sonata_block');
$node
->fixXmlConfig('default_context')
->fixXmlConfig('template')
->fixXmlConfig('block')
->fixXmlConfig('block_by_class')
->validate()
->always(function ($value) {
foreach ($value['blocks'] as $name => &$block) {
if (0 == count($block['contexts'])) {
$block['contexts'] = $value['default_contexts'];
}
}
if (isset($value['profiler']['container_types']) && !empty($value['profiler']['container_types'])
&& isset($value['container']['types']) && !empty($value['container']['types'])
&& 0 !== count(array_diff($value['profiler']['container_types'], $value['container']['types']))) {
throw new \RuntimeException('You cannot have different config options for sonata_block.profiler.container_types and sonata_block.container.types; the first one is deprecated, in case of doubt use the latter');
}
return $value;
})
->end()
->children()
->arrayNode('profiler')
->addDefaultsIfNotSet()
->fixXmlConfig('container_type', 'container_types')
->children()
->scalarNode('enabled')->defaultValue('%kernel.debug%')->end()
->scalarNode('template')->defaultValue('@SonataBlock/Profiler/block.html.twig')->end()
->arrayNode('container_types')
->isRequired()
// add default value to well know users of BlockBundle
->defaultValue(['sonata.block.service.container', 'sonata.page.block.container', 'sonata.dashboard.block.container', 'cmf.block.container', 'cmf.block.slideshow'])
->prototype('scalar')->end()
->end()
->end()
->end()
->arrayNode('default_contexts')
->prototype('scalar')->end()
->end()
->scalarNode('context_manager')->defaultValue('sonata.block.context_manager.default')->end()
->arrayNode('http_cache')
->addDefaultsIfNotSet()
->children()
->scalarNode('handler')->defaultValue('sonata.block.cache.handler.default')->end()
->booleanNode('listener')->defaultTrue()->end()
->end()
->end()
->arrayNode('templates')
->addDefaultsIfNotSet()
->children()
->scalarNode('block_base')->defaultNull()->end()
->scalarNode('block_container')->defaultNull()->end()
->end()
->end()
->arrayNode('container')
->info('block container configuration')
->addDefaultsIfNotSet()
->fixXmlConfig('type', 'types')
->fixXmlConfig('template', 'templates')
->children()
->arrayNode('types')
->info('container service ids')
->isRequired()
// add default value to well know users of BlockBundle
->defaultValue(['sonata.block.service.container', 'sonata.page.block.container', 'sonata.dashboard.block.container', 'cmf.block.container', 'cmf.block.slideshow'])
->prototype('scalar')->end()
->end()
->arrayNode('templates')
->info('container templates')
->isRequired()
->defaultValue($this->defaultContainerTemplates)
->prototype('scalar')->end()
->end()
->end()
->end()
->arrayNode('blocks')
->info('configuration per block service')
->useAttributeAsKey('id')
->prototype('array')
->fixXmlConfig('context')
->fixXmlConfig('template')
->fixXmlConfig('setting')
->children()
->arrayNode('contexts')
->prototype('scalar')->end()
->end()
->arrayNode('templates')
->prototype('array')
->children()
->scalarNode('name')->cannotBeEmpty()->end()
->scalarNode('template')->cannotBeEmpty()->end()
->end()
->end()
->end()
->scalarNode('cache')->defaultValue('sonata.cache.noop')->end()
->arrayNode('settings')
->info('default settings')
->useAttributeAsKey('id')
->prototype('scalar')->end()
->end()
->arrayNode('exception')
->children()
->scalarNode('filter')->defaultNull()->end()
->scalarNode('renderer')->defaultNull()->end()
->end()
->end()
->end()
->end()
->end()
->arrayNode('menus')
->info('KNP Menus available in sonata.block.menu block configuration')
->useAttributeAsKey('id')
->prototype('scalar')->end()
->validate()
->always(function ($value) {
if (count($value) > 0) {
@trigger_error(
'The menus configuration key is deprecated since 3.3 and will be removed in 4.0.',
E_USER_DEPRECATED
);
}
return $value;
})
->end()
->end()
->arrayNode('blocks_by_class')
->info('configuration per block class')
->useAttributeAsKey('class')
->prototype('array')
->fixXmlConfig('setting')
->children()
->scalarNode('cache')->defaultValue('sonata.cache.noop')->end()
->arrayNode('settings')
->info('default settings')
->useAttributeAsKey('id')
->prototype('scalar')->end()
->end()
->end()
->end()
->end()
->arrayNode('exception')
->addDefaultsIfNotSet()
->fixXmlConfig('filter')
->fixXmlConfig('renderer')
->children()
->arrayNode('default')
->addDefaultsIfNotSet()
->children()
->scalarNode('filter')->defaultValue('debug_only')->end()
->scalarNode('renderer')->defaultValue('throw')->end()
->end()
->end()
->arrayNode('filters')
->useAttributeAsKey('id')
->prototype('scalar')->end()
->defaultValue([
'debug_only' => 'sonata.block.exception.filter.debug_only',
'ignore_block_exception' => 'sonata.block.exception.filter.ignore_block_exception',
'keep_all' => 'sonata.block.exception.filter.keep_all',
'keep_none' => 'sonata.block.exception.filter.keep_none',
])
->end()
->arrayNode('renderers')
->useAttributeAsKey('id')
->prototype('scalar')->end()
->defaultValue([
'inline' => 'sonata.block.exception.renderer.inline',
'inline_debug' => 'sonata.block.exception.renderer.inline_debug',
'throw' => 'sonata.block.exception.renderer.throw',
])
->end()
->end()
->end()
->end()
;
return $treeBuilder;
}
/**
* @param array $config
* @param ContainerBuilder $container
*
* @return Configuration
*/
public function getConfiguration(array $config, ContainerBuilder $container)
{
return new self([]);
}
}

View File

@@ -0,0 +1,323 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Processor;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
/**
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
class SonataBlockExtension extends Extension
{
/**
* {@inheritdoc}
*/
final public function getConfiguration(array $config, ContainerBuilder $container)
{
$bundles = $container->getParameter('kernel.bundles');
$defaultTemplates = [];
if (isset($bundles['SonataPageBundle'])) {
$defaultTemplates['@SonataPage/Block/block_container.html.twig'] = 'SonataPageBundle default template';
} else {
$defaultTemplates['@SonataBlock/Block/block_container.html.twig'] = 'SonataBlockBundle default template';
}
if (isset($bundles['SonataSeoBundle'])) {
$defaultTemplates['@SonataSeo/Block/block_social_container.html.twig'] = 'SonataSeoBundle (to contain social buttons)';
}
return new Configuration($defaultTemplates);
}
/**
* {@inheritdoc}
*/
public function load(array $configs, ContainerBuilder $container)
{
$bundles = $container->getParameter('kernel.bundles');
$processor = new Processor();
$configuration = $this->getConfiguration($configs, $container);
$config = $processor->processConfiguration($configuration, $configs);
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('block.xml');
$loader->load('form.xml');
$loader->load('core.xml');
$loader->load('exception.xml');
$loader->load('commands.xml');
$this->fixConfigurationDeprecation($config);
$this->configureBlockContainers($container, $config);
$this->configureContext($container, $config);
$this->configureLoaderChain($container, $config);
$this->configureCache($container, $config);
$this->configureForm($container, $config);
$this->configureProfiler($container, $config);
$this->configureException($container, $config);
$this->configureMenus($container, $config);
if (\PHP_VERSION_ID < 70000) {
$this->configureClassesToCompile();
}
if ($config['templates']['block_base'] === null) {
if (isset($bundles['SonataPageBundle'])) {
$config['templates']['block_base'] = '@SonataPage/Block/block_base.html.twig';
$config['templates']['block_container'] = '@SonataPage/Block/block_container.html.twig';
} else {
$config['templates']['block_base'] = '@SonataBlock/Block/block_base.html.twig';
$config['templates']['block_container'] = '@SonataBlock/Block/block_container.html.twig';
}
}
$container->getDefinition('sonata.block.twig.global')->replaceArgument(0, $config['templates']);
}
/**
* @param ContainerBuilder $container
* @param array $config
*/
public function configureBlockContainers(ContainerBuilder $container, array $config)
{
$container->setParameter('sonata.block.container.types', $config['container']['types']);
$container->getDefinition('sonata.block.form.type.container_template')->replaceArgument(0, $config['container']['templates']);
}
/**
* @param array $config
*/
public function fixConfigurationDeprecation(array &$config)
{
if (count(array_diff($config['profiler']['container_types'], $config['container']['types']))) {
$config['container']['types'] = array_merge($config['profiler']['container_types'], $config['container']['types']);
}
}
/**
* @param ContainerBuilder $container
* @param array $config
*/
public function configureMenus(ContainerBuilder $container, array $config)
{
$bundles = $container->getParameter('kernel.bundles');
if (!isset($bundles['KnpMenuBundle'])) {
$container->removeDefinition('sonata.block.service.menu');
return;
}
$container->getDefinition('sonata.block.menu.registry')->replaceArgument(0, $config['menus']);
}
/**
* @param ContainerBuilder $container
* @param array $config
*/
public function configureContext(ContainerBuilder $container, array $config)
{
$container->setParameter($this->getAlias().'.blocks', $config['blocks']);
$container->setParameter($this->getAlias().'.blocks_by_class', $config['blocks_by_class']);
$container->setAlias('sonata.block.context_manager', $config['context_manager']);
}
/**
* @param ContainerBuilder $container
* @param array $config
*/
public function configureCache(ContainerBuilder $container, array $config)
{
$container->setAlias('sonata.block.cache.handler', $config['http_cache']['handler']);
if ($config['http_cache']['listener']) {
$container->getDefinition($config['http_cache']['handler'])
->addTag('kernel.event_listener', ['event' => 'kernel.response', 'method' => 'onKernelResponse']);
}
$cacheBlocks = [];
foreach ($config['blocks'] as $service => $settings) {
$cacheBlocks['by_type'][$service] = $settings['cache'];
}
foreach ($config['blocks_by_class'] as $class => $settings) {
$cacheBlocks['by_class'][$class] = $settings['cache'];
}
$container->setParameter($this->getAlias().'.cache_blocks', $cacheBlocks);
}
/**
* @param ContainerBuilder $container
* @param array $config
*/
public function configureLoaderChain(ContainerBuilder $container, array $config)
{
$types = [];
foreach ($config['blocks'] as $service => $settings) {
$types[] = $service;
}
$container->getDefinition('sonata.block.loader.service')->replaceArgument(0, $types);
}
/**
* @param ContainerBuilder $container
* @param array $config
*/
public function configureForm(ContainerBuilder $container, array $config)
{
$defaults = $config['default_contexts'];
$contexts = [];
foreach ($config['blocks'] as $service => $settings) {
if (0 == count($settings['contexts'])) {
$settings['contexts'] = $defaults;
}
foreach ($settings['contexts'] as $context) {
if (!isset($contexts[$context])) {
$contexts[$context] = [];
}
$contexts[$context][] = $service;
}
}
}
/**
* Configures the block profiler.
*
* @param ContainerBuilder $container Container
* @param array $config Configuration
*/
public function configureProfiler(ContainerBuilder $container, array $config)
{
$container->setAlias('sonata.block.renderer', 'sonata.block.renderer.default');
if (!$config['profiler']['enabled']) {
return;
}
// add the block data collector
$definition = new Definition('Sonata\BlockBundle\Profiler\DataCollector\BlockDataCollector');
$definition->setPublic(false);
$definition->addTag('data_collector', ['id' => 'block', 'template' => $config['profiler']['template']]);
$definition->addArgument(new Reference('sonata.block.templating.helper'));
$definition->addArgument($config['container']['types']);
$container->setDefinition('sonata.block.data_collector', $definition);
}
/**
* Configure the exception parameters.
*
* @param ContainerBuilder $container Container builder
* @param array $config An array of configuration
*/
public function configureException(ContainerBuilder $container, array $config)
{
// retrieve available filters
$filters = [];
foreach ($config['exception']['filters'] as $name => $filter) {
$filters[$name] = $filter;
}
// retrieve available renderers
$renderers = [];
foreach ($config['exception']['renderers'] as $name => $renderer) {
$renderers[$name] = $renderer;
}
// retrieve block customization
$blockFilters = [];
$blockRenderers = [];
foreach ($config['blocks'] as $service => $settings) {
if (isset($settings['exception'], $settings['exception']['filter'])) {
$blockFilters[$service] = $settings['exception']['filter'];
}
if (isset($settings['exception'], $settings['exception']['renderer'])) {
$blockRenderers[$service] = $settings['exception']['renderer'];
}
}
$definition = $container->getDefinition('sonata.block.exception.strategy.manager');
$definition->replaceArgument(1, $filters);
$definition->replaceArgument(2, $renderers);
$definition->replaceArgument(3, $blockFilters);
$definition->replaceArgument(4, $blockRenderers);
// retrieve default values
$defaultFilter = $config['exception']['default']['filter'];
$defaultRenderer = $config['exception']['default']['renderer'];
$definition->addMethodCall('setDefaultFilter', [$defaultFilter]);
$definition->addMethodCall('setDefaultRenderer', [$defaultRenderer]);
}
/**
* Add class to compile.
*/
public function configureClassesToCompile()
{
$this->addClassesToCompile([
'Sonata\\BlockBundle\\Block\\BlockLoaderChain',
'Sonata\\BlockBundle\\Block\\BlockLoaderInterface',
'Sonata\\BlockBundle\\Block\\BlockRenderer',
'Sonata\\BlockBundle\\Block\\BlockRendererInterface',
'Sonata\\BlockBundle\\Block\\BlockServiceInterface',
'Sonata\\BlockBundle\\Block\\BlockServiceManager',
'Sonata\\BlockBundle\\Block\\BlockServiceManagerInterface',
'Sonata\\BlockBundle\\Block\\Loader\\ServiceLoader',
'Sonata\\BlockBundle\\Block\\Service\\EmptyBlockService',
'Sonata\\BlockBundle\\Block\\Service\\RssBlockService',
'Sonata\\BlockBundle\\Block\\Service\\MenuBlockService',
'Sonata\\BlockBundle\\Block\\Service\\TextBlockService',
'Sonata\\BlockBundle\\Exception\\BlockExceptionInterface',
'Sonata\\BlockBundle\\Exception\\BlockNotFoundException',
'Sonata\\BlockBundle\\Exception\\Filter\\DebugOnlyFilter',
'Sonata\\BlockBundle\\Exception\\Filter\\FilterInterface',
'Sonata\\BlockBundle\\Exception\\Filter\\IgnoreClassFilter',
'Sonata\\BlockBundle\\Exception\\Filter\\KeepAllFilter',
'Sonata\\BlockBundle\\Exception\\Filter\\KeepNoneFilter',
'Sonata\\BlockBundle\\Exception\\Renderer\\InlineDebugRenderer',
'Sonata\\BlockBundle\\Exception\\Renderer\\InlineRenderer',
'Sonata\\BlockBundle\\Exception\\Renderer\\MonkeyThrowRenderer',
'Sonata\\BlockBundle\\Exception\\Renderer\\RendererInterface',
'Sonata\\BlockBundle\\Exception\\Strategy\\StrategyManager',
'Sonata\\BlockBundle\\Exception\\Strategy\\StrategyManagerInterface',
'Sonata\\BlockBundle\\Form\\Type\\ServiceListType',
'Sonata\\BlockBundle\\Model\\BaseBlock',
'Sonata\\BlockBundle\\Model\\Block',
'Sonata\\BlockBundle\\Model\\BlockInterface',
'Sonata\\BlockBundle\\Model\\BlockManagerInterface',
'Sonata\\BlockBundle\\Model\\EmptyBlock',
'Sonata\\BlockBundle\\Twig\\Extension\\BlockExtension',
'Sonata\\BlockBundle\\Twig\\GlobalVariables',
]);
}
/**
* {@inheritdoc}
*/
public function getNamespace()
{
return 'http://sonata-project.com/schema/dic/block';
}
}

View File

@@ -0,0 +1,71 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Event;
use Sonata\BlockBundle\Model\BlockInterface;
use Symfony\Component\EventDispatcher\Event;
class BlockEvent extends Event
{
/**
* @var array
*/
protected $settings;
/**
* @var BlockInterface[]
*/
protected $blocks = [];
/**
* @param array $settings
*/
public function __construct(array $settings = [])
{
$this->settings = $settings;
}
/**
* @param BlockInterface $block
*/
public function addBlock(BlockInterface $block)
{
$this->blocks[] = $block;
}
/**
* @return array
*/
public function getSettings()
{
return $this->settings;
}
/**
* @return mixed
*/
public function getBlocks()
{
return $this->blocks;
}
/**
* @param string $name
* @param mixed $default
*
* @return mixed
*/
public function getSetting($name, $default = null)
{
return isset($this->settings[$name]) ? $this->settings[$name] : $default;
}
}

View File

@@ -0,0 +1,52 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Event;
use Sonata\AdminBundle\Admin\AdminInterface;
use Sonata\BlockBundle\Model\Block;
use Symfony\Component\EventDispatcher\Event;
/**
* This class is just a demo of how to define a Listener to generate a valid block instance used
* to render a block from an event call using the sonata_block_render_event template helper.
*
* For instance, you can add an element on the top to each admin form with the following code:
*
* text.listener:
* class: Sonata\BlockBundle\Event\TextBlockListener
* tags:
* - { name: kernel.event_listener, event: 'sonata.block.event.sonata.admin.edit.form.top', method: onBlock}
*/
class TextBlockListener
{
/**
* @param BlockEvent $event
*/
public function onBlock(BlockEvent $event)
{
$content = 'This block is coming from inline event from the template';
if ($event->getSetting('admin') instanceof AdminInterface && 'edit' == $event->getSetting('action')) {
$admin = $event->getSetting('admin');
$content = sprintf("<p class='well'>The admin subject is <strong>%s</strong></p>", $admin->toString($admin->getSubject()));
}
$block = new Block();
$block->setId(uniqid());
$block->setSettings([
'content' => $event->getSetting('content', $content),
]);
$block->setType('sonata.block.service.text');
$event->addBlock($block);
}
}

View File

@@ -0,0 +1,19 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Exception;
/**
* Interface to implement exception identified as block-specific exceptions.
*/
interface BlockExceptionInterface
{
}

View File

@@ -0,0 +1,18 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Exception;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class BlockNotFoundException extends NotFoundHttpException
{
}

View File

@@ -0,0 +1,18 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Exception;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class BlockOptionsException extends NotFoundHttpException
{
}

View File

@@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Exception\Filter;
use Sonata\BlockBundle\Model\BlockInterface;
/**
* This filter handles exceptions only when debug mode is enabled.
*
* @author Olivier Paradis <paradis.olivier@gmail.com>
*/
class DebugOnlyFilter implements FilterInterface
{
/**
* @var bool
*/
protected $debug;
/**
* @param bool $debug
*/
public function __construct($debug)
{
$this->debug = $debug;
}
/**
* {@inheritdoc}
*/
public function handle(\Exception $exception, BlockInterface $block)
{
return $this->debug ? true : false;
}
}

View File

@@ -0,0 +1,34 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Exception\Filter;
use Sonata\BlockBundle\Model\BlockInterface;
/**
* Interface for the exception filter used in the exception strategy management.
*
* It's purpose is to define which exceptions should be managed and which should simply be ignored.
*
* @author Olivier Paradis <paradis.olivier@gmail.com>
*/
interface FilterInterface
{
/**
* Returns whether or not this filter handles this exception for given block.
*
* @param \Exception $exception Exception to manage
* @param BlockInterface $block Block that provoked the exception
*
* @return bool
*/
public function handle(\Exception $exception, BlockInterface $block);
}

View File

@@ -0,0 +1,44 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Exception\Filter;
use Sonata\BlockBundle\Model\BlockInterface;
/**
* This filter ignores exceptions that inherit a given class or interface, or in other words, it will only handle
* exceptions that do not inherit the given class or interface.
*
* @author Olivier Paradis <paradis.olivier@gmail.com>
*/
class IgnoreClassFilter implements FilterInterface
{
/**
* @var string
*/
protected $class;
/**
* @param string $class
*/
public function __construct($class)
{
$this->class = $class;
}
/**
* {@inheritdoc}
*/
public function handle(\Exception $exception, BlockInterface $block)
{
return !$exception instanceof $this->class;
}
}

View File

@@ -0,0 +1,30 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Exception\Filter;
use Sonata\BlockBundle\Model\BlockInterface;
/**
* This filter will handle all exceptions.
*
* @author Olivier Paradis <paradis.olivier@gmail.com>
*/
class KeepAllFilter implements FilterInterface
{
/**
* {@inheritdoc}
*/
public function handle(\Exception $exception, BlockInterface $block)
{
return true;
}
}

View File

@@ -0,0 +1,30 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Exception\Filter;
use Sonata\BlockBundle\Model\BlockInterface;
/**
* This filters will ignore all exceptions.
*
* @author Olivier Paradis <paradis.olivier@gmail.com>
*/
class KeepNoneFilter implements FilterInterface
{
/**
* {@inheritdoc}
*/
public function handle(\Exception $exception, BlockInterface $block)
{
return false;
}
}

View File

@@ -0,0 +1,90 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Exception\Renderer;
use Sonata\BlockBundle\Model\BlockInterface;
use Symfony\Component\Debug\Exception\FlattenException;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Templating\EngineInterface;
/**
* This renderer uses a template to display an error message at the block position with extensive debug information.
*
* @author Olivier Paradis <paradis.olivier@gmail.com>
*/
class InlineDebugRenderer implements RendererInterface
{
/**
* @var EngineInterface
*/
protected $templating;
/**
* @var string
*/
protected $template;
/**
* @var bool
*/
protected $forceStyle;
/**
* @var bool
*/
protected $debug;
/**
* @param EngineInterface $templating Templating engine
* @param string $template Template to render
* @param bool $debug Whether the debug is enabled or not
* @param bool $forceStyle Whether to force style within the template or not
*/
public function __construct(EngineInterface $templating, $template, $debug, $forceStyle = true)
{
$this->templating = $templating;
$this->template = $template;
$this->debug = $debug;
$this->forceStyle = $forceStyle;
}
/**
* {@inheritdoc}
*/
public function render(\Exception $exception, BlockInterface $block, Response $response = null)
{
$response = $response ?: new Response();
// enforce debug mode or ignore silently
if (!$this->debug) {
return $response;
}
$flattenException = FlattenException::create($exception);
$code = $flattenException->getStatusCode();
$parameters = [
'exception' => $flattenException,
'status_code' => $code,
'status_text' => isset(Response::$statusTexts[$code]) ? Response::$statusTexts[$code] : '',
'logger' => false,
'currentContent' => false,
'block' => $block,
'forceStyle' => $this->forceStyle,
];
$content = $this->templating->render($this->template, $parameters);
$response->setContent($content);
return $response;
}
}

View File

@@ -0,0 +1,62 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Exception\Renderer;
use Sonata\BlockBundle\Model\BlockInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Templating\EngineInterface;
/**
* This renderer uses a template to display an error message at the block position.
*
* @author Olivier Paradis <paradis.olivier@gmail.com>
*/
class InlineRenderer implements RendererInterface
{
/**
* @var EngineInterface
*/
protected $templating;
/**
* @var string
*/
protected $template;
/**
* @param EngineInterface $templating Templating engine
* @param string $template Template to render
*/
public function __construct(EngineInterface $templating, $template)
{
$this->templating = $templating;
$this->template = $template;
}
/**
* {@inheritdoc}
*/
public function render(\Exception $exception, BlockInterface $block, Response $response = null)
{
$parameters = [
'exception' => $exception,
'block' => $block,
];
$content = $this->templating->render($this->template, $parameters);
$response = $response ?: new Response();
$response->setContent($content);
return $response;
}
}

View File

@@ -0,0 +1,31 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Exception\Renderer;
use Sonata\BlockBundle\Model\BlockInterface;
use Symfony\Component\HttpFoundation\Response;
/**
* This renderer re-throws the exception and lets the framework handle the exception.
*
* @author Olivier Paradis <paradis.olivier@gmail.com>
*/
class MonkeyThrowRenderer implements RendererInterface
{
/**
* {@inheritdoc}
*/
public function render(\Exception $banana, BlockInterface $block, Response $response = null)
{
throw $banana;
}
}

View File

@@ -0,0 +1,34 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Exception\Renderer;
use Sonata\BlockBundle\Model\BlockInterface;
use Symfony\Component\HttpFoundation\Response;
/**
* Interface for exception renderer.
*
* @author Olivier Paradis <paradis.olivier@gmail.com>
*/
interface RendererInterface
{
/**
* Renders an exception into an HTTP response.
*
* @param \Exception $exception Exception provoked
* @param BlockInterface $block Block that provoked the exception
* @param Response $response Response to alter
*
* @return Response
*/
public function render(\Exception $exception, BlockInterface $block, Response $response = null);
}

View File

@@ -0,0 +1,210 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Exception\Strategy;
use Sonata\BlockBundle\Exception\Filter\FilterInterface;
use Sonata\BlockBundle\Exception\Renderer\RendererInterface;
use Sonata\BlockBundle\Model\BlockInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Response;
/**
* The strategy manager handles exceptions thrown by a block. It uses an exception filter to identify which exceptions
* it should handle or ignore. It then uses an exception renderer to "somehow" display the exception.
*
* @author Olivier Paradis <paradis.olivier@gmail.com>
*/
class StrategyManager implements StrategyManagerInterface
{
/**
* @var ContainerInterface
*/
protected $container;
/**
* @var array
*/
protected $filters;
/**
* @var array
*/
protected $renderers;
/**
* @var array
*/
protected $blockFilters;
/**
* @var array
*/
protected $blockRenderers;
/**
* @var string
*/
protected $defaultFilter;
/**
* @var string
*/
protected $defaultRenderer;
/**
* @param ContainerInterface $container Dependency injection container
* @param array $filters Filter definitions
* @param array $renderers Renderer definitions
* @param array $blockFilters Filter names for each block
* @param array $blockRenderers Renderer names for each block
*/
public function __construct(ContainerInterface $container, array $filters, array $renderers, array $blockFilters, array $blockRenderers)
{
$this->container = $container;
$this->filters = $filters;
$this->renderers = $renderers;
$this->blockFilters = $blockFilters;
$this->blockRenderers = $blockRenderers;
}
/**
* Sets the default filter name.
*
* @param string $name
*
* @throws \InvalidArgumentException
*/
public function setDefaultFilter($name)
{
if (!array_key_exists($name, $this->filters)) {
throw new \InvalidArgumentException(sprintf('Cannot set default exception filter "%s". It does not exist.', $name));
}
$this->defaultFilter = $name;
}
/**
* Sets the default renderer name.
*
* @param string $name
*
* @throws \InvalidArgumentException
*/
public function setDefaultRenderer($name)
{
if (!array_key_exists($name, $this->renderers)) {
throw new \InvalidArgumentException(sprintf('Cannot set default exception renderer "%s". It does not exist.', $name));
}
$this->defaultRenderer = $name;
}
/**
* {@inheritdoc}
*/
public function handleException(\Exception $exception, BlockInterface $block, Response $response = null)
{
$response = $response ?: new Response();
$response->setPrivate();
$filter = $this->getBlockFilter($block);
if ($filter->handle($exception, $block)) {
$renderer = $this->getBlockRenderer($block);
$response = $renderer->render($exception, $block, $response);
}
// render empty block template?
return $response;
}
/**
* Returns the exception renderer for given block.
*
* @param BlockInterface $block
*
* @throws \RuntimeException
*
* @return RendererInterface
*/
public function getBlockRenderer(BlockInterface $block)
{
$type = $block->getType();
$name = isset($this->blockRenderers[$type]) ? $this->blockRenderers[$type] : $this->defaultRenderer;
$service = $this->getRendererService($name);
if (!$service instanceof RendererInterface) {
throw new \RuntimeException(sprintf('The service "%s" is not an exception renderer', $name));
}
return $service;
}
/**
* Returns the exception filter for given block.
*
* @param BlockInterface $block
*
* @throws \RuntimeException
*
* @return FilterInterface
*/
public function getBlockFilter(BlockInterface $block)
{
$type = $block->getType();
$name = isset($this->blockFilters[$type]) ? $this->blockFilters[$type] : $this->defaultFilter;
$service = $this->getFilterService($name);
if (!$service instanceof FilterInterface) {
throw new \RuntimeException(sprintf('The service "%s" is not an exception filter', $name));
}
return $service;
}
/**
* Returns the filter service for given filter name.
*
* @param string $name
*
* @throws \RuntimeException
*
* @return object
*/
protected function getFilterService($name)
{
if (!isset($this->filters[$name])) {
throw new \RuntimeException('The filter "%s" does not exist.');
}
return $this->container->get($this->filters[$name]);
}
/**
* Returns the renderer service for given renderer name.
*
* @param string $name
*
* @throws \RuntimeException
*
* @return object
*/
protected function getRendererService($name)
{
if (!isset($this->renderers[$name])) {
throw new \RuntimeException('The renderer "%s" does not exist.');
}
return $this->container->get($this->renderers[$name]);
}
}

View File

@@ -0,0 +1,34 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Exception\Strategy;
use Sonata\BlockBundle\Model\BlockInterface;
use Symfony\Component\HttpFoundation\Response;
/**
* Interface for exception strategy management.
*
* @author Olivier Paradis <paradis.olivier@gmail.com>
*/
interface StrategyManagerInterface
{
/**
* Handles an exception for a given block.
*
* @param \Exception $exception Exception to handle
* @param BlockInterface $block Block that provoked the exception
* @param Response $response Response provided to the block service
*
* @return Response
*/
public function handleException(\Exception $exception, BlockInterface $block, Response $response = null);
}

View File

@@ -0,0 +1,69 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Form\Type;
use Sonata\AdminBundle\Form\Type\Filter\ChoiceType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* @author Hugo Briand <briand@ekino.com>
*/
class ContainerTemplateType extends AbstractType
{
/**
* @var array
*/
protected $templateChoices;
/**
* @param array $templateChoices
*/
public function __construct(array $templateChoices)
{
$this->templateChoices = $templateChoices;
}
/**
* {@inheritdoc}
*/
public function getBlockPrefix()
{
return 'sonata_type_container_template_choice';
}
/**
* {@inheritdoc}
*/
public function getName()
{
return $this->getBlockPrefix();
}
/**
* {@inheritdoc}
*/
public function getParent()
{
return ChoiceType::class;
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'choices' => $this->templateChoices,
]);
}
}

View File

@@ -0,0 +1,95 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Form\Type;
use Sonata\BlockBundle\Block\BlockServiceManagerInterface;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ServiceListType extends AbstractType
{
protected $manager;
/**
* @param BlockServiceManagerInterface $manager
*/
public function __construct(BlockServiceManagerInterface $manager)
{
$this->manager = $manager;
}
/**
* {@inheritdoc}
*/
public function getBlockPrefix()
{
return 'sonata_block_service_choice';
}
/**
* {@inheritdoc}
*/
public function getName()
{
return $this->getBlockPrefix();
}
/**
* {@inheritdoc}
*/
public function getParent()
{
return ChoiceType::class;
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$manager = $this->manager;
$resolver->setRequired([
'context',
]);
$resolver->setDefaults([
'multiple' => false,
'expanded' => false,
'choices' => function (Options $options, $previousValue) use ($manager) {
$types = [];
foreach ($manager->getServicesByContext($options['context'], $options['include_containers']) as $code => $service) {
$types[$code] = sprintf('%s - %s', $service->getName(), $code);
}
return $types;
},
'preferred_choices' => [],
'empty_data' => function (Options $options) {
$multiple = isset($options['multiple']) && $options['multiple'];
$expanded = isset($options['expanded']) && $options['expanded'];
return $multiple || $expanded ? [] : '';
},
'empty_value' => function (Options $options, $previousValue) {
$multiple = isset($options['multiple']) && $options['multiple'];
$expanded = isset($options['expanded']) && $options['expanded'];
return $multiple || $expanded || !isset($previousValue) ? null : '';
},
'error_bubbling' => false,
'include_containers' => false,
]);
}
}

View File

@@ -0,0 +1,40 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Menu;
use Knp\Menu\FactoryInterface;
use Knp\Menu\ItemInterface;
/**
* @author Christian Gripp <mail@core23.de>
*
* @deprecated since 3.9, to be removed with 4.0.
*/
interface MenuBuilderInterface
{
/**
* Create a knp menu.
*
* @param FactoryInterface $factory
* @param array $options
*
* @return ItemInterface
*/
public function buildMenu(FactoryInterface $factory, array $options);
/**
* Return the name.
*
* @return string
*/
public function getName();
}

View File

@@ -0,0 +1,67 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Menu;
/**
* @author Christian Gripp <mail@core23.de>
*/
final class MenuRegistry implements MenuRegistryInterface
{
/**
* @var string[]
*/
private $names = [];
/**
* MenuRegistry constructor.
*
* @param string[] $menus
*
* NEXT_MAJOR: remove constructor parameter
*/
public function __construct($menus = null)
{
if (null !== $menus) {
$this->names = $menus;
@trigger_error(
'The menus parameter in '.__CLASS__.' is deprecated since 3.3 and will be removed in 4.0.',
E_USER_DEPRECATED
);
}
}
/**
* {@inheritdoc}
*/
public function add($menu)
{
if ($menu instanceof MenuBuilderInterface) {
@trigger_error(
'Adding a '.MenuBuilderInterface::class.' is deprecated since 3.9 and will be removed in 4.0.',
E_USER_DEPRECATED
);
return;
}
$this->names[$menu] = $menu;
}
/**
* {@inheritdoc}
*/
public function getAliasNames()
{
return $this->names;
}
}

View File

@@ -0,0 +1,32 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Menu;
/**
* @author Christian Gripp <mail@core23.de>
*/
interface MenuRegistryInterface
{
/**
* Adds a new menu.
*
* @param string $name
*/
public function add($name);
/**
* Returns all alias names.
*
* @return string[]
*/
public function getAliasNames();
}

View File

@@ -0,0 +1,283 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Model;
/**
* Base abstract Block class that provides a default implementation of the block interface.
*/
abstract class BaseBlock implements BlockInterface
{
/**
* @var string|null
*/
protected $name;
/**
* @var array
*/
protected $settings;
/**
* @var bool
*/
protected $enabled;
/**
* @var int|null
*/
protected $position;
/**
* @var BlockInterface|null
*/
protected $parent;
/**
* @var BlockInterface[]
*/
protected $children;
/**
* @var \DateTime|null
*/
protected $createdAt;
/**
* @var \DateTime|null
*/
protected $updatedAt;
/**
* @var string|null
*/
protected $type;
/**
* @var int|null
*/
protected $ttl;
public function __construct()
{
$this->settings = [];
$this->enabled = false;
$this->children = [];
}
/**
* {@inheritdoc}
*/
public function __toString()
{
return sprintf('%s ~ #%s', $this->getName(), $this->getId());
}
/**
* {@inheritdoc}
*/
public function setName($name)
{
$this->name = $name;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return $this->name;
}
/**
* {@inheritdoc}
*/
public function setType($type)
{
$this->type = $type;
}
/**
* {@inheritdoc}
*/
public function getType()
{
return $this->type;
}
/**
* {@inheritdoc}
*/
public function setSettings(array $settings = [])
{
$this->settings = $settings;
}
/**
* {@inheritdoc}
*/
public function getSettings()
{
return $this->settings;
}
/**
* {@inheritdoc}
*/
public function setSetting($name, $value)
{
$this->settings[$name] = $value;
}
/**
* {@inheritdoc}
*/
public function getSetting($name, $default = null)
{
return isset($this->settings[$name]) ? $this->settings[$name] : $default;
}
/**
* {@inheritdoc}
*/
public function setEnabled($enabled)
{
$this->enabled = $enabled;
}
/**
* {@inheritdoc}
*/
public function getEnabled()
{
return $this->enabled;
}
/**
* {@inheritdoc}
*/
public function setPosition($position)
{
$this->position = $position;
}
/**
* {@inheritdoc}
*/
public function getPosition()
{
return $this->position;
}
/**
* {@inheritdoc}
*/
public function setCreatedAt(\DateTime $createdAt = null)
{
$this->createdAt = $createdAt;
}
/**
* {@inheritdoc}
*/
public function getCreatedAt()
{
return $this->createdAt;
}
/**
* {@inheritdoc}
*/
public function setUpdatedAt(\DateTime $updatedAt = null)
{
$this->updatedAt = $updatedAt;
}
/**
* {@inheritdoc}
*/
public function getUpdatedAt()
{
return $this->updatedAt;
}
/**
* {@inheritdoc}
*/
public function addChildren(BlockInterface $child)
{
$this->children[] = $child;
$child->setParent($this);
}
/**
* {@inheritdoc}
*/
public function getChildren()
{
return $this->children;
}
/**
* {@inheritdoc}
*/
public function setParent(BlockInterface $parent = null)
{
$this->parent = $parent;
}
/**
* {@inheritdoc}
*/
public function getParent()
{
return $this->parent;
}
/**
* {@inheritdoc}
*/
public function hasParent()
{
return $this->getParent() instanceof self;
}
/**
* {@inheritdoc}
*/
public function getTtl()
{
if (!$this->getSetting('use_cache', true)) {
return 0;
}
$ttl = $this->getSetting('ttl', 86400);
foreach ($this->getChildren() as $block) {
$blockTtl = $block->getTtl();
$ttl = ($blockTtl < $ttl) ? $blockTtl : $ttl;
}
$this->ttl = $ttl;
return $this->ttl;
}
/**
* {@inheritdoc}
*/
public function hasChildren()
{
return count($this->children) > 0;
}
}

View File

@@ -0,0 +1,39 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Model;
/**
* Block model with concrete implementation of BlockInterface.
*/
class Block extends BaseBlock
{
/**
* @var mixed
*/
protected $id;
/**
* {@inheritdoc}
*/
public function setId($id)
{
$this->id = $id;
}
/**
* {@inheritdoc}
*/
public function getId()
{
return $this->id;
}
}

View File

@@ -0,0 +1,197 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Model;
/**
* Interface of Block.
*/
interface BlockInterface
{
/**
* Sets the block Id.
*
* @param mixed $id
*/
public function setId($id);
/**
* Returns the block id.
*
* @return mixed void
*/
public function getId();
/**
* Sets the name.
*
* @param string $name
*/
public function setName($name);
/**
* Returns the name.
*
* @return string|null
*/
public function getName();
/**
* Sets the type.
*
* @param string $type
*/
public function setType($type);
/**
* Returns the type.
*
* @return string|null $type
*/
public function getType();
/**
* Sets whether or not this block is enabled.
*
* @param bool $enabled
*/
public function setEnabled($enabled);
/**
* Returns whether or not this block is enabled.
*
* @return bool $enabled
*/
public function getEnabled();
/**
* Set the block ordered position.
*
* @param int $position
*/
public function setPosition($position);
/**
* Returns the block ordered position.
*
* @return int|null $position
*/
public function getPosition();
/**
* Sets the creation date and time.
*
* @param \DateTime $createdAt
*/
public function setCreatedAt(\DateTime $createdAt = null);
/**
* Returns the creation date and time.
*
* @return \DateTime|null $createdAt
*/
public function getCreatedAt();
/**
* Set the last update date and time.
*
* @param \DateTime $updatedAt
*/
public function setUpdatedAt(\DateTime $updatedAt = null);
/**
* Returns the last update date and time.
*
* @return \DateTime|null $updatedAt
*/
public function getUpdatedAt();
/**
* Returns the block cache TTL.
*
* @return int
*/
public function getTtl();
/**
* Sets the block settings.
*
* @param array $settings An array of key/value
*/
public function setSettings(array $settings = []);
/**
* Returns the block settings.
*
* @return array $settings An array of key/value
*/
public function getSettings();
/**
* Sets one block setting.
*
* @param string $name Key name
* @param mixed $value Value
*/
public function setSetting($name, $value);
/**
* Returns one block setting or the given default value if no value is found.
*
* @param string $name Key name
* @param mixed|null $default Default value
*
* @return mixed
*/
public function getSetting($name, $default = null);
/**
* Add one child block.
*
* @param BlockInterface $children
*/
public function addChildren(self $children);
/**
* Returns child blocks.
*
* @return BlockInterface[] $children
*/
public function getChildren();
/**
* Returns whether or not this block has children.
*
* @return bool
*/
public function hasChildren();
/**
* Set the parent block.
*
* @param BlockInterface|null $parent
*/
public function setParent(self $parent = null);
/**
* Returns the parent block.
*
* @return BlockInterface|null $parent
*/
public function getParent();
/**
* Returns whether or not this block has a parent.
*
* @return bool
*/
public function hasParent();
}

View File

@@ -0,0 +1,19 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Model;
use Sonata\CoreBundle\Model\ManagerInterface;
use Sonata\CoreBundle\Model\PageableManagerInterface;
interface BlockManagerInterface extends ManagerInterface, PageableManagerInterface
{
}

View File

@@ -0,0 +1,19 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Model;
/**
* EmptyBlock model to be used to return an empty result if a block is not found or not valid.
*/
class EmptyBlock extends Block
{
}

View File

@@ -0,0 +1,191 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Profiler\DataCollector;
use Sonata\BlockBundle\Templating\Helper\BlockHelper;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface;
/**
* Block data collector for the symfony web profiling.
*
* @author Olivier Paradis <paradis.olivier@gmail.com>
*/
class BlockDataCollector implements DataCollectorInterface, \Serializable
{
/**
* @var BlockHelper
*/
protected $blocksHelper;
/**
* @var array
*/
protected $blocks = [];
/**
* @var array
*/
protected $containers = [];
/**
* @var array
*/
protected $realBlocks = [];
/**
* @var array
*/
protected $containerTypes = [];
/**
* @var array
*/
protected $events = [];
/**
* @param BlockHelper $blockHelper Block renderer
* @param array $containerTypes array of container types
*/
public function __construct(BlockHelper $blockHelper, array $containerTypes)
{
$this->blocksHelper = $blockHelper;
$this->containerTypes = $containerTypes;
}
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = null)
{
$this->blocks = $this->blocksHelper->getTraces();
// split into containers & real blocks
foreach ($this->blocks as $id => $block) {
if (!is_array($block)) {
return; // something went wrong while collecting information
}
if ('_events' == $id) {
foreach ($block as $uniqid => $event) {
$this->events[$uniqid] = $event;
}
continue;
}
if (in_array($block['type'], $this->containerTypes)) {
$this->containers[$id] = $block;
} else {
$this->realBlocks[$id] = $block;
}
}
}
/**
* Returns the number of block used.
*
* @return int
*/
public function getTotalBlock()
{
return count($this->realBlocks) + count($this->containers);
}
/**
* Return the events used on the current page.
*
* @return array
*/
public function getEvents()
{
return $this->events;
}
/**
* Returns the block rendering history.
*
* @return array
*/
public function getBlocks()
{
return $this->blocks;
}
/**
* Returns the container blocks.
*
* @return array
*/
public function getContainers()
{
return $this->containers;
}
/**
* Returns the real blocks (non-container).
*
* @return array
*/
public function getRealBlocks()
{
return $this->realBlocks;
}
/**
* {@inheritdoc}
*/
public function serialize()
{
$data = [
'blocks' => $this->blocks,
'containers' => $this->containers,
'realBlocks' => $this->realBlocks,
'events' => $this->events,
];
return serialize($data);
}
/**
* {@inheritdoc}
*/
public function unserialize($data)
{
$merged = unserialize($data);
$this->blocks = $merged['blocks'];
$this->containers = $merged['containers'];
$this->realBlocks = $merged['realBlocks'];
$this->events = $merged['events'];
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'block';
}
/**
* {@inheritdoc}
*/
public function reset()
{
$this->blocks = [];
$this->containers = [];
$this->realBlocks = [];
$this->events = [];
}
}

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="sonata.block.service.container.class">Sonata\BlockBundle\Block\Service\ContainerBlockService</parameter>
<parameter key="sonata.block.service.empty.class">Sonata\BlockBundle\Block\Service\EmptyBlockService</parameter>
<parameter key="sonata.block.service.text.class">Sonata\BlockBundle\Block\Service\TextBlockService</parameter>
<parameter key="sonata.block.service.rss.class">Sonata\BlockBundle\Block\Service\RssBlockService</parameter>
<parameter key="sonata.block.service.menu.class">Sonata\BlockBundle\Block\Service\MenuBlockService</parameter>
<parameter key="sonata.block.service.template.class">Sonata\BlockBundle\Block\Service\TemplateBlockService</parameter>
</parameters>
<services>
<service id="sonata.block.service.container" class="%sonata.block.service.container.class%">
<tag name="sonata.block"/>
<argument>sonata.block.container</argument>
<argument type="service" id="sonata.templating"/>
</service>
<service id="sonata.block.service.empty" class="%sonata.block.service.empty.class%">
<tag name="sonata.block"/>
<argument>sonata.block.empty</argument>
<argument type="service" id="sonata.templating"/>
</service>
<service id="sonata.block.service.text" class="%sonata.block.service.text.class%">
<tag name="sonata.block"/>
<argument>sonata.block.text</argument>
<argument type="service" id="sonata.templating"/>
</service>
<service id="sonata.block.service.rss" class="%sonata.block.service.rss.class%">
<tag name="sonata.block"/>
<argument>sonata.block.rss</argument>
<argument type="service" id="sonata.templating"/>
</service>
<service id="sonata.block.service.menu" class="%sonata.block.service.menu.class%">
<tag name="sonata.block"/>
<argument>sonata.block.menu</argument>
<argument type="service" id="sonata.templating"/>
<argument type="service" id="knp_menu.menu_provider"/>
<argument type="service" id="sonata.block.menu.registry"/>
</service>
<service id="sonata.block.service.template" class="%sonata.block.service.template.class%">
<tag name="sonata.block"/>
<argument>sonata.block.template</argument>
<argument type="service" id="sonata.templating"/>
</service>
</services>
</container>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="Sonata\BlockBundle\Command\DebugBlocksCommand" class="Sonata\BlockBundle\Command\DebugBlocksCommand">
<tag name="console.command"/>
</service>
</services>
</container>

View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="sonata.templating.locator" class="Sonata\BlockBundle\Templating\TemplateLocator">
<argument type="service" id="file_locator"/>
<argument>%kernel.cache_dir%</argument>
</service>
<service id="sonata.templating.name_parser" class="Sonata\BlockBundle\Templating\TemplateNameParser">
<argument type="service" id="kernel"/>
</service>
<service id="sonata.templating" class="Sonata\BlockBundle\Templating\TwigEngine">
<argument type="service" id="twig"/>
<argument type="service" id="sonata.templating.name_parser"/>
<argument type="service" id="sonata.templating.locator"/>
</service>
<service id="sonata.block.manager" class="Sonata\BlockBundle\Block\BlockServiceManager" public="false">
<argument type="service" id="service_container"/>
<argument>%kernel.debug%</argument>
<argument type="service" id="logger" on-invalid="ignore"/>
</service>
<service id="sonata.block.menu.registry" class="Sonata\BlockBundle\Menu\MenuRegistry" public="true">
<argument/>
</service>
<service id="sonata.block.context_manager.default" class="Sonata\BlockBundle\Block\BlockContextManager" public="true">
<argument type="service" id="sonata.block.loader.chain"/>
<argument type="service" id="sonata.block.manager"/>
<argument>%sonata_block.cache_blocks%</argument>
<argument type="service" id="logger" on-invalid="ignore"/>
</service>
<service id="sonata.block.renderer.default" class="Sonata\BlockBundle\Block\BlockRenderer" public="true">
<argument type="service" id="sonata.block.manager"/>
<argument type="service" id="sonata.block.exception.strategy.manager"/>
<argument type="service" id="logger" on-invalid="ignore"/>
<argument>%kernel.debug%</argument>
</service>
<service id="sonata.block.twig.extension" class="Sonata\BlockBundle\Twig\Extension\BlockExtension" public="false">
<tag name="twig.extension"/>
<argument type="service" id="sonata.block.templating.helper"/>
</service>
<service id="sonata.block.templating.helper" class="Sonata\BlockBundle\Templating\Helper\BlockHelper">
<tag name="templating.helper" alias="sonata_block"/>
<argument type="service" id="sonata.block.manager"/>
<argument>%sonata_block.cache_blocks%</argument>
<argument type="service" id="sonata.block.renderer"/>
<argument type="service" id="sonata.block.context_manager"/>
<argument type="service" id="event_dispatcher"/>
<argument type="service" id="sonata.cache.manager" on-invalid="ignore"/>
<argument type="service" id="sonata.block.cache.handler.default" on-invalid="ignore"/>
<argument type="service" id="debug.stopwatch" on-invalid="ignore"/>
</service>
<service id="sonata.block.loader.chain" class="Sonata\BlockBundle\Block\BlockLoaderChain">
<argument/>
</service>
<service id="sonata.block.loader.service" class="Sonata\BlockBundle\Block\Loader\ServiceLoader">
<tag name="sonata.block.loader"/>
<argument/>
</service>
<service id="sonata.block.twig.global" class="Sonata\BlockBundle\Twig\GlobalVariables">
<argument/>
</service>
<service id="sonata.block.cache.handler.default" class="Sonata\BlockBundle\Cache\HttpCacheHandler"/>
<service id="sonata.block.cache.handler.noop" class="Sonata\BlockBundle\Cache\NoopHttpCacheHandler"/>
</services>
</container>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="sonata.block.exception.strategy.manager.class">Sonata\BlockBundle\Exception\Strategy\StrategyManager</parameter>
</parameters>
<services>
<!-- exception strategy manager -->
<service id="sonata.block.exception.strategy.manager" class="%sonata.block.exception.strategy.manager.class%">
<argument type="service" id="service_container"/>
<argument type="collection"/>
<argument type="collection"/>
<argument type="collection"/>
<argument type="collection"/>
</service>
<!-- exception filters -->
<service id="sonata.block.exception.filter.keep_none" class="Sonata\BlockBundle\Exception\Filter\KeepNoneFilter" public="true"/>
<service id="sonata.block.exception.filter.keep_all" class="Sonata\BlockBundle\Exception\Filter\KeepAllFilter" public="true"/>
<service id="sonata.block.exception.filter.debug_only" class="Sonata\BlockBundle\Exception\Filter\DebugOnlyFilter" public="true">
<argument>%kernel.debug%</argument>
</service>
<service id="sonata.block.exception.filter.ignore_block_exception" class="Sonata\BlockBundle\Exception\Filter\IgnoreClassFilter" public="true">
<argument>Sonata\BlockBundle\Exception\BlockExceptionInterface</argument>
</service>
<!-- exception renderers -->
<service id="sonata.block.exception.renderer.inline" class="Sonata\BlockBundle\Exception\Renderer\InlineRenderer" public="true">
<argument type="service" id="sonata.templating"/>
<argument>@SonataBlock/Block/block_exception.html.twig</argument>
</service>
<service id="sonata.block.exception.renderer.inline_debug" class="Sonata\BlockBundle\Exception\Renderer\InlineDebugRenderer" public="true">
<argument type="service" id="sonata.templating"/>
<argument>@SonataBlock/Block/block_exception_debug.html.twig</argument>
<argument>%kernel.debug%</argument>
<argument>true</argument>
</service>
<service id="sonata.block.exception.renderer.throw" class="Sonata\BlockBundle\Exception\Renderer\MonkeyThrowRenderer" public="true"/>
</services>
</container>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="sonata.block.form.type.block" class="Sonata\BlockBundle\Form\Type\ServiceListType">
<tag name="form.type" alias="sonata_block_service_choice"/>
<argument type="service" id="sonata.block.manager"/>
</service>
<service id="sonata.block.form.type.container_template" class="Sonata\BlockBundle\Form\Type\ContainerTemplateType">
<tag name="form.type" alias="sonata_type_container_template_choice"/>
<argument/>
</service>
</services>
</container>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<serializer>
<class name="Sonata\BlockBundle\Model\BaseBlock" exclusion-policy="all" xml-root-name="block">
<property name="name" type="string" expose="true" since-version="1.0" groups="sonata_api_read,sonata_api_write,sonata_search"/>
<property name="type" type="string" expose="true" since-version="1.0" groups="sonata_api_read,sonata_api_write,sonata_search"/>
<virtual-property name="settings" type="array" expose="true" since-version="1.0" groups="sonata_api_read,sonata_api_write,sonata_search" method="getSettings"/>
<property name="enabled" type="boolean" expose="true" since-version="1.0" groups="sonata_api_read,sonata_api_write,sonata_search"/>
<property name="position" type="integer" expose="true" since-version="1.0" groups="sonata_api_read,sonata_api_write,sonata_search"/>
<property name="parent" serialized-name="parent_id" type="sonata_page_block_id" expose="true" since-version="1.0" groups="sonata_api_read,sonata_api_write,sonata_search"/>
<property name="page" serialized-name="page_id" type="sonata_page_page_id" expose="true" since-version="1.0" groups="sonata_api_read,sonata_api_write,sonata_search"/>
<property name="createdAt" type="DateTime" expose="true" since-version="1.0" groups="sonata_api_read,sonata_search"/>
<property name="updatedAt" type="DateTime" expose="true" since-version="1.0" groups="sonata_api_read,sonata_search"/>
</class>
</serializer>

View File

@@ -0,0 +1,21 @@
The MIT License
Copyright (c) 2010-2013 thomas.rabaix@sonata-project.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\OptionsResolver;
if (!interface_exists('Symfony\Component\OptionsResolver\OptionsResolverInterface')) {
/**
* @deprecated since 3.9, to be removed in 4.0. Use \Symfony\Component\OptionsResolver\OptionsResolver instead.
*/
interface OptionsResolverInterface
{
}
}

View File

@@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file source-language="en" target-language="de" datatype="plaintext" original="SonataBlockBundle.en.xliff">
<body>
<trans-unit id="sonata.block.container">
<source>sonata.block.container</source>
<target>Container</target>
</trans-unit>
<trans-unit id="sonata.block.text">
<source>sonata.block.text</source>
<target>Einfacher Text</target>
</trans-unit>
<trans-unit id="sonata.block.rss">
<source>sonata.block.rss</source>
<target>RSS Feed</target>
</trans-unit>
<trans-unit id="sonata.block.menu">
<source>sonata.block.menu</source>
<target>Menü</target>
</trans-unit>
<trans-unit id="sonata.block.template">
<source>sonata.block.template</source>
<target>Template</target>
</trans-unit>
<trans-unit id="form.label_template">
<source>form.label_template</source>
<target>Template</target>
</trans-unit>
<trans-unit id="form.label_content">
<source>form.label_content</source>
<target>Inhalt</target>
</trans-unit>
<trans-unit id="form.label_url">
<source>form.label_url</source>
<target>URL</target>
</trans-unit>
<trans-unit id="form.label_title">
<source>form.label_title</source>
<target>Titel</target>
</trans-unit>
<trans-unit id="form.label_code">
<source>form.label_code</source>
<target>Code</target>
</trans-unit>
<trans-unit id="form.label_layout">
<source>form.label_layout</source>
<target>Layout</target>
</trans-unit>
<trans-unit id="form.label_class">
<source>form.label_class</source>
<target>CSS Klasse</target>
</trans-unit>
<trans-unit id="form.label_cache_policy">
<source>form.label_cache_policy</source>
<target>Cache Policy</target>
</trans-unit>
<trans-unit id="form.label_safe_labels">
<source>form.label_safe_labels</source>
<target>Sichere Bezeichner</target>
</trans-unit>
<trans-unit id="form.label_current_class">
<source>form.label_current_class</source>
<target>Aktuelle CSS Klasse</target>
</trans-unit>
<trans-unit id="form.label_first_class">
<source>form.label_first_class</source>
<target>Erste CSS Klasse</target>
</trans-unit>
<trans-unit id="form.label_last_class">
<source>form.label_last_class</source>
<target>Letzte CSS Klasse</target>
</trans-unit>
<trans-unit id="form.label_menu_class">
<source>form.label_menu_class</source>
<target>Menu CSS Klasse</target>
</trans-unit>
<trans-unit id="form.label_children_class">
<source>form.label_children_class</source>
<target>Children CSS Klasse</target>
</trans-unit>
<trans-unit id="form.label_menu_template">
<source>form.label_menu_template</source>
<target>Menü Template</target>
</trans-unit>
<trans-unit id="form.label_translation_domain">
<source>form.label_translation_domain</source>
<target>Übersetzungsdatei</target>
</trans-unit>
<trans-unit id="form.label_icon">
<source>form.label_icon</source>
<target>Icon</target>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file source-language="en" datatype="plaintext" original="file.ext">
<body>
<trans-unit id="sonata.block.container">
<source>sonata.block.container</source>
<target>Container</target>
</trans-unit>
<trans-unit id="sonata.block.text">
<source>sonata.block.text</source>
<target>Simple text</target>
</trans-unit>
<trans-unit id="sonata.block.rss">
<source>sonata.block.rss</source>
<target>RSS feed</target>
</trans-unit>
<trans-unit id="sonata.block.menu">
<source>sonata.block.menu</source>
<target>Menu</target>
</trans-unit>
<trans-unit id="sonata.block.template">
<source>sonata.block.template</source>
<target>Template</target>
</trans-unit>
<trans-unit id="form.label_template">
<source>form.label_template</source>
<target>Template</target>
</trans-unit>
<trans-unit id="form.label_content">
<source>form.label_content</source>
<target>Content</target>
</trans-unit>
<trans-unit id="form.label_url">
<source>form.label_url</source>
<target>URL</target>
</trans-unit>
<trans-unit id="form.label_title">
<source>form.label_title</source>
<target>Title</target>
</trans-unit>
<trans-unit id="form.label_code">
<source>form.label_code</source>
<target>Code</target>
</trans-unit>
<trans-unit id="form.label_layout">
<source>form.label_layout</source>
<target>Layout</target>
</trans-unit>
<trans-unit id="form.label_class">
<source>form.label_class</source>
<target>CSS Class</target>
</trans-unit>
<trans-unit id="form.label_cache_policy">
<source>form.label_cache_policy</source>
<target>Cache policy</target>
</trans-unit>
<trans-unit id="form.label_safe_labels">
<source>form.label_safe_labels</source>
<target>Safe labels</target>
</trans-unit>
<trans-unit id="form.label_current_class">
<source>form.label_current_class</source>
<target>Current CSS Class</target>
</trans-unit>
<trans-unit id="form.label_first_class">
<source>form.label_first_class</source>
<target>First CSS Class</target>
</trans-unit>
<trans-unit id="form.label_last_class">
<source>form.label_last_class</source>
<target>Last CSS Class</target>
</trans-unit>
<trans-unit id="form.label_menu_class">
<source>form.label_menu_class</source>
<target>Menu CSS Class</target>
</trans-unit>
<trans-unit id="form.label_children_class">
<source>form.label_children_class</source>
<target>Children CSS Class</target>
</trans-unit>
<trans-unit id="form.label_menu_template">
<source>form.label_menu_template</source>
<target>Menu Template</target>
</trans-unit>
<trans-unit id="form.label_translation_domain">
<source>form.label_translation_domain</source>
<target>Translation domain</target>
</trans-unit>
<trans-unit id="form.label_icon">
<source>form.label_icon</source>
<target>Icon</target>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file source-language="en" target-language="fr" datatype="plaintext" original="SonataBlockBundle.en.xliff">
<body>
<trans-unit id="sonata.block.container">
<source>sonata.block.container</source>
<target>Conteneur</target>
</trans-unit>
<trans-unit id="sonata.block.text">
<source>sonata.block.text</source>
<target>Texte</target>
</trans-unit>
<trans-unit id="sonata.block.rss">
<source>sonata.block.rss</source>
<target>Flux RSS</target>
</trans-unit>
<trans-unit id="sonata.block.menu">
<source>sonata.block.menu</source>
<target>Menu</target>
</trans-unit>
<trans-unit id="sonata.block.template">
<source>sonata.block.template</source>
<target>Vue Partielle</target>
</trans-unit>
<trans-unit id="form.label_template">
<source>form.label_template</source>
<target>Template</target>
</trans-unit>
<trans-unit id="form.label_content">
<source>form.label_content</source>
<target>Contenu</target>
</trans-unit>
<trans-unit id="form.label_url">
<source>form.label_url</source>
<target>URL</target>
</trans-unit>
<trans-unit id="form.label_title">
<source>form.label_title</source>
<target>Titre</target>
</trans-unit>
<trans-unit id="form.label_code">
<source>form.label_code</source>
<target>Code</target>
</trans-unit>
<trans-unit id="form.label_layout">
<source>form.label_layout</source>
<target>Layout</target>
</trans-unit>
<trans-unit id="form.label_class">
<source>form.label_class</source>
<target>Classe CSS</target>
</trans-unit>
<trans-unit id="form.label_cache_policy">
<source>form.label_cache_policy</source>
<target>Cache Policy</target>
</trans-unit>
<trans-unit id="form.label_safe_labels">
<source>form.label_safe_labels</source>
<target>Libellés sûrs</target>
</trans-unit>
<trans-unit id="form.label_current_class">
<source>form.label_current_class</source>
<target>Classe CSS de l'élément actuel du menu</target>
</trans-unit>
<trans-unit id="form.label_first_class">
<source>form.label_first_class</source>
<target>Classe CSS du premier élément du menu</target>
</trans-unit>
<trans-unit id="form.label_last_class">
<source>form.label_last_class</source>
<target>Classe CSS du dernier élément du menu</target>
</trans-unit>
<trans-unit id="form.label_menu_class">
<source>form.label_menu_class</source>
<target>Classe CSS du menu</target>
</trans-unit>
<trans-unit id="form.label_children_class">
<source>form.label_children_class</source>
<target>Classe CSS des sous-menus</target>
</trans-unit>
<trans-unit id="form.label_menu_template">
<source>form.label_menu_template</source>
<target>Template du menu</target>
</trans-unit>
<trans-unit id="form.label_translation_domain">
<source>form.label_translation_domain</source>
<target>Domaine de traduction</target>
</trans-unit>
<trans-unit id="form.label_icon">
<source>form.label_icon</source>
<target>Icône</target>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file source-language="en" target-language="hu" datatype="plaintext" original="SonataBlockBundle.en.xliff">
<body>
<trans-unit id="sonata.block.container">
<source>sonata.block.container</source>
<target>Konténer</target>
</trans-unit>
<trans-unit id="sonata.block.text">
<source>sonata.block.text</source>
<target>Szöveg</target>
</trans-unit>
<trans-unit id="sonata.block.rss">
<source>sonata.block.rss</source>
<target>RSS hírfolyam</target>
</trans-unit>
<trans-unit id="sonata.block.menu">
<source>sonata.block.menu</source>
<target>Menü</target>
</trans-unit>
<trans-unit id="sonata.block.template">
<source>sonata.block.template</source>
<target>Sablon</target>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file source-language="en" target-language="it" datatype="plaintext" original="SonataBlockBundle.en.xliff">
<body>
<trans-unit id="sonata.block.container">
<source>sonata.block.container</source>
<target>Contenitore</target>
</trans-unit>
<trans-unit id="sonata.block.text">
<source>sonata.block.text</source>
<target>Testo semplice</target>
</trans-unit>
<trans-unit id="sonata.block.rss">
<source>sonata.block.rss</source>
<target>Feed RSS</target>
</trans-unit>
<trans-unit id="sonata.block.menu">
<source>sonata.block.menu</source>
<target>Menu</target>
</trans-unit>
<trans-unit id="sonata.block.template">
<source>sonata.block.template</source>
<target>Template</target>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file source-language="en" target-language="ru" datatype="plaintext" original="SonataBlockBundle.en.xliff">
<body>
<trans-unit id="sonata.block.container">
<source>sonata.block.container</source>
<target>Контейнер</target>
</trans-unit>
<trans-unit id="sonata.block.text">
<source>sonata.block.text</source>
<target>Простой текст</target>
</trans-unit>
<trans-unit id="sonata.block.rss">
<source>sonata.block.rss</source>
<target>RSS-лента</target>
</trans-unit>
<trans-unit id="sonata.block.menu">
<source>sonata.block.menu</source>
<target>Меню</target>
</trans-unit>
<trans-unit id="sonata.block.template">
<source>sonata.block.template</source>
<target>Шаблон</target>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file source-language="en" target-language="de" datatype="plaintext" original="validators.en.xliff">
<body>
<trans-unit id="sonata.block.menu.not_existing">
<source>sonata.block.menu.not_existing</source>
<target>Menü %name% ist nicht vorhanden.</target>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file source-language="en" datatype="plaintext" original="validators.en.xliff">
<body>
<trans-unit id="sonata.block.menu.not_existing">
<source>sonata.block.menu.not_existing</source>
<target>Menu %name% does not exist.</target>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file source-language="en" datatype="plaintext" original="validators.en.xliff">
<body>
<trans-unit id="sonata.block.menu.not_existing">
<source>sonata.block.menu.not_existing</source>
<target>Le menu "%name%" n'existe pas.</target>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -0,0 +1,13 @@
{#
This file is part of the Sonata package.
(c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
#}
<div id="cms-block-{{ block.id }}" class="cms-block cms-block-element">
{% block block %}EMPTY CONTENT{% endblock %}
</div>

View File

@@ -0,0 +1,29 @@
{#
This file is part of the Sonata package.
(c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
#}
{% extends sonata_block.templates.block_base %}
{# block classes are prepended with a container class #}
{% block block_class %} cms-container{% if not block.hasParent() %} cms-container-root{%endif%}{% if settings.class %} {{ settings.class }}{% endif %}{% endblock %}
{# identify a block role used by the page editor #}
{% block block_role %}container{% endblock %}
{# render container block #}
{% block block %}
{% if decorator %}{{ decorator.pre|raw }}{% endif %}
{% for child in block.children %}
{% block block_child_render %}
{{ sonata_block_render(child) }}
{% endblock %}
{% endfor %}
{% if decorator %}{{ decorator.post|raw }}{% endif %}
{% endblock %}

View File

@@ -0,0 +1,16 @@
{#
This file is part of the Sonata package.
(c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
#}
{% extends sonata_block.templates.block_base %}
{% block block %}
{{ content|raw }}
{% endblock %}

View File

@@ -0,0 +1,16 @@
{#
This file is part of the Sonata package.
(c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
#}
{% extends sonata_block.templates.block_base %}
{% block block %}
{{ knp_menu_render(menu, menu_options) }}
{% endblock %}

View File

@@ -0,0 +1,45 @@
{#
This file is part of the Sonata package.
(c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
#}
{% extends sonata_block.templates.block_base %}
{% block block %}
<div class="panel panel-default {{ settings.class }}">
{% if settings.title is not empty %}
<div class="panel-heading">
<h4 class="panel-title">
{% if settings.icon %}
<i class="{{ settings.icon }}" aria-hidden="true"></i>
{% endif %}
{% if settings.translation_domain %}
{{ settings.title|trans({}, settings.translation_domain) }}
{% else %}
{{ settings.title }}
{% endif %}
</h4>
</div>
{% endif %}
<div class="panel-body">
<div class="media">
{% for feed in feeds %}
<div class="media-body">
<h5 class="media-heading">
<a href="{{ feed.link }}" rel="nofollow" title="{{ feed.title }}">{{ feed.title }}</a>
</h5>
{{ feed.description|raw }}
</div>
{% else %}
No feeds available.
{% endfor %}
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,16 @@
{#
This file is part of the Sonata package.
(c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
#}
{% extends sonata_block.templates.block_base %}
{% block block %}
{{ settings.content|raw }}
{% endblock %}

View File

@@ -0,0 +1,19 @@
{#
This file is part of the Sonata package.
(c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
#}
{% extends sonata_block.templates.block_base %}
{% block block %}
<div class="cms-block-exception">
<h2>{{ block.name }}</h2>
<h3>{{ exception.message }}</h3>
</div>
{% endblock %}

View File

@@ -0,0 +1,24 @@
{#
This file is part of the Sonata package.
(c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
#}
{% extends sonata_block.templates.block_base %}
{% block block %}
<div class="cms-block-exception" {% if forceStyle %}style="overflow-y: scroll; min-width: 300px; max-height: 300px;"{% endif %}>
{# this is dirty but the alternative would require a new block-optimized exception css #}
{% if forceStyle %}
<link href="{{ asset('bundles/framework/css/exception_layout.css') }}" rel="stylesheet" type="text/css" media="all" />
<link href="{{ asset('bundles/framework/css/exception.css') }}" rel="stylesheet" type="text/css" media="all" />
{% endif %}
{% include '@Twig/Exception/exception.html.twig' %}
</div>
{% endblock %}

View File

@@ -0,0 +1,10 @@
{#
This file is part of the Sonata package.
(c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
#}

View File

@@ -0,0 +1,59 @@
{#
This file is part of the Sonata package.
(c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
#}
{% extends 'knp_menu.html.twig' %}
{% block list %}
{% import 'knp_menu.html.twig' as macros %}
{% if item.hasChildren and options.depth is not same as(0) and item.displayChildren %}
<div{{ macros.attributes(listAttributes) }}>
{{ block('children') }}
</div>
{% endif %}
{% endblock %}
{% block item %}
{% import 'knp_menu.html.twig' as macros %}
{% if item.displayed %}
{# building the class of the item #}
{%- set classes = item.attribute('class') is not empty ? [item.attribute('class')] : [] %}
{%- if matcher.isCurrent(item) %}
{%- set classes = classes|merge([options.currentClass]) %}
{%- elseif matcher.isAncestor(item, options.depth) %}
{%- set classes = classes|merge([options.ancestorClass]) %}
{%- endif %}
{%- if item.actsLikeFirst %}
{%- set classes = classes|merge([options.firstClass]) %}
{%- endif %}
{%- if item.actsLikeLast %}
{%- set classes = classes|merge([options.lastClass]) %}
{%- endif %}
{%- set attributes = item.attributes %}
{%- if classes is not empty %}
{%- set attributes = attributes|merge({'class': classes|join(' ')}) %}
{%- endif %}
{# displaying the item #}
{%- if item.uri is not empty and (not item.current or options.currentAsLink) %}
{{ block('linkElement') }}
{%- else %}
{{ block('spanElement') }}
{%- endif %}
{# render the list of children#}
{%- set childrenClasses = item.childrenAttribute('class') is not empty ? [item.childrenAttribute('class')] : [] %}
{%- set childrenClasses = childrenClasses|merge(['menu_level_' ~ item.level]) %}
{%- set listAttributes = item.childrenAttributes|merge({'class': childrenClasses|join(' ') }) %}
{{ block('list') }}
{% endif %}
{% endblock %}
{% block linkElement %}<a href="{{ item.uri }}"{{ macros.attributes(item.attributes|merge(item.linkAttributes)|merge(attributes)) }}>{{ block('label') }}</a>{% endblock %}
{% block spanElement %}<div{{ macros.attributes(item.attributes|merge(item.labelAttributes)|merge(attributes)) }}><h4 class="list-group-item-heading">{{ block('label') }}</h4></div>{% endblock %}

View File

@@ -0,0 +1,44 @@
{#
This file is part of the Sonata package.
(c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
#}
{% extends sonata_block.templates.block_base %}
{% block block %}
<h3>Sonata Block Template</h3>
If you want to use the <code>sonata.block.template</code> block type, you need to create a template :
<pre>
{%- verbatim -%}
{# file: '@My/Block/my_block_feature_1.html.twig' #}
{% extends sonata_block.templates.block_base %}
{% block block %}
&lt;h3&gt;The block title&lt;/h3&gt;
&lt;p&gt;
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam vel turpis at lacus
vehicula fringilla at eu lectus. Duis vitae arcu congue, porttitor nisi sit amet,
mattis metus. Nunc mollis elit ut lectus cursus luctus. Aliquam eu magna sit amet
massa volutpat auctor.
&lt;/p&gt;
{% endblock %}
{%- endverbatim -%}
</pre>
And then call it from a template with the <code>sonata_block_render</code> helper:
<pre>
{%- verbatim -%}
{{ sonata_block_render({ 'type': 'sonata.block.service.template' }, {
'template': '@My/Block/my_block_feature_1.html.twig',
}) }}
{%- endverbatim -%}
</pre>
{% endblock %}

View File

@@ -0,0 +1,201 @@
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
{% block toolbar %}
<div class="sf-toolbar-block">
<a href="{{ path('_profiler', { 'token': token, 'panel': name }) }}">
<div class="sf-toolbar-icon">
{{ include('@SonataBlock/Profiler/icon.svg') }}
<span class="sf-toolbar-value">{{ collector.getTotalBlock() }}</span>
</div>
</a>
<div class="sf-toolbar-info">
<div class="sf-toolbar-info-piece">
<b>Real Blocks</b>
<span>{{ collector.realBlocks|length }}</span>
</div>
<div class="sf-toolbar-info-piece">
<b>Containers</b>
<span>{{ collector.containers|length }}</span>
</div>
<div class="sf-toolbar-info-piece">
<b>Events</b>
<span>{{ collector.events|length }}</span>
</div>
</div>
</div>
{% endblock %}
{% block menu %}
<span class="label">
<span class="icon">
{{ include('@SonataBlock/Profiler/icon.svg') }}
</span>
<strong>Blocks{% if collector.events|length > 0 %}<strong>*</strong>{% endif %}</strong>
<span class="count">
<span>{{ collector.getTotalBlock() }}</span>
</span>
</span>
{% endblock %}
{% block panel %}
<h2>Events Blocks</h2>
<table>
<tr>
<th>code name</th>
<th>listener tag</th>
<th>Block types</th>
<th>Listeners</th>
</tr>
{% for event in collector.events %}
<tr>
<td>{{ event['template_code'] }}</td>
<td>{{ event['event_name'] }}</td>
<td>
{% for type in event['blocks'] %}
{{ type.1 }} (id:{{ type.0 }})
{% else %}
no block returned
{% endfor %}
</td>
<td>
{% for listener in event['listeners'] %}
{{ listener }}
{% else %}
no listener registered
{% endfor %}
</td>
</tr>
{% endfor %}
</table>
<h2>Real Blocks</h2>
{% set blocks = collector.realBlocks %}
<div class="tab-content">
{{ block('table_v2') }}
</div>
<h2>Containers Blocks</h2>
{% set blocks = collector.containers %}
<div class="tab-content">
{{ block('table_v2') }}
</div>
{% endblock %}
{% block table %}
<table>
<tr>
<th>Id</th>
<th>Name</th>
<th>Type</th>
<th>Mem. (diff)</th>
<th>Mem. (peak)</th>
<th>Duration</th>
</tr>
{% for id, block in blocks %}
{% set rowspan = 1 %}
{% if block.cache.handler %}
{% set rowspan = rowspan + 1 %}
{% endif %}
{% if block.assets.js|length > 0 or block.assets.css|length > 0 %}
{% set rowspan = rowspan + 1 %}
{% endif %}
<tr>
<th style="vertical-align: top" rowspan="{{ rowspan }}">{{ id }}</th>
<td>{{ block.name }}</td>
<td>{{ block.type }}</td>
<td>{{ ((block.memory_end-block.memory_start)/1000)|number_format(0) }} Kb</td>
<td>{{ (block.memory_peak/1000)|number_format(0) }} Kb</td>
<td>{{ block.duration|number_format(2) }} ms</td>
</tr>
{% if block.cache.handler %}
<tr style="vertical-align: top">
<td colspan="3">
Cache Keys: <pre>{{ block.cache.keys|json_encode() }}</pre> <br />
Contextual Keys: <pre>{{ block.cache.contextual_keys|json_encode() }}</pre>
</td>
<td colspan="2">
TTL: {{ block.cache.ttl }}s. <br />
Lifetime: {{ block.cache.lifetime }}s. <br />
Backend: {{ block.cache.handler }} <br />
Loading from cache: {% if block.cache.from_cache %}YES{% else %}NO{% endif %} <br />
</td>
</tr>
{% endif %}
{% if block.assets.js|length > 0 or block.assets.css|length > 0 %}
<tr>
<td colspan="5">
Javascripts: <pre>{{ block.assets.js|json_encode() }}</pre><br />
Stylesheets: <pre>{{ block.assets.css|json_encode() }}</pre>
</td>
</tr>
{% endif %}
{% endfor %}
</table>
{% endblock %}
{% block table_v2 %}
{% for id, block in blocks %}
<table>
<thead>
<tr>
<th colspan="2">Block {{ id }}</th>
</tr>
</thead>
<tbody>
<tr>
<th>Name</th>
<td>{{ block.name }}</td>
</tr>
<tr>
<th>Type</th>
<td>{{ block.type }}</td>
</tr>
<tr>
<th>Mem. diff / Mem. peak / Duration</th>
<td>{{ ((block.memory_end-block.memory_start)/1000)|number_format(0) }} Kb / {{ (block.memory_peak/1000)|number_format(0) }} Kb / {{ block.duration|number_format(2) }} ms</td>
</tr>
{% if block.cache.handler %}
<tr>
<th>Cache backend</th>
<td>
{{ block.cache.handler }} - Loading from cache: {% if block.cache.from_cache %}YES{% else %}NO{% endif %}
</td>
</tr>
<tr>
<th>Cache TTL / Lifetime</th>
<td>
{{ block.cache.ttl }}s. / {{ block.cache.lifetime }}s
</td>
</tr>
<tr>
<th>
Cache Informations
</th>
<td>
Cache Keys: <pre>{{ block.cache.keys|json_encode() }}</pre> <br />
Contextual Keys: <pre>{{ block.cache.contextual_keys|json_encode() }}</pre> <br />
</td>
</tr>
{% endif %}
{% if block.assets.js|length > 0 or block.assets.css|length > 0 %}
<tr>
<th>Assets</th>
<td>
Javascripts: <pre>{{ block.assets.js|json_encode() }}</pre><br />
Stylesheets: <pre>{{ block.assets.css|json_encode() }}</pre>
</td>
</tr>
{% endif %}
</tbody>
</table>
{% endfor %}
{% endblock %}

View File

@@ -0,0 +1,3 @@
<svg height="24" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
<path fill="#AAAAAA" d="M832 1024v384q0 52-38 90t-90 38h-512q-52 0-90-38t-38-90v-384q0-52 38-90t90-38h512q52 0 90 38t38 90zm0-768v384q0 52-38 90t-90 38h-512q-52 0-90-38t-38-90v-384q0-52 38-90t90-38h512q52 0 90 38t38 90zm896 768v384q0 52-38 90t-90 38h-512q-52 0-90-38t-38-90v-384q0-52 38-90t90-38h512q52 0 90 38t38 90zm0-768v384q0 52-38 90t-90 38h-512q-52 0-90-38t-38-90v-384q0-52 38-90t90-38h512q52 0 90 38t38 90z"/>
</svg>

After

Width:  |  Height:  |  Size: 505 B

View File

@@ -0,0 +1,51 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle;
use Sonata\BlockBundle\DependencyInjection\Compiler\GlobalVariablesCompilerPass;
use Sonata\BlockBundle\DependencyInjection\Compiler\TweakCompilerPass;
use Sonata\CoreBundle\Form\FormHelper;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class SonataBlockBundle extends Bundle
{
/**
* {@inheritdoc}
*/
public function build(ContainerBuilder $container)
{
$container->addCompilerPass(new TweakCompilerPass());
$container->addCompilerPass(new GlobalVariablesCompilerPass());
$this->registerFormMapping();
}
/**
* {@inheritdoc}
*/
public function boot()
{
$this->registerFormMapping();
}
/**
* Register form mapping information.
*/
public function registerFormMapping()
{
FormHelper::registerFormTypeMapping([
'sonata_block_service_choice' => 'Sonata\BlockBundle\Form\Type\ServiceListType',
'sonata_type_container_template_choice' => 'Sonata\BlockBundle\Form\Type\ContainerTemplateType',
]);
}
}

View File

@@ -0,0 +1,18 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Templating;
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface as TemplatingEngineInterface;
interface EngineInterface extends TemplatingEngineInterface
{
}

View File

@@ -0,0 +1,484 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Templating\Helper;
use Doctrine\Common\Util\ClassUtils;
use Sonata\BlockBundle\Block\BlockContextInterface;
use Sonata\BlockBundle\Block\BlockContextManagerInterface;
use Sonata\BlockBundle\Block\BlockRendererInterface;
use Sonata\BlockBundle\Block\BlockServiceManagerInterface;
use Sonata\BlockBundle\Cache\HttpCacheHandlerInterface;
use Sonata\BlockBundle\Event\BlockEvent;
use Sonata\BlockBundle\Model\BlockInterface;
use Sonata\BlockBundle\Util\RecursiveBlockIterator;
use Sonata\Cache\CacheAdapterInterface;
use Sonata\Cache\CacheManagerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Stopwatch\Stopwatch;
use Symfony\Component\Templating\Helper\Helper;
class BlockHelper extends Helper
{
/**
* @var BlockServiceManagerInterface
*/
private $blockServiceManager;
/**
* @var CacheManagerInterface|null
*/
private $cacheManager;
/**
* @var array
*/
private $cacheBlocks;
/**
* @var BlockRendererInterface
*/
private $blockRenderer;
/**
* @var BlockContextManagerInterface
*/
private $blockContextManager;
/**
* @var HttpCacheHandlerInterface|null
*/
private $cacheHandler;
/**
* @var EventDispatcherInterface
*/
private $eventDispatcher;
/**
* This property is a state variable holdings all assets used by the block for the current PHP request
* It is used to correctly render the javascripts and stylesheets tags on the main layout.
*
* @var array
*/
private $assets;
/**
* @var array
*/
private $traces;
/**
* @var Stopwatch|null
*/
private $stopwatch;
/**
* @param BlockServiceManagerInterface $blockServiceManager
* @param array $cacheBlocks
* @param BlockRendererInterface $blockRenderer
* @param BlockContextManagerInterface $blockContextManager
* @param EventDispatcherInterface $eventDispatcher
* @param CacheManagerInterface $cacheManager
* @param HttpCacheHandlerInterface $cacheHandler
* @param Stopwatch $stopwatch
*/
public function __construct(BlockServiceManagerInterface $blockServiceManager, array $cacheBlocks, BlockRendererInterface $blockRenderer,
BlockContextManagerInterface $blockContextManager, EventDispatcherInterface $eventDispatcher,
CacheManagerInterface $cacheManager = null, HttpCacheHandlerInterface $cacheHandler = null, Stopwatch $stopwatch = null)
{
$this->blockServiceManager = $blockServiceManager;
$this->cacheBlocks = $cacheBlocks;
$this->blockRenderer = $blockRenderer;
$this->eventDispatcher = $eventDispatcher;
$this->cacheManager = $cacheManager;
$this->blockContextManager = $blockContextManager;
$this->cacheHandler = $cacheHandler;
$this->stopwatch = $stopwatch;
$this->assets = [
'js' => [],
'css' => [],
];
$this->traces = [
'_events' => [],
];
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'sonata_block';
}
/**
* @param string $media Unused, only kept to not break existing code
* @param string $basePath Base path to prepend to the stylesheet urls
*
* @return array|string
*/
public function includeJavascripts($media, $basePath = '')
{
$html = '';
foreach ($this->assets['js'] as $javascript) {
$html .= "\n".sprintf('<script src="%s%s" type="text/javascript"></script>', $basePath, $javascript);
}
return $html;
}
/**
* @param string $media The css media type to use: all|screen|...
* @param string $basePath Base path to prepend to the stylesheet urls
*
* @return array|string
*/
public function includeStylesheets($media, $basePath = '')
{
if (0 === count($this->assets['css'])) {
return '';
}
$html = sprintf("<style type='text/css' media='%s'>", $media);
foreach ($this->assets['css'] as $stylesheet) {
$html .= "\n".sprintf('@import url(%s%s);', $basePath, $stylesheet);
}
$html .= "\n</style>";
return $html;
}
/**
* @param string $name
* @param array $options
*
* @return string
*/
public function renderEvent($name, array $options = [])
{
$eventName = sprintf('sonata.block.event.%s', $name);
/** @var BlockEvent $event */
$event = $this->eventDispatcher->dispatch($eventName, new BlockEvent($options));
$content = '';
foreach ($event->getBlocks() as $block) {
$content .= $this->render($block);
}
if ($this->stopwatch) {
$this->traces['_events'][uniqid()] = [
'template_code' => $name,
'event_name' => $eventName,
'blocks' => $this->getEventBlocks($event),
'listeners' => $this->getEventListeners($eventName),
];
}
return $content;
}
/**
* Check if a given block type exists.
*
* @param string $type Block type to check for
*
* @return bool
*/
public function exists($type)
{
return $this->blockContextManager->exists($type);
}
/**
* @param mixed $block
* @param array $options
*
* @return null|string
*/
public function render($block, array $options = [])
{
$blockContext = $this->blockContextManager->get($block, $options);
if (!$blockContext instanceof BlockContextInterface) {
return '';
}
$stats = [];
if ($this->stopwatch) {
$stats = $this->startTracing($blockContext->getBlock());
}
$service = $this->blockServiceManager->get($blockContext->getBlock());
$this->computeAssets($blockContext, $stats);
$useCache = $blockContext->getSetting('use_cache');
$cacheKeys = $response = false;
$cacheService = $useCache ? $this->getCacheService($blockContext->getBlock(), $stats) : false;
if ($cacheService) {
$cacheKeys = array_merge(
$service->getCacheKeys($blockContext->getBlock()),
$blockContext->getSetting('extra_cache_keys')
);
if ($this->stopwatch) {
$stats['cache']['keys'] = $cacheKeys;
}
// Please note, some cache handler will always return true (js for instance)
// This will allows to have a non cacheable block, but the global page can still be cached by
// a reverse proxy, as the generated page will never get the generated Response from the block.
if ($cacheService->has($cacheKeys)) {
$cacheElement = $cacheService->get($cacheKeys);
if ($this->stopwatch) {
$stats['cache']['from_cache'] = false;
}
if (!$cacheElement->isExpired() && $cacheElement->getData() instanceof Response) {
/* @var Response $response */
if ($this->stopwatch) {
$stats['cache']['from_cache'] = true;
}
$response = $cacheElement->getData();
}
}
}
if (!$response) {
$recorder = null;
if ($this->cacheManager) {
$recorder = $this->cacheManager->getRecorder();
if ($recorder) {
$recorder->add($blockContext->getBlock());
$recorder->push();
}
}
$response = $this->blockRenderer->render($blockContext);
$contextualKeys = $recorder ? $recorder->pop() : [];
if ($this->stopwatch) {
$stats['cache']['contextual_keys'] = $contextualKeys;
}
if ($response->isCacheable() && $cacheKeys && $cacheService) {
$cacheService->set($cacheKeys, $response, (int) $response->getTtl(), $contextualKeys);
}
}
if ($this->stopwatch) {
$stats['cache']['created_at'] = $response->getDate();
$stats['cache']['ttl'] = $response->getTtl() ?: 0;
$stats['cache']['age'] = $response->getAge();
}
// update final ttl for the whole Response
if ($this->cacheHandler) {
$this->cacheHandler->updateMetadata($response, $blockContext);
}
if ($this->stopwatch) {
$this->stopTracing($blockContext->getBlock(), $stats);
}
return $response->getContent();
}
/**
* Returns the rendering traces.
*
* @return array
*/
public function getTraces()
{
return $this->traces;
}
/**
* Traverse the parent block and its children to retrieve the correct list css and javascript only for main block.
*
* @param BlockContextInterface $blockContext
* @param array $stats
*/
protected function computeAssets(BlockContextInterface $blockContext, array &$stats = null)
{
if ($blockContext->getBlock()->hasParent()) {
return;
}
$service = $this->blockServiceManager->get($blockContext->getBlock());
$assets = [
'js' => $service->getJavascripts('all'),
'css' => $service->getStylesheets('all'),
];
if ($blockContext->getBlock()->hasChildren()) {
$iterator = new \RecursiveIteratorIterator(new RecursiveBlockIterator($blockContext->getBlock()->getChildren()));
foreach ($iterator as $block) {
$assets = [
'js' => array_merge($this->blockServiceManager->get($block)->getJavascripts('all'), $assets['js']),
'css' => array_merge($this->blockServiceManager->get($block)->getStylesheets('all'), $assets['css']),
];
}
}
if ($this->stopwatch) {
$stats['assets'] = $assets;
}
$this->assets = [
'js' => array_unique(array_merge($assets['js'], $this->assets['js'])),
'css' => array_unique(array_merge($assets['css'], $this->assets['css'])),
];
}
/**
* @param BlockInterface $block
*
* @return array
*/
protected function startTracing(BlockInterface $block)
{
if (null !== $this->stopwatch) {
$this->traces[$block->getId()] = $this->stopwatch->start(
sprintf('%s (id: %s, type: %s)', $block->getName(), $block->getId(), $block->getType())
);
}
return [
'name' => $block->getName(),
'type' => $block->getType(),
'duration' => false,
'memory_start' => memory_get_usage(true),
'memory_end' => false,
'memory_peak' => false,
'cache' => [
'keys' => [],
'contextual_keys' => [],
'handler' => false,
'from_cache' => false,
'ttl' => 0,
'created_at' => false,
'lifetime' => 0,
'age' => 0,
],
'assets' => [
'js' => [],
'css' => [],
],
];
}
/**
* @param BlockInterface $block
* @param array $stats
*/
protected function stopTracing(BlockInterface $block, array $stats)
{
$e = $this->traces[$block->getId()]->stop();
$this->traces[$block->getId()] = array_merge($stats, [
'duration' => $e->getDuration(),
'memory_end' => memory_get_usage(true),
'memory_peak' => memory_get_peak_usage(true),
]);
$this->traces[$block->getId()]['cache']['lifetime'] = $this->traces[$block->getId()]['cache']['age'] + $this->traces[$block->getId()]['cache']['ttl'];
}
/**
* @param BlockEvent $event
*
* @return array
*/
protected function getEventBlocks(BlockEvent $event)
{
$results = [];
foreach ($event->getBlocks() as $block) {
$results[] = [$block->getId(), $block->getType()];
}
return $results;
}
/**
* @param string $eventName
*
* @return array
*/
protected function getEventListeners($eventName)
{
$results = [];
foreach ($this->eventDispatcher->getListeners($eventName) as $listener) {
if ($listener instanceof \Closure) {
$results[] = '{closure}()';
} elseif (is_object($listener[0])) {
$results[] = get_class($listener[0]);
} elseif (is_string($listener[0])) {
$results[] = $listener[0];
} else {
$results[] = 'Unknown type!';
}
}
return $results;
}
/**
* @param BlockInterface $block
* @param array $stats
*
* @return CacheAdapterInterface|false
*/
protected function getCacheService(BlockInterface $block, array &$stats = null)
{
if (!$this->cacheManager) {
return false;
}
// type by block class
$class = ClassUtils::getClass($block);
$cacheServiceId = isset($this->cacheBlocks['by_class'][$class]) ? $this->cacheBlocks['by_class'][$class] : false;
// type by block service
if (!$cacheServiceId) {
$cacheServiceId = isset($this->cacheBlocks['by_type'][$block->getType()]) ? $this->cacheBlocks['by_type'][$block->getType()] : false;
}
if (!$cacheServiceId) {
return false;
}
if ($this->stopwatch) {
$stats['cache']['handler'] = $cacheServiceId;
}
return $this->cacheManager->getCacheService($cacheServiceId);
}
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\BlockBundle\Templating;
use Symfony\Bundle\FrameworkBundle\Templating\Loader\TemplateLocator as FrameworkTemplateLocator;
/**
* @deprecated since 3.9, to be removed with 4.0.
*/
class TemplateLocator extends FrameworkTemplateLocator
{
}

Some files were not shown because too many files have changed in this diff Show More