├── LICENSE ├── composer.json └── src ├── Block ├── BlockContext.php ├── BlockContextInterface.php ├── BlockContextManager.php ├── BlockContextManagerInterface.php ├── BlockLoaderChain.php ├── BlockLoaderInterface.php ├── BlockRenderer.php ├── BlockRendererInterface.php ├── BlockServiceManager.php ├── BlockServiceManagerInterface.php ├── Loader │ └── ServiceLoader.php └── Service │ ├── AbstractBlockService.php │ ├── AbstractMenuBlockService.php │ ├── BlockServiceInterface.php │ ├── ContainerBlockService.php │ ├── EditableBlockService.php │ ├── EmptyBlockService.php │ ├── MenuBlockService.php │ ├── RssBlockService.php │ ├── TemplateBlockService.php │ └── TextBlockService.php ├── Command └── DebugBlocksCommand.php ├── DependencyInjection ├── Compiler │ ├── GlobalVariablesCompilerPass.php │ └── TweakCompilerPass.php ├── Configuration.php └── SonataBlockExtension.php ├── Event └── BlockEvent.php ├── Exception ├── BlockExceptionInterface.php ├── BlockNotFoundException.php ├── BlockServiceNotFoundException.php ├── Filter │ ├── DebugOnlyFilter.php │ ├── FilterInterface.php │ ├── IgnoreClassFilter.php │ ├── KeepAllFilter.php │ └── KeepNoneFilter.php ├── Renderer │ ├── InlineDebugRenderer.php │ ├── InlineRenderer.php │ ├── MonkeyThrowRenderer.php │ └── RendererInterface.php └── Strategy │ ├── StrategyManager.php │ └── StrategyManagerInterface.php ├── Form ├── Mapper │ └── FormMapper.php └── Type │ ├── ContainerTemplateType.php │ └── ServiceListType.php ├── Menu ├── MenuRegistry.php └── MenuRegistryInterface.php ├── Meta ├── Metadata.php └── MetadataInterface.php ├── Model ├── BaseBlock.php ├── Block.php ├── BlockInterface.php └── EmptyBlock.php ├── Profiler └── DataCollector │ └── BlockDataCollector.php ├── Resources ├── config │ ├── block.php │ ├── commands.php │ ├── core.php │ ├── exception.php │ ├── form.php │ └── knp_block.php ├── meta │ └── LICENSE ├── translations │ ├── SonataBlockBundle.ar.xliff │ ├── SonataBlockBundle.de.xliff │ ├── SonataBlockBundle.en.xliff │ ├── SonataBlockBundle.fr.xliff │ ├── SonataBlockBundle.hu.xliff │ ├── SonataBlockBundle.it.xliff │ ├── SonataBlockBundle.nl.xliff │ ├── SonataBlockBundle.ru.xliff │ ├── validators.de.xliff │ ├── validators.en.xliff │ ├── validators.fr.xliff │ ├── validators.hu.xliff │ └── validators.nl.xliff └── views │ ├── Block │ ├── block_base.html.twig │ ├── block_container.html.twig │ ├── block_core_action.html.twig │ ├── block_core_menu.html.twig │ ├── block_core_rss.html.twig │ ├── block_core_text.html.twig │ ├── block_exception.html.twig │ ├── block_exception_debug.html.twig │ ├── block_no_page_available.html.twig │ ├── block_side_menu_template.html.twig │ └── block_template.html.twig │ └── Profiler │ ├── block.html.twig │ └── icon.svg ├── SonataBlockBundle.php ├── Templating └── Helper │ └── BlockHelper.php ├── Test └── BlockServiceTestCase.php ├── Twig ├── Extension │ └── BlockExtension.php └── GlobalVariables.php └── Util ├── RecursiveBlockIterator.php └── RecursiveBlockIteratorIterator.php /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2010 Thomas Rabaix 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sonata-project/block-bundle", 3 | "description": "Symfony SonataBlockBundle", 4 | "license": "MIT", 5 | "type": "symfony-bundle", 6 | "keywords": [ 7 | "sonata", 8 | "block" 9 | ], 10 | "authors": [ 11 | { 12 | "name": "Thomas Rabaix", 13 | "email": "thomas.rabaix@sonata-project.org", 14 | "homepage": "https://sonata-project.org" 15 | }, 16 | { 17 | "name": "Sonata Community", 18 | "homepage": "https://github.com/sonata-project/SonataBlockBundle/contributors" 19 | } 20 | ], 21 | "homepage": "https://docs.sonata-project.org/projects/SonataBlockBundle", 22 | "require": { 23 | "php": "^8.1", 24 | "doctrine/collections": "^1.8 || ^2.0", 25 | "doctrine/common": "^3.0", 26 | "psr/container": "^1.0 || ^2.0", 27 | "psr/log": "^2.0 || ^3.0", 28 | "sonata-project/form-extensions": "^1.4 || ^2.0", 29 | "symfony/asset": "^6.4 || ^7.1", 30 | "symfony/config": "^6.4 || ^7.1", 31 | "symfony/console": "^6.4 || ^7.1", 32 | "symfony/dependency-injection": "^6.4 || ^7.1", 33 | "symfony/event-dispatcher": "^6.4 || ^7.1", 34 | "symfony/event-dispatcher-contracts": "^2.0 || ^3.0", 35 | "symfony/form": "^6.4 || ^7.1", 36 | "symfony/framework-bundle": "^6.4 || ^7.1", 37 | "symfony/http-foundation": "^6.4 || ^7.1", 38 | "symfony/http-kernel": "^6.4 || ^7.1", 39 | "symfony/options-resolver": "^6.4 || ^7.1", 40 | "symfony/twig-bundle": "^6.4 || ^7.1", 41 | "symfony/validator": "^6.4 || ^7.1", 42 | "twig/twig": "^3.0" 43 | }, 44 | "require-dev": { 45 | "friendsofphp/php-cs-fixer": "^3.4", 46 | "knplabs/knp-menu": "^3.1", 47 | "knplabs/knp-menu-bundle": "^3.0", 48 | "matthiasnoback/symfony-config-test": "^4.2 || ^5.0", 49 | "matthiasnoback/symfony-dependency-injection-test": "^4.1 || ^5.0", 50 | "phpstan/extension-installer": "^1.0", 51 | "phpstan/phpstan": "^1.0 || ^2.0", 52 | "phpstan/phpstan-phpunit": "^1.0 || ^2.0", 53 | "phpstan/phpstan-strict-rules": "^1.0 || ^2.0", 54 | "phpstan/phpstan-symfony": "^1.0 || ^2.0", 55 | "phpunit/phpunit": "^9.5.13", 56 | "psalm/plugin-phpunit": "^0.18 || ^0.19", 57 | "psalm/plugin-symfony": "^5.0", 58 | "rector/rector": "^1.1 || ^2.0", 59 | "symfony/browser-kit": "^6.4 || ^7.1", 60 | "symfony/error-handler": "^6.4 || ^7.1", 61 | "symfony/phpunit-bridge": "^6.4 || ^7.1", 62 | "symfony/stopwatch": "^6.4 || ^7.1", 63 | "vimeo/psalm": "^5.8 || ^6.10" 64 | }, 65 | "conflict": { 66 | "knplabs/knp-menu-bundle": "<3.0" 67 | }, 68 | "suggest": { 69 | "knplabs/knp-menu-bundle": "Required to use the menu block" 70 | }, 71 | "minimum-stability": "dev", 72 | "prefer-stable": true, 73 | "autoload": { 74 | "psr-4": { 75 | "Sonata\\BlockBundle\\": "src/" 76 | } 77 | }, 78 | "autoload-dev": { 79 | "psr-4": { 80 | "Sonata\\BlockBundle\\Tests\\": "tests/" 81 | } 82 | }, 83 | "config": { 84 | "allow-plugins": { 85 | "composer/package-versions-deprecated": true, 86 | "phpstan/extension-installer": true 87 | }, 88 | "sort-packages": true 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Block/BlockContext.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Block; 15 | 16 | use Sonata\BlockBundle\Model\BlockInterface; 17 | 18 | final class BlockContext implements BlockContextInterface 19 | { 20 | /** 21 | * @param array $settings 22 | */ 23 | public function __construct( 24 | private BlockInterface $block, 25 | private array $settings = [], 26 | ) { 27 | } 28 | 29 | public function getBlock(): BlockInterface 30 | { 31 | return $this->block; 32 | } 33 | 34 | public function getSettings(): array 35 | { 36 | return $this->settings; 37 | } 38 | 39 | public function getSetting(string $name): mixed 40 | { 41 | if (!\array_key_exists($name, $this->settings)) { 42 | throw new \RuntimeException(\sprintf('Unable to find the option `%s` (%s) - define the option in the related BlockServiceInterface', $name, $this->block->getType() ?? '')); 43 | } 44 | 45 | return $this->settings[$name]; 46 | } 47 | 48 | public function setSetting(string $name, mixed $value): BlockContextInterface 49 | { 50 | if (!\array_key_exists($name, $this->settings)) { 51 | throw new \RuntimeException(\sprintf('It\'s not possible add non existing setting `%s`.', $name)); 52 | } 53 | 54 | $this->settings[$name] = $value; 55 | 56 | return $this; 57 | } 58 | 59 | public function getTemplate(): string 60 | { 61 | $template = $this->getSetting('template'); 62 | 63 | if (!\is_string($template)) { 64 | throw new \InvalidArgumentException('The "template" setting MUST be a string.'); 65 | } 66 | 67 | return $template; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Block/BlockContextInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Block; 15 | 16 | use Sonata\BlockBundle\Model\BlockInterface; 17 | 18 | interface BlockContextInterface 19 | { 20 | public function getBlock(): BlockInterface; 21 | 22 | /** 23 | * @return array 24 | */ 25 | public function getSettings(): array; 26 | 27 | public function getSetting(string $name): mixed; 28 | 29 | public function setSetting(string $name, mixed $value): self; 30 | 31 | public function getTemplate(): string; 32 | } 33 | -------------------------------------------------------------------------------- /src/Block/BlockContextManager.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Block; 15 | 16 | use Doctrine\Common\Util\ClassUtils; 17 | use Psr\Log\LoggerInterface; 18 | use Psr\Log\NullLogger; 19 | use Sonata\BlockBundle\Model\BlockInterface; 20 | use Symfony\Component\OptionsResolver\Exception\ExceptionInterface; 21 | use Symfony\Component\OptionsResolver\OptionsResolver; 22 | 23 | final class BlockContextManager implements BlockContextManagerInterface 24 | { 25 | /** 26 | * @var array> 27 | */ 28 | private array $settingsByType = []; 29 | 30 | /** 31 | * @var array> 32 | * 33 | * @phpstan-var array> 34 | */ 35 | private array $settingsByClass = []; 36 | 37 | public function __construct( 38 | private BlockLoaderInterface $blockLoader, 39 | private BlockServiceManagerInterface $blockService, 40 | private LoggerInterface $logger = new NullLogger(), 41 | ) { 42 | } 43 | 44 | public function addSettingsByType(string $type, array $settings, bool $replace = false): void 45 | { 46 | $typeSettings = $this->settingsByType[$type] ?? []; 47 | if ($replace) { 48 | $this->settingsByType[$type] = array_merge($typeSettings, $settings); 49 | } else { 50 | $this->settingsByType[$type] = array_merge($settings, $typeSettings); 51 | } 52 | } 53 | 54 | public function addSettingsByClass(string $class, array $settings, bool $replace = false): void 55 | { 56 | $classSettings = $this->settingsByClass[$class] ?? []; 57 | if ($replace) { 58 | $this->settingsByClass[$class] = array_merge($classSettings, $settings); 59 | } else { 60 | $this->settingsByClass[$class] = array_merge($settings, $classSettings); 61 | } 62 | } 63 | 64 | public function exists(string $type): bool 65 | { 66 | return $this->blockLoader->exists($type); 67 | } 68 | 69 | public function get($meta, array $settings = []): BlockContextInterface 70 | { 71 | if (!$meta instanceof BlockInterface) { 72 | $block = $this->blockLoader->load($meta); 73 | 74 | if (\is_array($meta) && isset($meta['settings'])) { 75 | // merge user settings 76 | $settings = array_merge($meta['settings'], $settings); 77 | } 78 | 79 | $block->setSettings($settings); 80 | } else { 81 | $block = $meta; 82 | } 83 | 84 | try { 85 | $settings = $this->resolve($block, array_merge($block->getSettings(), $settings)); 86 | } catch (ExceptionInterface $e) { 87 | $this->logger->error(\sprintf( 88 | '[cms::blockContext] block.id=%s - error while resolving options - %s', 89 | $block->getId() ?? '', 90 | $e->getMessage() 91 | )); 92 | 93 | $template = $block->getSetting('template'); 94 | 95 | $settings = $this->resolve( 96 | $block, 97 | $settings + (\is_string($template) ? ['template' => $template] : []) 98 | ); 99 | } 100 | 101 | return new BlockContext($block, $settings); 102 | } 103 | 104 | private function configureSettings(OptionsResolver $optionsResolver, BlockInterface $block): void 105 | { 106 | // defaults for all blocks 107 | $optionsResolver->setDefaults([ 108 | 'attr' => [], 109 | ]); 110 | 111 | $optionsResolver->setDefined('template'); 112 | 113 | $optionsResolver 114 | ->addAllowedTypes('attr', 'array') 115 | ->addAllowedTypes('template', 'string'); 116 | 117 | // add type and class settings for block 118 | $class = ClassUtils::getClass($block); 119 | $settingsByType = $this->settingsByType[$block->getType() ?? ''] ?? []; 120 | $settingsByClass = $this->settingsByClass[$class] ?? []; 121 | $optionsResolver->setDefaults(array_merge($settingsByType, $settingsByClass)); 122 | } 123 | 124 | /** 125 | * @param array $settings 126 | * 127 | * @return array 128 | */ 129 | private function resolve(BlockInterface $block, array $settings): array 130 | { 131 | $optionsResolver = new OptionsResolver(); 132 | 133 | $this->configureSettings($optionsResolver, $block); 134 | 135 | $service = $this->blockService->get($block); 136 | $service->configureSettings($optionsResolver); 137 | 138 | return $optionsResolver->resolve($settings); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/Block/BlockContextManagerInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Block; 15 | 16 | use Sonata\BlockBundle\Exception\BlockNotFoundException; 17 | use Sonata\BlockBundle\Model\BlockInterface; 18 | 19 | interface BlockContextManagerInterface 20 | { 21 | /** 22 | * Add settings for a block service. 23 | * 24 | * @param string $type block service 25 | * @param array $settings 26 | * @param bool $replace replace existing settings 27 | */ 28 | public function addSettingsByType(string $type, array $settings, bool $replace = false): void; 29 | 30 | /** 31 | * Add settings for a block class. 32 | * 33 | * @param string $class block class 34 | * @param array $settings 35 | * @param bool $replace replace existing settings 36 | * 37 | * @phpstan-param class-string $class 38 | */ 39 | public function addSettingsByClass(string $class, array $settings, bool $replace = false): void; 40 | 41 | /** 42 | * @param string|array|BlockInterface $meta Data send to the loader to load a block, can be anything... 43 | * @param array $settings 44 | * 45 | * @throws BlockNotFoundException 46 | */ 47 | public function get($meta, array $settings = []): BlockContextInterface; 48 | 49 | public function exists(string $type): bool; 50 | } 51 | -------------------------------------------------------------------------------- /src/Block/BlockLoaderChain.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Block; 15 | 16 | use Sonata\BlockBundle\Exception\BlockNotFoundException; 17 | use Sonata\BlockBundle\Model\BlockInterface; 18 | 19 | final class BlockLoaderChain implements BlockLoaderInterface 20 | { 21 | /** 22 | * @param BlockLoaderInterface[] $loaders 23 | */ 24 | public function __construct(private array $loaders) 25 | { 26 | } 27 | 28 | /** 29 | * Check if a given block type exists. 30 | * 31 | * @param string $type Block type to check for 32 | */ 33 | public function exists(string $type): bool 34 | { 35 | foreach ($this->loaders as $loader) { 36 | if ($loader->exists($type)) { 37 | return true; 38 | } 39 | } 40 | 41 | return false; 42 | } 43 | 44 | public function load($configuration): BlockInterface 45 | { 46 | if (!\is_string($configuration) && !\is_array($configuration)) { 47 | throw new \TypeError(\sprintf( 48 | 'Argument 1 passed to %s must be of type string or array, %s given', 49 | __METHOD__, 50 | \gettype($configuration) 51 | )); 52 | } 53 | 54 | foreach ($this->loaders as $loader) { 55 | if ($loader->support($configuration)) { 56 | return $loader->load($configuration); 57 | } 58 | } 59 | 60 | throw new BlockNotFoundException(); 61 | } 62 | 63 | public function support($configuration): bool 64 | { 65 | if (!\is_string($configuration) && !\is_array($configuration)) { 66 | throw new \TypeError(\sprintf( 67 | 'Argument 1 passed to %s must be of type string or array, %s given', 68 | __METHOD__, 69 | \gettype($configuration) 70 | )); 71 | } 72 | 73 | return true; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Block/BlockLoaderInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Block; 15 | 16 | use Sonata\BlockBundle\Exception\BlockNotFoundException; 17 | use Sonata\BlockBundle\Model\BlockInterface; 18 | 19 | interface BlockLoaderInterface 20 | { 21 | /** 22 | * @param string|array $configuration 23 | * 24 | * @throws BlockNotFoundException if no block with that name is found 25 | */ 26 | public function load($configuration): BlockInterface; 27 | 28 | /** 29 | * @param string|array $configuration 30 | */ 31 | public function support($configuration): bool; 32 | 33 | public function exists(string $type): bool; 34 | } 35 | -------------------------------------------------------------------------------- /src/Block/BlockRenderer.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Block; 15 | 16 | use Psr\Log\LoggerInterface; 17 | use Sonata\BlockBundle\Exception\Strategy\StrategyManagerInterface; 18 | use Symfony\Component\HttpFoundation\Response; 19 | 20 | /** 21 | * Handles the execution and rendering of a block. 22 | */ 23 | final class BlockRenderer implements BlockRendererInterface 24 | { 25 | public function __construct( 26 | private BlockServiceManagerInterface $blockServiceManager, 27 | private StrategyManagerInterface $exceptionStrategyManager, 28 | private ?LoggerInterface $logger = null, 29 | ) { 30 | } 31 | 32 | public function render(BlockContextInterface $blockContext, ?Response $response = null): Response 33 | { 34 | $block = $blockContext->getBlock(); 35 | 36 | if (null !== $this->logger) { 37 | $this->logger->info( 38 | \sprintf('[cms::renderBlock] block.id=%d, block.type=%s', $block->getId() ?? '', $block->getType() ?? '') 39 | ); 40 | } 41 | 42 | try { 43 | $service = $this->blockServiceManager->get($block); 44 | $service->load($block); 45 | 46 | $response = $service->execute($blockContext, $response ?? new Response()); 47 | } catch (\Throwable $exception) { 48 | if (null !== $this->logger) { 49 | $this->logger->error(\sprintf( 50 | '[cms::renderBlock] block.id=%d - error while rendering block - %s', 51 | $block->getId() ?? '', 52 | $exception->getMessage() 53 | ), compact('exception')); 54 | } 55 | 56 | $response = $this->exceptionStrategyManager->handleException($exception, $blockContext->getBlock(), $response); 57 | } 58 | 59 | return $response; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Block/BlockRendererInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Block; 15 | 16 | use Symfony\Component\HttpFoundation\Response; 17 | 18 | interface BlockRendererInterface 19 | { 20 | public function render(BlockContextInterface $blockContext, ?Response $response = null): Response; 21 | } 22 | -------------------------------------------------------------------------------- /src/Block/BlockServiceManager.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Block; 15 | 16 | use Psr\Container\ContainerInterface; 17 | use Sonata\BlockBundle\Block\Service\BlockServiceInterface; 18 | use Sonata\BlockBundle\Block\Service\EditableBlockService; 19 | use Sonata\BlockBundle\Exception\BlockServiceNotFoundException; 20 | use Sonata\BlockBundle\Model\BlockInterface; 21 | use Sonata\Form\Validator\ErrorElement; 22 | 23 | final class BlockServiceManager implements BlockServiceManagerInterface 24 | { 25 | /** 26 | * @var array 27 | */ 28 | private array $services = []; 29 | 30 | private bool $inValidate = false; 31 | 32 | /** 33 | * @var array 34 | */ 35 | private array $contexts = []; 36 | 37 | /** 38 | * @param string[] $containerTypes 39 | */ 40 | public function __construct( 41 | private ContainerInterface $container, 42 | private array $containerTypes, 43 | ) { 44 | } 45 | 46 | public function get(BlockInterface $block): BlockServiceInterface 47 | { 48 | $blockType = $block->getType(); 49 | if (null === $blockType) { 50 | throw new \RuntimeException('The block service `` does not exist'); 51 | } 52 | 53 | $this->load($blockType); 54 | 55 | $service = $this->services[$blockType]; 56 | \assert($service instanceof BlockServiceInterface); 57 | 58 | return $service; 59 | } 60 | 61 | public function getService(string $name): BlockServiceInterface 62 | { 63 | return $this->load($name); 64 | } 65 | 66 | public function has(string $name): bool 67 | { 68 | return isset($this->services[$name]); 69 | } 70 | 71 | /** 72 | * @param BlockServiceInterface|string $service 73 | */ 74 | public function add(string $name, $service, array $contexts = []): void 75 | { 76 | if (!\is_string($service) && !$service instanceof BlockServiceInterface) { 77 | throw new \TypeError(\sprintf( 78 | 'Argument 2 passed to %s() must be of type string or an object implementing %s, %s given', 79 | __METHOD__, 80 | BlockServiceInterface::class, 81 | get_debug_type($service) 82 | )); 83 | } 84 | $this->services[$name] = $service; 85 | 86 | foreach ($contexts as $context) { 87 | if (!\array_key_exists($context, $this->contexts)) { 88 | $this->contexts[$context] = []; 89 | } 90 | 91 | $this->contexts[$context][] = $name; 92 | } 93 | } 94 | 95 | public function getServices(): array 96 | { 97 | foreach ($this->services as $id) { 98 | if (\is_string($id)) { 99 | $this->load($id); 100 | } 101 | } 102 | 103 | /** @var array $services */ 104 | $services = $this->services; 105 | 106 | return $services; 107 | } 108 | 109 | public function getServicesByContext(string $context, bool $includeContainers = true): array 110 | { 111 | if (!\array_key_exists($context, $this->contexts)) { 112 | return []; 113 | } 114 | 115 | $services = []; 116 | 117 | foreach ($this->contexts[$context] as $name) { 118 | if (!$includeContainers && \in_array($name, $this->containerTypes, true)) { 119 | continue; 120 | } 121 | 122 | $services[$name] = $this->getService($name); 123 | } 124 | 125 | return $services; 126 | } 127 | 128 | /** 129 | * @todo: this function should be remove into a proper statefull object 130 | */ 131 | public function validate(ErrorElement $errorElement, BlockInterface $block): void 132 | { 133 | if (null === $block->getId() && null === $block->getType()) { 134 | return; 135 | } 136 | 137 | if ($this->inValidate) { 138 | return; 139 | } 140 | 141 | // As block can be nested, we only need to validate the main block, not the children 142 | try { 143 | $this->inValidate = true; 144 | 145 | $blockService = $this->get($block); 146 | 147 | if ($blockService instanceof EditableBlockService) { 148 | $blockService->validate($errorElement, $block); 149 | } 150 | 151 | $this->inValidate = false; 152 | } catch (\Exception $exception) { 153 | $this->inValidate = false; 154 | 155 | throw $exception; 156 | } 157 | } 158 | 159 | /** 160 | * @throws BlockServiceNotFoundException 161 | */ 162 | private function load(string $type): BlockServiceInterface 163 | { 164 | if (!$this->has($type)) { 165 | throw new BlockServiceNotFoundException(\sprintf('The block service `%s` does not exist', $type)); 166 | } 167 | 168 | if (!$this->services[$type] instanceof BlockServiceInterface) { 169 | $blockService = $this->container->get($type); 170 | if (!$blockService instanceof BlockServiceInterface) { 171 | throw new BlockServiceNotFoundException(\sprintf('The service %s does not implement BlockServiceInterface', $type)); 172 | } 173 | 174 | $this->services[$type] = $blockService; 175 | } 176 | 177 | return $this->services[$type]; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/Block/BlockServiceManagerInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Block; 15 | 16 | use Sonata\BlockBundle\Block\Service\BlockServiceInterface; 17 | use Sonata\BlockBundle\Model\BlockInterface; 18 | use Sonata\Form\Validator\ErrorElement; 19 | 20 | interface BlockServiceManagerInterface 21 | { 22 | /** 23 | * @param BlockServiceInterface|string $service 24 | * @param string[] $contexts 25 | */ 26 | public function add(string $name, $service, array $contexts = []): void; 27 | 28 | /** 29 | * Return the block service linked to the link. 30 | */ 31 | public function get(BlockInterface $block): BlockServiceInterface; 32 | 33 | /** 34 | * @return array 35 | */ 36 | public function getServices(): array; 37 | 38 | /** 39 | * @return array 40 | */ 41 | public function getServicesByContext(string $context, bool $includeContainers = true): array; 42 | 43 | public function has(string $name): bool; 44 | 45 | public function getService(string $name): BlockServiceInterface; 46 | 47 | public function validate(ErrorElement $errorElement, BlockInterface $block): void; 48 | } 49 | -------------------------------------------------------------------------------- /src/Block/Loader/ServiceLoader.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Block\Loader; 15 | 16 | use Sonata\BlockBundle\Block\BlockLoaderInterface; 17 | use Sonata\BlockBundle\Model\Block; 18 | use Sonata\BlockBundle\Model\BlockInterface; 19 | 20 | final class ServiceLoader implements BlockLoaderInterface 21 | { 22 | /** 23 | * @param string[] $types 24 | */ 25 | public function __construct(private array $types) 26 | { 27 | } 28 | 29 | /** 30 | * Check if a given block type exists. 31 | * 32 | * @param string $type Block type to check for 33 | */ 34 | public function exists(string $type): bool 35 | { 36 | return \in_array($type, $this->types, true); 37 | } 38 | 39 | public function load($configuration): BlockInterface 40 | { 41 | if (!\is_string($configuration) && !\is_array($configuration)) { 42 | throw new \TypeError(\sprintf( 43 | 'Argument 1 passed to %s must be of type string or array, %s given', 44 | __METHOD__, 45 | \gettype($configuration) 46 | )); 47 | } 48 | 49 | if (\is_string($configuration)) { 50 | $configuration = [ 51 | 'type' => $configuration, 52 | ]; 53 | } 54 | 55 | if (!\in_array($configuration['type'], $this->types, true)) { 56 | throw new \RuntimeException(\sprintf( 57 | 'The block type "%s" does not exist', 58 | $configuration['type'] 59 | )); 60 | } 61 | 62 | $block = new Block(); 63 | $block->setId(uniqid('', true)); 64 | $block->setType($configuration['type']); 65 | $block->setEnabled(true); 66 | $block->setCreatedAt(new \DateTime()); 67 | $block->setUpdatedAt(new \DateTime()); 68 | $block->setSettings($configuration['settings'] ?? []); 69 | 70 | return $block; 71 | } 72 | 73 | public function support($configuration): bool 74 | { 75 | if (!\is_string($configuration) && !\is_array($configuration)) { 76 | throw new \TypeError(\sprintf( 77 | 'Argument 1 passed to %s must be of type string or array, %s given', 78 | __METHOD__, 79 | \gettype($configuration) 80 | )); 81 | } 82 | 83 | if (!\is_array($configuration)) { 84 | return false; 85 | } 86 | 87 | if (!isset($configuration['type'])) { 88 | return false; 89 | } 90 | 91 | return true; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Block/Service/AbstractBlockService.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Block\Service; 15 | 16 | use Sonata\BlockBundle\Block\BlockContextInterface; 17 | use Sonata\BlockBundle\Model\BlockInterface; 18 | use Symfony\Component\HttpFoundation\Response; 19 | use Symfony\Component\OptionsResolver\OptionsResolver; 20 | use Twig\Environment; 21 | 22 | /** 23 | * @author Sullivan Senechal 24 | */ 25 | abstract class AbstractBlockService implements BlockServiceInterface 26 | { 27 | public function __construct(private Environment $twig) 28 | { 29 | } 30 | 31 | /** 32 | * Returns a Response object than can be cacheable. 33 | * 34 | * @param array $parameters 35 | */ 36 | public function renderResponse(string $view, array $parameters = [], ?Response $response = null): Response 37 | { 38 | $response ??= new Response(); 39 | 40 | $response->setContent($this->twig->render($view, $parameters)); 41 | 42 | return $response; 43 | } 44 | 45 | /** 46 | * Define the default options for the block. 47 | */ 48 | public function configureSettings(OptionsResolver $resolver): void 49 | { 50 | } 51 | 52 | public function getCacheKeys(BlockInterface $block): array 53 | { 54 | $updatedAt = $block->getUpdatedAt(); 55 | 56 | return [ 57 | 'block_id' => $block->getId(), 58 | 'updated_at' => null !== $updatedAt ? $updatedAt->format('U') : null, 59 | ]; 60 | } 61 | 62 | public function load(BlockInterface $block): void 63 | { 64 | } 65 | 66 | public function execute(BlockContextInterface $blockContext, ?Response $response = null): Response 67 | { 68 | $template = $blockContext->getTemplate(); 69 | 70 | return $this->renderResponse($template, [ 71 | 'block_context' => $blockContext, 72 | 'block' => $blockContext->getBlock(), 73 | ], $response); 74 | } 75 | 76 | public function getTwig(): Environment 77 | { 78 | return $this->twig; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Block/Service/AbstractMenuBlockService.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Block\Service; 15 | 16 | use Knp\Menu\ItemInterface; 17 | use Sonata\BlockBundle\Block\BlockContextInterface; 18 | use Sonata\BlockBundle\Form\Mapper\FormMapper; 19 | use Sonata\BlockBundle\Model\BlockInterface; 20 | use Sonata\Form\Type\ImmutableArrayType; 21 | use Sonata\Form\Validator\ErrorElement; 22 | use Symfony\Component\Form\Extension\Core\Type\CheckboxType; 23 | use Symfony\Component\Form\Extension\Core\Type\TextType; 24 | use Symfony\Component\Form\FormTypeInterface; 25 | use Symfony\Component\HttpFoundation\Response; 26 | use Symfony\Component\OptionsResolver\OptionsResolver; 27 | 28 | abstract class AbstractMenuBlockService extends AbstractBlockService implements EditableBlockService 29 | { 30 | public function execute(BlockContextInterface $blockContext, ?Response $response = null): Response 31 | { 32 | $template = $blockContext->getTemplate(); 33 | 34 | $responseSettings = [ 35 | 'menu' => $this->getMenu($blockContext), 36 | 'menu_options' => $this->getMenuOptions($blockContext->getSettings()), 37 | 'block' => $blockContext->getBlock(), 38 | 'context' => $blockContext, 39 | ]; 40 | 41 | return $this->renderResponse($template, $responseSettings, $response); 42 | } 43 | 44 | public function configureCreateForm(FormMapper $form, BlockInterface $block): void 45 | { 46 | $this->configureEditForm($form, $block); 47 | } 48 | 49 | public function configureEditForm(FormMapper $form, BlockInterface $block): void 50 | { 51 | $form->add('settings', ImmutableArrayType::class, [ 52 | 'keys' => $this->getFormSettingsKeys(), 53 | 'translation_domain' => 'SonataBlockBundle', 54 | ]); 55 | } 56 | 57 | public function validate(ErrorElement $errorElement, BlockInterface $block): void 58 | { 59 | } 60 | 61 | public function configureSettings(OptionsResolver $resolver): void 62 | { 63 | $resolver->setDefaults([ 64 | 'title' => '', 65 | 'template' => '@SonataBlock/Block/block_core_menu.html.twig', 66 | 'safe_labels' => false, 67 | 'current_class' => 'active', 68 | 'first_class' => false, 69 | 'last_class' => false, 70 | 'menu_template' => null, 71 | ]); 72 | } 73 | 74 | /** 75 | * @return array, array}> 76 | */ 77 | protected function getFormSettingsKeys(): array 78 | { 79 | return [ 80 | ['title', TextType::class, [ 81 | 'required' => false, 82 | 'label' => 'form.label_title', 83 | 'translation_domain' => 'SonataBlockBundle', 84 | ]], 85 | ['safe_labels', CheckboxType::class, [ 86 | 'required' => false, 87 | 'label' => 'form.label_safe_labels', 88 | 'translation_domain' => 'SonataBlockBundle', 89 | ]], 90 | ['current_class', TextType::class, [ 91 | 'required' => false, 92 | 'label' => 'form.label_current_class', 93 | 'translation_domain' => 'SonataBlockBundle', 94 | ]], 95 | ['first_class', TextType::class, [ 96 | 'required' => false, 97 | 'label' => 'form.label_first_class', 98 | 'translation_domain' => 'SonataBlockBundle', 99 | ]], 100 | ['last_class', TextType::class, [ 101 | 'required' => false, 102 | 'label' => 'form.label_last_class', 103 | 'translation_domain' => 'SonataBlockBundle', 104 | ]], 105 | ['menu_template', TextType::class, [ 106 | 'required' => false, 107 | 'label' => 'form.label_menu_template', 108 | 'translation_domain' => 'SonataBlockBundle', 109 | ]], 110 | ]; 111 | } 112 | 113 | /** 114 | * Gets the menu to render. 115 | */ 116 | abstract protected function getMenu(BlockContextInterface $blockContext): ItemInterface|string; 117 | 118 | /** 119 | * Replaces setting keys with knp menu item options keys. 120 | * 121 | * @param array $settings 122 | * 123 | * @return array 124 | */ 125 | private function getMenuOptions(array $settings): array 126 | { 127 | $mapping = [ 128 | 'current_class' => 'currentClass', 129 | 'first_class' => 'firstClass', 130 | 'last_class' => 'lastClass', 131 | 'safe_labels' => 'allow_safe_labels', 132 | 'menu_template' => 'template', 133 | ]; 134 | 135 | $options = []; 136 | 137 | foreach ($settings as $key => $value) { 138 | if (\array_key_exists($key, $mapping) && null !== $value) { 139 | $options[$mapping[$key]] = $value; 140 | } 141 | } 142 | 143 | return $options; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/Block/Service/BlockServiceInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Block\Service; 15 | 16 | use Sonata\BlockBundle\Block\BlockContextInterface; 17 | use Sonata\BlockBundle\Model\BlockInterface; 18 | use Symfony\Component\HttpFoundation\Response; 19 | use Symfony\Component\OptionsResolver\OptionsResolver; 20 | 21 | /** 22 | * @author Christian Gripp 23 | */ 24 | interface BlockServiceInterface 25 | { 26 | public function execute(BlockContextInterface $blockContext, ?Response $response = null): Response; 27 | 28 | public function load(BlockInterface $block): void; 29 | 30 | /** 31 | * @return array 32 | */ 33 | public function getCacheKeys(BlockInterface $block): array; 34 | 35 | /** 36 | * Define the default options for the block. 37 | */ 38 | public function configureSettings(OptionsResolver $resolver): void; 39 | } 40 | -------------------------------------------------------------------------------- /src/Block/Service/ContainerBlockService.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Block\Service; 15 | 16 | use Sonata\BlockBundle\Block\BlockContextInterface; 17 | use Sonata\BlockBundle\Form\Mapper\FormMapper; 18 | use Sonata\BlockBundle\Form\Type\ContainerTemplateType; 19 | use Sonata\BlockBundle\Meta\Metadata; 20 | use Sonata\BlockBundle\Meta\MetadataInterface; 21 | use Sonata\BlockBundle\Model\BlockInterface; 22 | use Sonata\Form\Type\CollectionType; 23 | use Sonata\Form\Type\ImmutableArrayType; 24 | use Sonata\Form\Validator\ErrorElement; 25 | use Symfony\Component\Form\Extension\Core\Type\TextareaType; 26 | use Symfony\Component\Form\Extension\Core\Type\TextType; 27 | use Symfony\Component\HttpFoundation\Response; 28 | use Symfony\Component\OptionsResolver\OptionsResolver; 29 | 30 | /** 31 | * Render children pages. 32 | * 33 | * @author Thomas Rabaix 34 | */ 35 | final class ContainerBlockService extends AbstractBlockService implements EditableBlockService 36 | { 37 | public function configureCreateForm(FormMapper $form, BlockInterface $block): void 38 | { 39 | $this->configureEditForm($form, $block); 40 | } 41 | 42 | public function configureEditForm(FormMapper $form, BlockInterface $block): void 43 | { 44 | $form->add('settings', ImmutableArrayType::class, [ 45 | 'keys' => [ 46 | ['code', TextType::class, [ 47 | 'required' => false, 48 | 'label' => 'form.label_code', 49 | 'translation_domain' => 'SonataBlockBundle', 50 | ]], 51 | ['layout', TextareaType::class, [ 52 | 'label' => 'form.label_layout', 53 | 'translation_domain' => 'SonataBlockBundle', 54 | ]], 55 | ['class', TextType::class, [ 56 | 'required' => false, 57 | 'label' => 'form.label_class', 58 | 'translation_domain' => 'SonataBlockBundle', 59 | ]], 60 | ['template', ContainerTemplateType::class, [ 61 | 'label' => 'form.label_template', 62 | 'translation_domain' => 'SonataBlockBundle', 63 | ]], 64 | ], 65 | 'translation_domain' => 'SonataBlockBundle', 66 | ]); 67 | 68 | $form->add('children', CollectionType::class); 69 | } 70 | 71 | public function execute(BlockContextInterface $blockContext, ?Response $response = null): Response 72 | { 73 | $template = $blockContext->getTemplate(); 74 | 75 | return $this->renderResponse($template, [ 76 | 'block' => $blockContext->getBlock(), 77 | 'decorator' => $this->getDecorator($blockContext->getSetting('layout')), 78 | 'settings' => $blockContext->getSettings(), 79 | ], $response); 80 | } 81 | 82 | public function validate(ErrorElement $errorElement, BlockInterface $block): void 83 | { 84 | } 85 | 86 | public function configureSettings(OptionsResolver $resolver): void 87 | { 88 | $resolver->setDefaults([ 89 | 'code' => '', 90 | 'layout' => '{{ CONTENT }}', 91 | 'class' => '', 92 | 'template' => '@SonataBlock/Block/block_container.html.twig', 93 | ]); 94 | } 95 | 96 | public function getMetadata(): MetadataInterface 97 | { 98 | return new Metadata('sonata.block.service.container', null, null, 'SonataBlockBundle', [ 99 | 'class' => 'fa fa-square-o', 100 | ]); 101 | } 102 | 103 | /** 104 | * Returns a decorator object/array from the container layout setting. 105 | * 106 | * @return array{pre?: string, post?: string} 107 | */ 108 | private function getDecorator(string $layout): array 109 | { 110 | $key = '{{ CONTENT }}'; 111 | if (!str_contains($layout, $key)) { 112 | return []; 113 | } 114 | 115 | $segments = explode($key, $layout); 116 | $decorator = [ 117 | 'pre' => $segments[0] ?? '', 118 | 'post' => $segments[1] ?? '', 119 | ]; 120 | 121 | return $decorator; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/Block/Service/EditableBlockService.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Block\Service; 15 | 16 | use Sonata\BlockBundle\Form\Mapper\FormMapper; 17 | use Sonata\BlockBundle\Meta\MetadataInterface; 18 | use Sonata\BlockBundle\Model\BlockInterface; 19 | use Sonata\Form\Validator\ErrorElement; 20 | 21 | /** 22 | * @author Christian Gripp 23 | */ 24 | interface EditableBlockService 25 | { 26 | public function configureEditForm(FormMapper $form, BlockInterface $block): void; 27 | 28 | public function configureCreateForm(FormMapper $form, BlockInterface $block): void; 29 | 30 | public function validate(ErrorElement $errorElement, BlockInterface $block): void; 31 | 32 | public function getMetadata(): MetadataInterface; 33 | } 34 | -------------------------------------------------------------------------------- /src/Block/Service/EmptyBlockService.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Block\Service; 15 | 16 | use Sonata\BlockBundle\Block\BlockContextInterface; 17 | use Symfony\Component\HttpFoundation\Response; 18 | 19 | final class EmptyBlockService extends AbstractBlockService 20 | { 21 | public function execute(BlockContextInterface $blockContext, ?Response $response = null): Response 22 | { 23 | return new Response(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Block/Service/MenuBlockService.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Block\Service; 15 | 16 | use Knp\Menu\ItemInterface; 17 | use Knp\Menu\Provider\MenuProviderInterface; 18 | use Sonata\BlockBundle\Block\BlockContextInterface; 19 | use Sonata\BlockBundle\Menu\MenuRegistryInterface; 20 | use Sonata\BlockBundle\Meta\Metadata; 21 | use Sonata\BlockBundle\Meta\MetadataInterface; 22 | use Sonata\BlockBundle\Model\BlockInterface; 23 | use Sonata\Form\Validator\ErrorElement; 24 | use Symfony\Component\Form\Extension\Core\Type\ChoiceType; 25 | use Symfony\Component\OptionsResolver\OptionsResolver; 26 | use Twig\Environment; 27 | 28 | /** 29 | * @author Hugo Briand 30 | */ 31 | final class MenuBlockService extends AbstractMenuBlockService 32 | { 33 | public function __construct( 34 | Environment $twig, 35 | private MenuProviderInterface $menuProvider, 36 | private MenuRegistryInterface $menuRegistry, 37 | ) { 38 | parent::__construct($twig); 39 | } 40 | 41 | public function validate(ErrorElement $errorElement, BlockInterface $block): void 42 | { 43 | $name = $block->getSetting('menu_name'); 44 | if (null !== $name && '' !== $name && !$this->menuProvider->has($name)) { 45 | // If we specified a menu_name, check that it exists 46 | $errorElement->with('menu_name') 47 | ->addViolation('sonata.block.menu.not_existing', ['%name%' => $name]) 48 | ->end(); 49 | } 50 | } 51 | 52 | public function configureSettings(OptionsResolver $resolver): void 53 | { 54 | parent::configureSettings($resolver); 55 | 56 | $resolver->setDefaults([ 57 | 'menu_name' => '', 58 | ]); 59 | } 60 | 61 | public function getMetadata(): MetadataInterface 62 | { 63 | return new Metadata('sonata.block.service.menu', null, null, 'SonataBlockBundle', [ 64 | 'class' => 'fa fa-bars', 65 | ]); 66 | } 67 | 68 | protected function getFormSettingsKeys(): array 69 | { 70 | $choiceOptions = [ 71 | 'required' => true, 72 | 'label' => 'form.label_menu_name', 73 | 'translation_domain' => 'SonataBlockBundle', 74 | ]; 75 | 76 | $choiceOptions['choices'] = array_flip($this->menuRegistry->getAliasNames()); 77 | 78 | return array_merge( 79 | parent::getFormSettingsKeys(), 80 | [ 81 | ['menu_name', ChoiceType::class, $choiceOptions], 82 | ] 83 | ); 84 | } 85 | 86 | protected function getMenu(BlockContextInterface $blockContext): ItemInterface|string 87 | { 88 | $settings = $blockContext->getSettings(); 89 | 90 | return $settings['menu_name']; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/Block/Service/RssBlockService.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Block\Service; 15 | 16 | use Sonata\BlockBundle\Block\BlockContextInterface; 17 | use Sonata\BlockBundle\Form\Mapper\FormMapper; 18 | use Sonata\BlockBundle\Meta\Metadata; 19 | use Sonata\BlockBundle\Meta\MetadataInterface; 20 | use Sonata\BlockBundle\Model\BlockInterface; 21 | use Sonata\Form\Type\ImmutableArrayType; 22 | use Sonata\Form\Validator\ErrorElement; 23 | use Symfony\Component\Form\Extension\Core\Type\TextType; 24 | use Symfony\Component\Form\Extension\Core\Type\UrlType; 25 | use Symfony\Component\HttpFoundation\Response; 26 | use Symfony\Component\OptionsResolver\OptionsResolver; 27 | use Symfony\Component\Validator\Constraints\Length; 28 | use Symfony\Component\Validator\Constraints\NotBlank; 29 | use Symfony\Component\Validator\Constraints\NotNull; 30 | 31 | /** 32 | * @author Thomas Rabaix 33 | */ 34 | final class RssBlockService extends AbstractBlockService implements EditableBlockService 35 | { 36 | public function configureSettings(OptionsResolver $resolver): void 37 | { 38 | $resolver->setDefaults([ 39 | 'url' => false, 40 | 'title' => null, 41 | 'translation_domain' => null, 42 | 'icon' => 'fa fa-rss-square', 43 | 'class' => null, 44 | 'template' => '@SonataBlock/Block/block_core_rss.html.twig', 45 | ]); 46 | } 47 | 48 | public function configureCreateForm(FormMapper $form, BlockInterface $block): void 49 | { 50 | $this->configureEditForm($form, $block); 51 | } 52 | 53 | public function configureEditForm(FormMapper $form, BlockInterface $block): void 54 | { 55 | $form->add('settings', ImmutableArrayType::class, [ 56 | 'keys' => [ 57 | ['url', UrlType::class, [ 58 | 'required' => false, 59 | 'label' => 'form.label_url', 60 | 'translation_domain' => 'SonataBlockBundle', 61 | ]], 62 | ['title', TextType::class, [ 63 | 'label' => 'form.label_title', 64 | 'translation_domain' => 'SonataBlockBundle', 65 | 'required' => false, 66 | ]], 67 | ['translation_domain', TextType::class, [ 68 | 'label' => 'form.label_translation_domain', 69 | 'translation_domain' => 'SonataBlockBundle', 70 | 'required' => false, 71 | ]], 72 | ['icon', TextType::class, [ 73 | 'label' => 'form.label_icon', 74 | 'translation_domain' => 'SonataBlockBundle', 75 | 'required' => false, 76 | ]], 77 | ['class', TextType::class, [ 78 | 'label' => 'form.label_class', 79 | 'translation_domain' => 'SonataBlockBundle', 80 | 'required' => false, 81 | ]], 82 | ], 83 | 'translation_domain' => 'SonataBlockBundle', 84 | ]); 85 | } 86 | 87 | public function validate(ErrorElement $errorElement, BlockInterface $block): void 88 | { 89 | $errorElement 90 | ->with('settings[url]') 91 | ->addConstraint(new NotNull()) 92 | ->addConstraint(new NotBlank()) 93 | ->end() 94 | ->with('settings[title]') 95 | ->addConstraint(new NotNull()) 96 | ->addConstraint(new NotBlank()) 97 | ->addConstraint(new Length(['max' => 50])) 98 | ->end(); 99 | } 100 | 101 | public function execute(BlockContextInterface $blockContext, ?Response $response = null): Response 102 | { 103 | // merge settings 104 | $settings = $blockContext->getSettings(); 105 | 106 | $feeds = false; 107 | if (\is_string($settings['url'])) { 108 | $options = [ 109 | 'http' => [ 110 | 'user_agent' => 'Sonata/RSS Reader', 111 | 'timeout' => 2, 112 | ], 113 | ]; 114 | 115 | // retrieve contents with a specific stream context to avoid php errors 116 | $content = @file_get_contents($settings['url'], false, stream_context_create($options)); 117 | 118 | if (false !== $content && '' !== $content) { 119 | // generate a simple xml element 120 | try { 121 | $feeds = new \SimpleXMLElement($content); 122 | $feeds = $feeds->channel->item; 123 | } catch (\Exception) { 124 | // silently fail error 125 | } 126 | } 127 | } 128 | 129 | $template = $blockContext->getTemplate(); 130 | 131 | return $this->renderResponse($template, [ 132 | 'feeds' => $feeds, 133 | 'block' => $blockContext->getBlock(), 134 | 'settings' => $settings, 135 | ], $response); 136 | } 137 | 138 | public function getMetadata(): MetadataInterface 139 | { 140 | return new Metadata('sonata.block.service.rss', null, null, 'SonataBlockBundle', [ 141 | 'class' => 'fa fa-rss-square', 142 | ]); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/Block/Service/TemplateBlockService.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Block\Service; 15 | 16 | use Sonata\BlockBundle\Block\BlockContextInterface; 17 | use Sonata\BlockBundle\Form\Mapper\FormMapper; 18 | use Sonata\BlockBundle\Meta\Metadata; 19 | use Sonata\BlockBundle\Meta\MetadataInterface; 20 | use Sonata\BlockBundle\Model\BlockInterface; 21 | use Sonata\Form\Type\ImmutableArrayType; 22 | use Sonata\Form\Validator\ErrorElement; 23 | use Symfony\Component\HttpFoundation\Response; 24 | use Symfony\Component\OptionsResolver\OptionsResolver; 25 | 26 | /** 27 | * @author Thomas Rabaix 28 | */ 29 | final class TemplateBlockService extends AbstractBlockService implements EditableBlockService 30 | { 31 | public function execute(BlockContextInterface $blockContext, ?Response $response = null): Response 32 | { 33 | $template = $blockContext->getTemplate(); 34 | 35 | return $this->renderResponse($template, [ 36 | 'block' => $blockContext->getBlock(), 37 | 'settings' => $blockContext->getSettings(), 38 | ], $response); 39 | } 40 | 41 | public function configureCreateForm(FormMapper $form, BlockInterface $block): void 42 | { 43 | $this->configureEditForm($form, $block); 44 | } 45 | 46 | public function configureEditForm(FormMapper $form, BlockInterface $block): void 47 | { 48 | $form->add('settings', ImmutableArrayType::class, [ 49 | 'keys' => [ 50 | ['template', null, [ 51 | 'label' => 'form.label_template', 52 | 'translation_domain' => 'SonataBlockBundle', 53 | ]], 54 | ], 55 | 'translation_domain' => 'SonataBlockBundle', 56 | ]); 57 | } 58 | 59 | public function validate(ErrorElement $errorElement, BlockInterface $block): void 60 | { 61 | } 62 | 63 | public function configureSettings(OptionsResolver $resolver): void 64 | { 65 | $resolver->setDefaults([ 66 | 'template' => '@SonataBlock/Block/block_template.html.twig', 67 | ]); 68 | } 69 | 70 | public function getMetadata(): MetadataInterface 71 | { 72 | return new Metadata('sonata.block.service.template', null, null, 'SonataBlockBundle', [ 73 | 'class' => 'fa fa-code', 74 | ]); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Block/Service/TextBlockService.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Block\Service; 15 | 16 | use Sonata\BlockBundle\Block\BlockContextInterface; 17 | use Sonata\BlockBundle\Form\Mapper\FormMapper; 18 | use Sonata\BlockBundle\Meta\Metadata; 19 | use Sonata\BlockBundle\Meta\MetadataInterface; 20 | use Sonata\BlockBundle\Model\BlockInterface; 21 | use Sonata\Form\Type\ImmutableArrayType; 22 | use Sonata\Form\Validator\ErrorElement; 23 | use Symfony\Component\Form\Extension\Core\Type\TextareaType; 24 | use Symfony\Component\HttpFoundation\Response; 25 | use Symfony\Component\OptionsResolver\OptionsResolver; 26 | 27 | /** 28 | * @author Thomas Rabaix 29 | */ 30 | final class TextBlockService extends AbstractBlockService implements EditableBlockService 31 | { 32 | public function execute(BlockContextInterface $blockContext, ?Response $response = null): Response 33 | { 34 | $template = $blockContext->getTemplate(); 35 | 36 | return $this->renderResponse($template, [ 37 | 'block' => $blockContext->getBlock(), 38 | 'settings' => $blockContext->getSettings(), 39 | ], $response); 40 | } 41 | 42 | public function configureCreateForm(FormMapper $form, BlockInterface $block): void 43 | { 44 | $this->configureEditForm($form, $block); 45 | } 46 | 47 | public function configureEditForm(FormMapper $form, BlockInterface $block): void 48 | { 49 | $form->add('settings', ImmutableArrayType::class, [ 50 | 'keys' => [ 51 | ['content', TextareaType::class, [ 52 | 'label' => 'form.label_content', 53 | 'translation_domain' => 'SonataBlockBundle', 54 | ]], 55 | ], 56 | 'translation_domain' => 'SonataBlockBundle', 57 | ]); 58 | } 59 | 60 | public function validate(ErrorElement $errorElement, BlockInterface $block): void 61 | { 62 | } 63 | 64 | public function configureSettings(OptionsResolver $resolver): void 65 | { 66 | $resolver->setDefaults([ 67 | 'content' => 'Insert your custom content here', 68 | 'template' => '@SonataBlock/Block/block_core_text.html.twig', 69 | ]); 70 | } 71 | 72 | public function getMetadata(): MetadataInterface 73 | { 74 | return new Metadata('sonata.block.service.text', null, null, 'SonataBlockBundle', [ 75 | 'class' => 'fa fa-file-text-o', 76 | ]); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Command/DebugBlocksCommand.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Command; 15 | 16 | use Sonata\BlockBundle\Block\BlockServiceManagerInterface; 17 | use Sonata\BlockBundle\Block\Service\EditableBlockService; 18 | use Symfony\Component\Console\Attribute\AsCommand; 19 | use Symfony\Component\Console\Command\Command; 20 | use Symfony\Component\Console\Input\InputInterface; 21 | use Symfony\Component\Console\Input\InputOption; 22 | use Symfony\Component\Console\Output\OutputInterface; 23 | use Symfony\Component\OptionsResolver\Exception\MissingOptionsException; 24 | use Symfony\Component\OptionsResolver\OptionsResolver; 25 | 26 | #[AsCommand(name: 'debug:sonata:block', description: 'Debug all blocks available, show default settings of each block')] 27 | final class DebugBlocksCommand extends Command 28 | { 29 | public function __construct(private BlockServiceManagerInterface $blockManager) 30 | { 31 | parent::__construct(); 32 | } 33 | 34 | public function configure(): void 35 | { 36 | $this 37 | ->addOption('context', 'c', InputOption::VALUE_REQUIRED, 'display service for the specified context'); 38 | } 39 | 40 | public function execute(InputInterface $input, OutputInterface $output): int 41 | { 42 | $context = $input->getOption('context'); 43 | if (\is_string($context)) { 44 | $services = $this->blockManager->getServicesByContext($context); 45 | } else { 46 | $services = $this->blockManager->getServices(); 47 | } 48 | 49 | foreach ($services as $code => $service) { 50 | $output->writeln(''); 51 | 52 | $title = ''; 53 | if ($service instanceof EditableBlockService) { 54 | $title = \sprintf(' (%s)', $service->getMetadata()->getTitle()); 55 | } 56 | $output->writeln(\sprintf('>> %s%s', $code, $title)); 57 | 58 | $resolver = new OptionsResolver(); 59 | $service->configureSettings($resolver); 60 | 61 | try { 62 | foreach ($resolver->resolve() as $key => $val) { 63 | $output->writeln(\sprintf(' %-30s%s', $key, json_encode($val, \JSON_THROW_ON_ERROR))); 64 | } 65 | } catch (MissingOptionsException) { 66 | foreach ($resolver->getDefinedOptions() as $option) { 67 | $output->writeln(\sprintf(' %s', $option)); 68 | } 69 | } 70 | } 71 | 72 | $output->writeln('done!'); 73 | 74 | return 0; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/DependencyInjection/Compiler/GlobalVariablesCompilerPass.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\DependencyInjection\Compiler; 15 | 16 | use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; 17 | use Symfony\Component\DependencyInjection\ContainerBuilder; 18 | use Symfony\Component\DependencyInjection\Reference; 19 | 20 | /** 21 | * GlobalVariablesCompilerPass. 22 | * 23 | * @author Thomas Rabaix 24 | */ 25 | final class GlobalVariablesCompilerPass implements CompilerPassInterface 26 | { 27 | public function process(ContainerBuilder $container): void 28 | { 29 | $container->getDefinition('twig') 30 | ->addMethodCall('addGlobal', ['sonata_block', new Reference('sonata.block.twig.global')]); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/DependencyInjection/Compiler/TweakCompilerPass.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\DependencyInjection\Compiler; 15 | 16 | use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; 17 | use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; 18 | use Symfony\Component\DependencyInjection\ContainerBuilder; 19 | use Symfony\Component\DependencyInjection\Reference; 20 | 21 | /** 22 | * Link the block service to the Page Manager. 23 | * 24 | * @author Thomas Rabaix 25 | */ 26 | final class TweakCompilerPass implements CompilerPassInterface 27 | { 28 | public function process(ContainerBuilder $container): void 29 | { 30 | $manager = $container->getDefinition('sonata.block.manager'); 31 | $registry = $container->getDefinition('sonata.block.menu.registry'); 32 | 33 | /** @var array $blocks */ 34 | $blocks = $container->getParameter('sonata_block.blocks'); 35 | /** @var array $blockTypes */ 36 | $blockTypes = $container->getParameter('sonata_blocks.block_types'); 37 | /** @var string[] $defaultContexts */ 38 | $defaultContexts = $container->getParameter('sonata_blocks.default_contexts'); 39 | 40 | /** @var array $blockServiceReferences */ 41 | $blockServiceReferences = []; 42 | foreach ($container->findTaggedServiceIds('sonata.block') as $id => $tags) { 43 | $settings = $this->createBlockSettings($tags, $defaultContexts); 44 | 45 | // Register blocks dynamically 46 | if (!\array_key_exists($id, $blocks)) { 47 | $blocks[$id] = $settings; 48 | } 49 | if (!\in_array($id, $blockTypes, true)) { 50 | $blockTypes[] = $id; 51 | } 52 | 53 | $manager->addMethodCall('add', [$id, $id, $settings['contexts']]); 54 | 55 | $blockServiceReferences[$id] = new Reference($id); 56 | } 57 | 58 | $manager->setArgument(0, ServiceLocatorTagPass::register($container, $blockServiceReferences)); 59 | 60 | foreach ($container->findTaggedServiceIds('knp_menu.menu') as $serviceId => $tags) { 61 | foreach ($tags as $attributes) { 62 | if (!isset($attributes['alias'])) { 63 | throw new \InvalidArgumentException(\sprintf('The alias is not defined in the "knp_menu.menu" tag for the service "%s"', $serviceId)); 64 | } 65 | $registry->addMethodCall('add', [$attributes['alias']]); 66 | } 67 | } 68 | 69 | $services = []; 70 | foreach ($container->findTaggedServiceIds('sonata.block.loader') as $serviceId => $tags) { 71 | $services[] = new Reference($serviceId); 72 | } 73 | 74 | $container->setParameter('sonata_block.blocks', $blocks); 75 | $container->setParameter('sonata_blocks.block_types', $blockTypes); 76 | 77 | $container->getDefinition('sonata.block.loader.service')->replaceArgument(0, $blockTypes); 78 | $container->getDefinition('sonata.block.loader.chain')->replaceArgument(0, $services); 79 | 80 | $this->applyContext($container); 81 | } 82 | 83 | /** 84 | * Apply configurations to the context manager. 85 | */ 86 | private function applyContext(ContainerBuilder $container): void 87 | { 88 | $definition = $container->findDefinition('sonata.block.context_manager'); 89 | 90 | /** @var array> $blocks */ 91 | $blocks = $container->getParameter('sonata_block.blocks'); 92 | foreach ($blocks as $service => $settings) { 93 | if (\count($settings['settings']) > 0) { 94 | $definition->addMethodCall('addSettingsByType', [$service, $settings['settings'], true]); 95 | } 96 | } 97 | 98 | /** @var array> $blocksByClass */ 99 | $blocksByClass = $container->getParameter('sonata_block.blocks_by_class'); 100 | foreach ($blocksByClass as $class => $settings) { 101 | if (\count($settings['settings']) > 0) { 102 | $definition->addMethodCall('addSettingsByClass', [$class, $settings['settings'], true]); 103 | } 104 | } 105 | } 106 | 107 | /** 108 | * @param array> $tags 109 | * @param string[] $defaultContexts 110 | * 111 | * @return array 112 | */ 113 | private function createBlockSettings(array $tags = [], array $defaultContexts = []): array 114 | { 115 | $contexts = $this->getContextFromTags($tags); 116 | 117 | if (0 === \count($contexts)) { 118 | $contexts = $defaultContexts; 119 | } 120 | 121 | return [ 122 | 'contexts' => $contexts, 123 | 'templates' => [], 124 | 'settings' => [], 125 | ]; 126 | } 127 | 128 | /** 129 | * @param array> $tags 130 | * 131 | * @return string[] 132 | */ 133 | private function getContextFromTags(array $tags): array 134 | { 135 | $contexts = []; 136 | foreach ($tags as $attribute) { 137 | if (\array_key_exists('context', $attribute) && \is_string($attribute['context'])) { 138 | $contexts[] = $attribute['context']; 139 | } 140 | } 141 | 142 | return $contexts; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\DependencyInjection; 15 | 16 | use Symfony\Component\Config\Definition\Builder\TreeBuilder; 17 | use Symfony\Component\Config\Definition\ConfigurationInterface; 18 | use Symfony\Component\DependencyInjection\ContainerBuilder; 19 | 20 | /** 21 | * This is the class that validates and merges configuration from your app/config files. 22 | * 23 | * To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html#cookbook-bundles-extension-config-class} 24 | */ 25 | final class Configuration implements ConfigurationInterface 26 | { 27 | /** 28 | * @param array $defaultContainerTemplates 29 | */ 30 | public function __construct(private array $defaultContainerTemplates) 31 | { 32 | } 33 | 34 | /** 35 | * @psalm-suppress UndefinedMethod 36 | * 37 | * @see https://github.com/psalm/psalm-plugin-symfony/issues/174 38 | */ 39 | public function getConfigTreeBuilder(): TreeBuilder 40 | { 41 | $treeBuilder = new TreeBuilder('sonata_block'); 42 | 43 | $node = $treeBuilder->getRootNode(); 44 | 45 | $node 46 | ->fixXmlConfig('default_context') 47 | ->fixXmlConfig('template') 48 | ->fixXmlConfig('block') 49 | ->fixXmlConfig('block_by_class') 50 | ->validate() 51 | ->always(static function (array &$value): array { 52 | foreach ($value['blocks'] as &$block) { 53 | if (0 === \count($block['contexts'])) { 54 | $block['contexts'] = $value['default_contexts']; 55 | } 56 | } 57 | 58 | return $value; 59 | }) 60 | ->end() 61 | ->children() 62 | ->arrayNode('profiler') 63 | ->addDefaultsIfNotSet() 64 | ->children() 65 | ->scalarNode('enabled')->defaultValue('%kernel.debug%')->end() 66 | ->scalarNode('template')->defaultValue('@SonataBlock/Profiler/block.html.twig')->end() 67 | ->end() 68 | ->end() 69 | 70 | ->arrayNode('default_contexts') 71 | ->prototype('scalar')->end() 72 | ->end() 73 | 74 | ->scalarNode('context_manager')->defaultValue('sonata.block.context_manager.default')->end() 75 | // NEXT_MAJOR: remove this on 6.x 76 | ->booleanNode('http_cache') 77 | ->defaultFalse() 78 | ->setDeprecated( 79 | 'sonata-project/block-bundle', 80 | '5.0', 81 | 'The "http_cache" option is deprecated and not doing anything anymore since sonata-project/block-bundle 5.0. It will be removed in 6.0.', 82 | ) 83 | ->end() 84 | ->arrayNode('templates') 85 | ->addDefaultsIfNotSet() 86 | ->children() 87 | ->scalarNode('block_base')->defaultNull()->end() 88 | ->scalarNode('block_container')->defaultNull()->end() 89 | ->end() 90 | ->end() 91 | 92 | ->arrayNode('container') 93 | ->info('block container configuration') 94 | ->addDefaultsIfNotSet() 95 | ->fixXmlConfig('type', 'types') 96 | ->fixXmlConfig('template', 'templates') 97 | ->children() 98 | ->arrayNode('types') 99 | ->info('container service ids') 100 | ->isRequired() 101 | // add default value to well know users of BlockBundle 102 | ->defaultValue(['sonata.block.service.container', 'sonata.page.block.container', 'sonata.dashboard.block.container', 'cmf.block.container', 'cmf.block.slideshow']) 103 | ->prototype('scalar')->end() 104 | ->end() 105 | ->arrayNode('templates') 106 | ->info('container templates') 107 | ->isRequired() 108 | ->defaultValue($this->defaultContainerTemplates) 109 | ->prototype('scalar')->end() 110 | ->end() 111 | 112 | ->end() 113 | ->end() 114 | 115 | ->arrayNode('blocks') 116 | ->info('configuration per block service') 117 | ->useAttributeAsKey('id') 118 | ->prototype('array') 119 | ->fixXmlConfig('context') 120 | ->fixXmlConfig('template') 121 | ->fixXmlConfig('setting') 122 | ->children() 123 | ->arrayNode('contexts') 124 | ->prototype('scalar')->end() 125 | ->end() 126 | ->arrayNode('templates') 127 | ->prototype('array') 128 | ->children() 129 | ->scalarNode('name')->cannotBeEmpty()->end() 130 | ->scalarNode('template')->cannotBeEmpty()->end() 131 | ->end() 132 | ->end() 133 | ->end() 134 | ->arrayNode('settings') 135 | ->info('default settings') 136 | ->useAttributeAsKey('id') 137 | ->prototype('scalar')->end() 138 | ->end() 139 | ->arrayNode('exception') 140 | ->children() 141 | ->scalarNode('filter')->defaultNull()->end() 142 | ->scalarNode('renderer')->defaultNull()->end() 143 | ->end() 144 | ->end() 145 | ->end() 146 | ->end() 147 | ->end() 148 | 149 | ->arrayNode('blocks_by_class') 150 | ->info('configuration per block class') 151 | ->useAttributeAsKey('class') 152 | ->prototype('array') 153 | ->fixXmlConfig('setting') 154 | ->children() 155 | ->arrayNode('settings') 156 | ->info('default settings') 157 | ->useAttributeAsKey('id') 158 | ->prototype('scalar')->end() 159 | ->end() 160 | ->end() 161 | ->end() 162 | ->end() 163 | 164 | ->arrayNode('exception') 165 | ->addDefaultsIfNotSet() 166 | ->fixXmlConfig('filter') 167 | ->fixXmlConfig('renderer') 168 | ->children() 169 | ->arrayNode('default') 170 | ->addDefaultsIfNotSet() 171 | ->children() 172 | ->scalarNode('filter')->defaultValue('debug_only')->end() 173 | ->scalarNode('renderer')->defaultValue('throw')->end() 174 | ->end() 175 | ->end() 176 | 177 | ->arrayNode('filters') 178 | ->useAttributeAsKey('id') 179 | ->prototype('scalar')->end() 180 | ->defaultValue([ 181 | 'debug_only' => 'sonata.block.exception.filter.debug_only', 182 | 'ignore_block_exception' => 'sonata.block.exception.filter.ignore_block_exception', 183 | 'keep_all' => 'sonata.block.exception.filter.keep_all', 184 | 'keep_none' => 'sonata.block.exception.filter.keep_none', 185 | ]) 186 | ->end() 187 | ->arrayNode('renderers') 188 | ->useAttributeAsKey('id') 189 | ->prototype('scalar')->end() 190 | ->defaultValue([ 191 | 'inline' => 'sonata.block.exception.renderer.inline', 192 | 'inline_debug' => 'sonata.block.exception.renderer.inline_debug', 193 | 'throw' => 'sonata.block.exception.renderer.throw', 194 | ]) 195 | ->end() 196 | ->end() 197 | ->end() 198 | ->end(); 199 | 200 | return $treeBuilder; 201 | } 202 | 203 | /** 204 | * @param array $config 205 | * 206 | * @return Configuration 207 | */ 208 | public function getConfiguration(array $config, ContainerBuilder $container) 209 | { 210 | return new self([]); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/DependencyInjection/SonataBlockExtension.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\DependencyInjection; 15 | 16 | use Sonata\BlockBundle\Profiler\DataCollector\BlockDataCollector; 17 | use Symfony\Component\Config\Definition\ConfigurationInterface; 18 | use Symfony\Component\Config\Definition\Processor; 19 | use Symfony\Component\Config\FileLocator; 20 | use Symfony\Component\DependencyInjection\ContainerBuilder; 21 | use Symfony\Component\DependencyInjection\Definition; 22 | use Symfony\Component\DependencyInjection\Extension\Extension; 23 | use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; 24 | use Symfony\Component\DependencyInjection\Reference; 25 | 26 | /** 27 | * @author Thomas Rabaix 28 | */ 29 | final class SonataBlockExtension extends Extension 30 | { 31 | /** 32 | * @param mixed[] $config 33 | */ 34 | public function getConfiguration(array $config, ContainerBuilder $container): ConfigurationInterface 35 | { 36 | /** @var array $bundles */ 37 | $bundles = $container->getParameter('kernel.bundles'); 38 | 39 | $defaultTemplates = []; 40 | if (isset($bundles['SonataPageBundle'])) { 41 | $defaultTemplates['SonataPageBundle default template'] = '@SonataPage/Block/block_container.html.twig'; 42 | } else { 43 | $defaultTemplates['SonataBlockBundle default template'] = '@SonataBlock/Block/block_container.html.twig'; 44 | } 45 | 46 | if (isset($bundles['SonataSeoBundle'])) { 47 | $defaultTemplates['SonataSeoBundle (to contain social buttons)'] = '@SonataSeo/Block/block_social_container.html.twig'; 48 | } 49 | 50 | return new Configuration($defaultTemplates); 51 | } 52 | 53 | public function load(array $configs, ContainerBuilder $container): void 54 | { 55 | /** @var array $bundles */ 56 | $bundles = $container->getParameter('kernel.bundles'); 57 | 58 | $processor = new Processor(); 59 | $configuration = $this->getConfiguration($configs, $container); 60 | $config = $processor->processConfiguration($configuration, $configs); 61 | 62 | $loader = new PhpFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); 63 | $loader->load('block.php'); 64 | 65 | if (isset($bundles['KnpMenuBundle'])) { 66 | $loader->load('knp_block.php'); 67 | } 68 | 69 | $loader->load('form.php'); 70 | $loader->load('core.php'); 71 | $loader->load('exception.php'); 72 | $loader->load('commands.php'); 73 | 74 | $this->configureBlockContainers($container, $config); 75 | $this->configureContext($container, $config); 76 | $this->configureLoaderChain($container, $config); 77 | $this->configureForm($container, $config); 78 | $this->configureProfiler($container, $config); 79 | $this->configureException($container, $config); 80 | 81 | if (null === $config['templates']['block_base']) { 82 | if (isset($bundles['SonataPageBundle'])) { 83 | $config['templates']['block_base'] = '@SonataPage/Block/block_base.html.twig'; 84 | $config['templates']['block_container'] = '@SonataPage/Block/block_container.html.twig'; 85 | } else { 86 | $config['templates']['block_base'] = '@SonataBlock/Block/block_base.html.twig'; 87 | $config['templates']['block_container'] = '@SonataBlock/Block/block_container.html.twig'; 88 | } 89 | } 90 | 91 | $container->getDefinition('sonata.block.twig.global')->replaceArgument(0, $config['templates']); 92 | } 93 | 94 | public function getNamespace(): string 95 | { 96 | return 'http://sonata-project.com/schema/dic/block'; 97 | } 98 | 99 | /** 100 | * @param array $config 101 | */ 102 | private function configureBlockContainers(ContainerBuilder $container, array $config): void 103 | { 104 | $container->setParameter('sonata.block.container.types', $config['container']['types']); 105 | 106 | $container->getDefinition('sonata.block.form.type.container_template')->replaceArgument(0, $config['container']['templates']); 107 | } 108 | 109 | /** 110 | * @param array $config 111 | */ 112 | private function configureContext(ContainerBuilder $container, array $config): void 113 | { 114 | $container->setParameter($this->getAlias().'.blocks', $config['blocks']); 115 | $container->setParameter($this->getAlias().'.blocks_by_class', $config['blocks_by_class']); 116 | 117 | $container->setAlias('sonata.block.context_manager', $config['context_manager']); 118 | } 119 | 120 | /** 121 | * @param array $config 122 | */ 123 | private function configureLoaderChain(ContainerBuilder $container, array $config): void 124 | { 125 | $types = []; 126 | foreach ($config['blocks'] as $service => $settings) { 127 | $types[] = $service; 128 | } 129 | 130 | $container->setParameter('sonata_blocks.block_types', $types); 131 | } 132 | 133 | /** 134 | * @param array $config 135 | */ 136 | private function configureForm(ContainerBuilder $container, array $config): void 137 | { 138 | $defaults = $config['default_contexts']; 139 | 140 | $contexts = []; 141 | 142 | foreach ($config['blocks'] as $service => $settings) { 143 | if (0 === \count($settings['contexts'])) { 144 | $settings['contexts'] = $defaults; 145 | } 146 | 147 | foreach ($settings['contexts'] as $context) { 148 | if (!isset($contexts[$context])) { 149 | $contexts[$context] = []; 150 | } 151 | 152 | $contexts[$context][] = $service; 153 | } 154 | } 155 | 156 | $container->setParameter('sonata_blocks.default_contexts', $defaults); 157 | } 158 | 159 | /** 160 | * @param array $config 161 | */ 162 | private function configureProfiler(ContainerBuilder $container, array $config): void 163 | { 164 | $container->setAlias('sonata.block.renderer', 'sonata.block.renderer.default'); 165 | 166 | if (false === $config['profiler']['enabled']) { 167 | return; 168 | } 169 | 170 | // add the block data collector 171 | $definition = new Definition(BlockDataCollector::class); 172 | $definition->setPublic(false); 173 | $definition->addTag('data_collector', ['id' => 'block', 'template' => $config['profiler']['template']]); 174 | $definition->addArgument(new Reference('sonata.block.templating.helper')); 175 | $definition->addArgument($config['container']['types']); 176 | 177 | $container->setDefinition('sonata.block.data_collector', $definition); 178 | } 179 | 180 | /** 181 | * @param array $config 182 | */ 183 | private function configureException(ContainerBuilder $container, array $config): void 184 | { 185 | // retrieve available filters 186 | $filters = []; 187 | foreach ($config['exception']['filters'] as $name => $filter) { 188 | $filters[$name] = $filter; 189 | } 190 | 191 | // retrieve available renderers 192 | $renderers = []; 193 | foreach ($config['exception']['renderers'] as $name => $renderer) { 194 | $renderers[$name] = $renderer; 195 | } 196 | 197 | // retrieve block customization 198 | $blockFilters = []; 199 | $blockRenderers = []; 200 | foreach ($config['blocks'] as $service => $settings) { 201 | if (isset($settings['exception']['filter'])) { 202 | $blockFilters[$service] = $settings['exception']['filter']; 203 | } 204 | if (isset($settings['exception']['renderer'])) { 205 | $blockRenderers[$service] = $settings['exception']['renderer']; 206 | } 207 | } 208 | 209 | $definition = $container->getDefinition('sonata.block.exception.strategy.manager'); 210 | $definition->replaceArgument(1, $filters); 211 | $definition->replaceArgument(2, $renderers); 212 | $definition->replaceArgument(3, $blockFilters); 213 | $definition->replaceArgument(4, $blockRenderers); 214 | 215 | // retrieve default values 216 | $defaultFilter = $config['exception']['default']['filter']; 217 | $defaultRenderer = $config['exception']['default']['renderer']; 218 | $definition->addMethodCall('setDefaultFilter', [$defaultFilter]); 219 | $definition->addMethodCall('setDefaultRenderer', [$defaultRenderer]); 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/Event/BlockEvent.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Event; 15 | 16 | use Sonata\BlockBundle\Model\BlockInterface; 17 | use Symfony\Contracts\EventDispatcher\Event; 18 | 19 | final class BlockEvent extends Event 20 | { 21 | /** 22 | * @var BlockInterface[] 23 | */ 24 | private array $blocks = []; 25 | 26 | /** 27 | * @param array $settings 28 | */ 29 | public function __construct(private array $settings = []) 30 | { 31 | } 32 | 33 | public function addBlock(BlockInterface $block): void 34 | { 35 | $this->blocks[] = $block; 36 | } 37 | 38 | /** 39 | * @return array 40 | */ 41 | public function getSettings(): array 42 | { 43 | return $this->settings; 44 | } 45 | 46 | /** 47 | * @return BlockInterface[] 48 | */ 49 | public function getBlocks(): array 50 | { 51 | return $this->blocks; 52 | } 53 | 54 | public function getSetting(string $name, mixed $default = null): mixed 55 | { 56 | return $this->settings[$name] ?? $default; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Exception/BlockExceptionInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Exception; 15 | 16 | /** 17 | * Interface to implement exception identified as block-specific exceptions. 18 | */ 19 | interface BlockExceptionInterface extends \Throwable 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/Exception/BlockNotFoundException.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Exception; 15 | 16 | use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; 17 | 18 | final class BlockNotFoundException extends NotFoundHttpException 19 | { 20 | } 21 | -------------------------------------------------------------------------------- /src/Exception/BlockServiceNotFoundException.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Exception; 15 | 16 | final class BlockServiceNotFoundException extends \RuntimeException implements BlockExceptionInterface 17 | { 18 | } 19 | -------------------------------------------------------------------------------- /src/Exception/Filter/DebugOnlyFilter.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Exception\Filter; 15 | 16 | use Sonata\BlockBundle\Model\BlockInterface; 17 | 18 | /** 19 | * This filter handles exceptions only when debug mode is enabled. 20 | * 21 | * @author Olivier Paradis 22 | */ 23 | final class DebugOnlyFilter implements FilterInterface 24 | { 25 | public function __construct(private bool $debug) 26 | { 27 | } 28 | 29 | public function handle(\Throwable $exception, BlockInterface $block): bool 30 | { 31 | return $this->debug; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Exception/Filter/FilterInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Exception\Filter; 15 | 16 | use Sonata\BlockBundle\Model\BlockInterface; 17 | 18 | /** 19 | * Interface for the exception filter used in the exception strategy management. 20 | * 21 | * It's purpose is to define which exceptions should be managed and which should simply be ignored. 22 | * 23 | * @author Olivier Paradis 24 | */ 25 | interface FilterInterface 26 | { 27 | /** 28 | * Returns whether or not this filter handles this exception for given block. 29 | * 30 | * @param \Throwable $exception Exception to manage 31 | * @param BlockInterface $block Block that provoked the exception 32 | */ 33 | public function handle(\Throwable $exception, BlockInterface $block): bool; 34 | } 35 | -------------------------------------------------------------------------------- /src/Exception/Filter/IgnoreClassFilter.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Exception\Filter; 15 | 16 | use Sonata\BlockBundle\Model\BlockInterface; 17 | 18 | /** 19 | * This filter ignores exceptions that inherit a given class or interface, or in other words, it will only handle 20 | * exceptions that do not inherit the given class or interface. 21 | * 22 | * @author Olivier Paradis 23 | */ 24 | final class IgnoreClassFilter implements FilterInterface 25 | { 26 | public function __construct(private string $class) 27 | { 28 | } 29 | 30 | public function handle(\Throwable $exception, BlockInterface $block): bool 31 | { 32 | return !$exception instanceof $this->class; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Exception/Filter/KeepAllFilter.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Exception\Filter; 15 | 16 | use Sonata\BlockBundle\Model\BlockInterface; 17 | 18 | /** 19 | * This filter will handle all exceptions. 20 | * 21 | * @author Olivier Paradis 22 | */ 23 | final class KeepAllFilter implements FilterInterface 24 | { 25 | public function handle(\Throwable $exception, BlockInterface $block): bool 26 | { 27 | return true; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Exception/Filter/KeepNoneFilter.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Exception\Filter; 15 | 16 | use Sonata\BlockBundle\Model\BlockInterface; 17 | 18 | /** 19 | * This filters will ignore all exceptions. 20 | * 21 | * @author Olivier Paradis 22 | */ 23 | final class KeepNoneFilter implements FilterInterface 24 | { 25 | public function handle(\Throwable $exception, BlockInterface $block): bool 26 | { 27 | return false; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Exception/Renderer/InlineDebugRenderer.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Exception\Renderer; 15 | 16 | use Sonata\BlockBundle\Model\BlockInterface; 17 | use Symfony\Component\ErrorHandler\Exception\FlattenException; 18 | use Symfony\Component\HttpFoundation\Response; 19 | use Twig\Environment; 20 | 21 | /** 22 | * This renderer uses a template to display an error message at the block position with extensive debug information. 23 | * 24 | * @author Olivier Paradis 25 | */ 26 | final class InlineDebugRenderer implements RendererInterface 27 | { 28 | public function __construct( 29 | private Environment $twig, 30 | private string $template, 31 | private bool $debug, 32 | private bool $forceStyle = true, 33 | ) { 34 | } 35 | 36 | public function render(\Exception $exception, BlockInterface $block, ?Response $response = null): Response 37 | { 38 | $response ??= new Response(); 39 | 40 | // enforce debug mode or ignore silently 41 | if (!$this->debug) { 42 | return $response; 43 | } 44 | 45 | $flattenException = FlattenException::create($exception); 46 | $code = $flattenException->getStatusCode(); 47 | 48 | $parameters = [ 49 | 'exception' => $flattenException, 50 | 'status_code' => $code, 51 | 'status_text' => Response::$statusTexts[$code] ?? '', 52 | 'logger' => false, 53 | 'currentContent' => false, 54 | 'block' => $block, 55 | 'forceStyle' => $this->forceStyle, 56 | ]; 57 | 58 | $content = $this->twig->render($this->template, $parameters); 59 | $response->setContent($content); 60 | 61 | return $response; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Exception/Renderer/InlineRenderer.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Exception\Renderer; 15 | 16 | use Sonata\BlockBundle\Model\BlockInterface; 17 | use Symfony\Component\HttpFoundation\Response; 18 | use Twig\Environment; 19 | 20 | /** 21 | * This renderer uses a template to display an error message at the block position. 22 | * 23 | * @author Olivier Paradis 24 | */ 25 | final class InlineRenderer implements RendererInterface 26 | { 27 | public function __construct( 28 | private Environment $twig, 29 | private string $template, 30 | ) { 31 | } 32 | 33 | public function render(\Exception $exception, BlockInterface $block, ?Response $response = null): Response 34 | { 35 | $parameters = [ 36 | 'exception' => $exception, 37 | 'block' => $block, 38 | ]; 39 | 40 | $content = $this->twig->render($this->template, $parameters); 41 | 42 | $response ??= new Response(); 43 | $response->setContent($content); 44 | 45 | return $response; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Exception/Renderer/MonkeyThrowRenderer.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Exception\Renderer; 15 | 16 | use Sonata\BlockBundle\Model\BlockInterface; 17 | use Symfony\Component\HttpFoundation\Response; 18 | 19 | /** 20 | * This renderer re-throws the exception and lets the framework handle the exception. 21 | * 22 | * @author Olivier Paradis 23 | */ 24 | final class MonkeyThrowRenderer implements RendererInterface 25 | { 26 | public function render(\Exception $exception, BlockInterface $block, ?Response $response = null): Response 27 | { 28 | throw $exception; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Exception/Renderer/RendererInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Exception\Renderer; 15 | 16 | use Sonata\BlockBundle\Model\BlockInterface; 17 | use Symfony\Component\HttpFoundation\Response; 18 | 19 | /** 20 | * Interface for exception renderer. 21 | * 22 | * @author Olivier Paradis 23 | */ 24 | interface RendererInterface 25 | { 26 | /** 27 | * Renders an exception into an HTTP response. 28 | * 29 | * @param \Exception $exception Exception provoked 30 | * @param BlockInterface $block Block that provoked the exception 31 | * @param Response $response Response to alter 32 | */ 33 | public function render(\Exception $exception, BlockInterface $block, ?Response $response = null): Response; 34 | } 35 | -------------------------------------------------------------------------------- /src/Exception/Strategy/StrategyManager.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Exception\Strategy; 15 | 16 | use Sonata\BlockBundle\Exception\Filter\FilterInterface; 17 | use Sonata\BlockBundle\Exception\Renderer\RendererInterface; 18 | use Sonata\BlockBundle\Model\BlockInterface; 19 | use Symfony\Component\DependencyInjection\ContainerInterface; 20 | use Symfony\Component\HttpFoundation\Response; 21 | 22 | /** 23 | * The strategy manager handles exceptions thrown by a block. It uses an exception filter to identify which exceptions 24 | * it should handle or ignore. It then uses an exception renderer to "somehow" display the exception. 25 | * 26 | * @author Olivier Paradis 27 | */ 28 | final class StrategyManager implements StrategyManagerInterface 29 | { 30 | private ?string $defaultFilter = null; 31 | 32 | private ?string $defaultRenderer = null; 33 | 34 | /** 35 | * @psalm-suppress ContainerDependency 36 | * 37 | * @param ContainerInterface $container Dependency injection container 38 | * @param array $filters Filter definitions 39 | * @param array $renderers Renderer definitions 40 | * @param array $blockFilters Filter names for each block 41 | * @param array $blockRenderers Renderer names for each block 42 | */ 43 | public function __construct( 44 | private ContainerInterface $container, 45 | private array $filters, 46 | private array $renderers, 47 | private array $blockFilters, 48 | private array $blockRenderers, 49 | ) { 50 | } 51 | 52 | /** 53 | * Sets the default filter name. 54 | * 55 | * @throws \InvalidArgumentException 56 | */ 57 | public function setDefaultFilter(string $name): void 58 | { 59 | if (!\array_key_exists($name, $this->filters)) { 60 | throw new \InvalidArgumentException(\sprintf('Cannot set default exception filter "%s". It does not exist.', $name)); 61 | } 62 | 63 | $this->defaultFilter = $name; 64 | } 65 | 66 | /** 67 | * Sets the default renderer name. 68 | * 69 | * @throws \InvalidArgumentException 70 | */ 71 | public function setDefaultRenderer(string $name): void 72 | { 73 | if (!\array_key_exists($name, $this->renderers)) { 74 | throw new \InvalidArgumentException(\sprintf('Cannot set default exception renderer "%s". It does not exist.', $name)); 75 | } 76 | 77 | $this->defaultRenderer = $name; 78 | } 79 | 80 | public function handleException(\Throwable $exception, BlockInterface $block, ?Response $response = null): Response 81 | { 82 | $response ??= new Response(); 83 | $response->setPrivate(); 84 | 85 | $filter = $this->getBlockFilter($block); 86 | if ($filter->handle($exception, $block)) { 87 | $renderer = $this->getBlockRenderer($block); 88 | 89 | // Convert throwable to exception 90 | if (!$exception instanceof \Exception) { 91 | /** @psalm-suppress PossiblyInvalidArgument */ 92 | $exception = new \Exception($exception->getMessage(), $exception->getCode(), $exception); 93 | } 94 | 95 | $response = $renderer->render($exception, $block, $response); 96 | } 97 | // render empty block template? 98 | 99 | return $response; 100 | } 101 | 102 | /** 103 | * Returns the exception renderer for given block. 104 | * 105 | * @throws \RuntimeException|\InvalidArgumentException 106 | */ 107 | public function getBlockRenderer(BlockInterface $block): RendererInterface 108 | { 109 | $type = $block->getType(); 110 | $name = $this->blockRenderers[$type] ?? $this->defaultRenderer; 111 | if (null === $name) { 112 | throw new \RuntimeException('No default renderer was set.'); 113 | } 114 | 115 | if (!isset($this->renderers[$name])) { 116 | throw new \RuntimeException('The renderer "%s" does not exist.'); 117 | } 118 | 119 | $service = $this->container->get($this->renderers[$name]); 120 | 121 | if (!$service instanceof RendererInterface) { 122 | throw new \InvalidArgumentException(\sprintf('The service "%s" is not an exception renderer.', $name)); 123 | } 124 | 125 | return $service; 126 | } 127 | 128 | /** 129 | * Returns the exception filter for given block. 130 | * 131 | * @throws \RuntimeException|\InvalidArgumentException 132 | */ 133 | public function getBlockFilter(BlockInterface $block): FilterInterface 134 | { 135 | $type = $block->getType(); 136 | $name = $this->blockFilters[$type] ?? $this->defaultFilter; 137 | if (null === $name) { 138 | throw new \RuntimeException('No default filter was set.'); 139 | } 140 | 141 | if (!isset($this->filters[$name])) { 142 | throw new \RuntimeException('The filter "%s" does not exist.'); 143 | } 144 | 145 | $service = $this->container->get($this->filters[$name]); 146 | 147 | if (!$service instanceof FilterInterface) { 148 | throw new \InvalidArgumentException(\sprintf('The service "%s" is not an exception filter.', $name)); 149 | } 150 | 151 | return $service; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/Exception/Strategy/StrategyManagerInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Exception\Strategy; 15 | 16 | use Sonata\BlockBundle\Model\BlockInterface; 17 | use Symfony\Component\HttpFoundation\Response; 18 | 19 | /** 20 | * Interface for exception strategy management. 21 | * 22 | * @author Olivier Paradis 23 | */ 24 | interface StrategyManagerInterface 25 | { 26 | /** 27 | * Handles an exception for a given block. 28 | * 29 | * @param \Throwable $exception Exception to handle 30 | * @param BlockInterface $block Block that provoked the exception 31 | * @param Response $response Response provided to the block service 32 | */ 33 | public function handleException(\Throwable $exception, BlockInterface $block, ?Response $response = null): Response; 34 | } 35 | -------------------------------------------------------------------------------- /src/Form/Mapper/FormMapper.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Form\Mapper; 15 | 16 | use Symfony\Component\Form\FormBuilderInterface; 17 | use Symfony\Component\Form\FormTypeInterface; 18 | 19 | /** 20 | * @author Christian Gripp 21 | */ 22 | interface FormMapper 23 | { 24 | /** 25 | * @param string[] $keys 26 | */ 27 | public function reorder(array $keys): static; 28 | 29 | /** 30 | * @param class-string|null $type 31 | * @param array $options 32 | */ 33 | public function add(string $name, ?string $type = null, array $options = []): static; 34 | 35 | public function remove(string $key): static; 36 | 37 | public function has(string $key): bool; 38 | 39 | public function get(string $key): FormBuilderInterface; 40 | } 41 | -------------------------------------------------------------------------------- /src/Form/Type/ContainerTemplateType.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Form\Type; 15 | 16 | use Symfony\Component\Form\AbstractType; 17 | use Symfony\Component\Form\Extension\Core\Type\ChoiceType; 18 | use Symfony\Component\OptionsResolver\OptionsResolver; 19 | 20 | /** 21 | * @author Hugo Briand 22 | * 23 | * @psalm-suppress MissingTemplateParam https://github.com/phpstan/phpstan-symfony/issues/320 24 | */ 25 | final class ContainerTemplateType extends AbstractType 26 | { 27 | /** 28 | * @param array $templateChoices 29 | */ 30 | public function __construct(private array $templateChoices) 31 | { 32 | } 33 | 34 | public function getBlockPrefix(): string 35 | { 36 | return 'sonata_type_container_template_choice'; 37 | } 38 | 39 | public function getParent(): string 40 | { 41 | return ChoiceType::class; 42 | } 43 | 44 | public function configureOptions(OptionsResolver $resolver): void 45 | { 46 | $resolver->setDefaults([ 47 | 'choices' => $this->templateChoices, 48 | ]); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Form/Type/ServiceListType.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Form\Type; 15 | 16 | use Sonata\BlockBundle\Block\BlockServiceManagerInterface; 17 | use Sonata\BlockBundle\Block\Service\EditableBlockService; 18 | use Symfony\Component\Form\AbstractType; 19 | use Symfony\Component\Form\Extension\Core\Type\ChoiceType; 20 | use Symfony\Component\OptionsResolver\Options; 21 | use Symfony\Component\OptionsResolver\OptionsResolver; 22 | 23 | /** 24 | * @psalm-suppress MissingTemplateParam https://github.com/phpstan/phpstan-symfony/issues/320 25 | */ 26 | final class ServiceListType extends AbstractType 27 | { 28 | public function __construct(private BlockServiceManagerInterface $manager) 29 | { 30 | } 31 | 32 | public function getBlockPrefix(): string 33 | { 34 | return 'sonata_block_service_choice'; 35 | } 36 | 37 | public function getParent(): string 38 | { 39 | return ChoiceType::class; 40 | } 41 | 42 | public function configureOptions(OptionsResolver $resolver): void 43 | { 44 | $manager = $this->manager; 45 | 46 | $resolver->setRequired([ 47 | 'context', 48 | ]); 49 | 50 | $resolver->setDefaults([ 51 | 'multiple' => false, 52 | 'expanded' => false, 53 | 'choices' => static function (Options $options) use ($manager): array { 54 | $types = []; 55 | foreach ($manager->getServicesByContext($options['context'], $options['include_containers']) as $code => $service) { 56 | if ($service instanceof EditableBlockService) { 57 | $types[\sprintf('%s - %s', $service->getMetadata()->getTitle(), $code)] = $code; 58 | } else { 59 | $types[\sprintf('%s', $code)] = $code; 60 | } 61 | } 62 | 63 | return $types; 64 | }, 65 | 'preferred_choices' => [], 66 | 'empty_data' => static function (Options $options) { 67 | $multiple = $options['multiple'] ?? false; 68 | $expanded = $options['expanded'] ?? false; 69 | 70 | return true === $multiple || true === $expanded ? [] : ''; 71 | }, 72 | 'empty_value' => static function (Options $options, mixed $previousValue): ?string { 73 | $multiple = $options['multiple'] ?? false; 74 | $expanded = $options['expanded'] ?? false; 75 | 76 | return true === $multiple || true === $expanded || !isset($previousValue) ? null : ''; 77 | }, 78 | 'error_bubbling' => false, 79 | 'include_containers' => false, 80 | ]); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Menu/MenuRegistry.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Menu; 15 | 16 | /** 17 | * @author Christian Gripp 18 | */ 19 | final class MenuRegistry implements MenuRegistryInterface 20 | { 21 | /** 22 | * @var string[] 23 | */ 24 | private array $names = []; 25 | 26 | public function add(string $name): void 27 | { 28 | $this->names[$name] = $name; 29 | } 30 | 31 | public function getAliasNames(): array 32 | { 33 | return $this->names; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Menu/MenuRegistryInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Menu; 15 | 16 | /** 17 | * @author Christian Gripp 18 | */ 19 | interface MenuRegistryInterface 20 | { 21 | /** 22 | * Adds a new menu. 23 | */ 24 | public function add(string $name): void; 25 | 26 | /** 27 | * Returns all alias names. 28 | * 29 | * @return string[] 30 | */ 31 | public function getAliasNames(): array; 32 | } 33 | -------------------------------------------------------------------------------- /src/Meta/Metadata.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Meta; 15 | 16 | /** 17 | * @author Hugo Briand 18 | */ 19 | final class Metadata implements MetadataInterface 20 | { 21 | /** 22 | * @param array $options 23 | */ 24 | public function __construct( 25 | private string $title, 26 | private ?string $description = null, 27 | private ?string $image = null, 28 | private ?string $domain = null, 29 | private array $options = [], 30 | ) { 31 | } 32 | 33 | public function getTitle(): string 34 | { 35 | return $this->title; 36 | } 37 | 38 | public function getDescription(): ?string 39 | { 40 | return $this->description; 41 | } 42 | 43 | public function getImage(): ?string 44 | { 45 | return $this->image; 46 | } 47 | 48 | public function getDomain(): ?string 49 | { 50 | return $this->domain; 51 | } 52 | 53 | public function getOptions(): array 54 | { 55 | return $this->options; 56 | } 57 | 58 | public function getOption(string $name, mixed $default = null): mixed 59 | { 60 | return \array_key_exists($name, $this->options) ? $this->options[$name] : $default; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Meta/MetadataInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Meta; 15 | 16 | /** 17 | * @author Hugo Briand 18 | */ 19 | interface MetadataInterface 20 | { 21 | public function getTitle(): string; 22 | 23 | public function getDescription(): ?string; 24 | 25 | public function getImage(): ?string; 26 | 27 | public function getDomain(): ?string; 28 | 29 | /** 30 | * @return array 31 | */ 32 | public function getOptions(): array; 33 | 34 | public function getOption(string $name, mixed $default = null): mixed; 35 | } 36 | -------------------------------------------------------------------------------- /src/Model/BaseBlock.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Model; 15 | 16 | use Doctrine\Common\Collections\ArrayCollection; 17 | use Doctrine\Common\Collections\Collection; 18 | 19 | /** 20 | * Base abstract Block class that provides a default implementation of the block interface. 21 | */ 22 | abstract class BaseBlock implements BlockInterface, \Stringable 23 | { 24 | /** 25 | * @var string|null 26 | */ 27 | protected $name; 28 | 29 | /** 30 | * @var array 31 | */ 32 | protected $settings; 33 | 34 | /** 35 | * @var bool 36 | */ 37 | protected $enabled; 38 | 39 | /** 40 | * @var int|null 41 | */ 42 | protected $position; 43 | 44 | /** 45 | * @var BlockInterface|null 46 | */ 47 | protected $parent; 48 | 49 | /** 50 | * @var Collection 51 | */ 52 | protected $children; 53 | 54 | /** 55 | * @var \DateTime|null 56 | */ 57 | protected $createdAt; 58 | 59 | /** 60 | * @var \DateTime|null 61 | */ 62 | protected $updatedAt; 63 | 64 | /** 65 | * @var string|null 66 | */ 67 | protected $type; 68 | 69 | public function __construct() 70 | { 71 | $this->settings = []; 72 | $this->enabled = false; 73 | $this->children = new ArrayCollection(); 74 | } 75 | 76 | public function __toString(): string 77 | { 78 | return \sprintf('%s ~ #%s', $this->getName() ?? '', $this->getId() ?? ''); 79 | } 80 | 81 | public function setName(string $name): void 82 | { 83 | $this->name = $name; 84 | } 85 | 86 | public function getName(): ?string 87 | { 88 | return $this->name; 89 | } 90 | 91 | public function setType(string $type): void 92 | { 93 | $this->type = $type; 94 | } 95 | 96 | public function getType(): ?string 97 | { 98 | return $this->type; 99 | } 100 | 101 | public function setSettings(array $settings = []): void 102 | { 103 | $this->settings = $settings; 104 | } 105 | 106 | public function getSettings(): array 107 | { 108 | return $this->settings; 109 | } 110 | 111 | public function setSetting(string $name, mixed $value): void 112 | { 113 | $this->settings[$name] = $value; 114 | } 115 | 116 | public function getSetting(string $name, mixed $default = null): mixed 117 | { 118 | return $this->settings[$name] ?? $default; 119 | } 120 | 121 | public function setEnabled(bool $enabled): void 122 | { 123 | $this->enabled = $enabled; 124 | } 125 | 126 | public function getEnabled(): bool 127 | { 128 | return $this->enabled; 129 | } 130 | 131 | public function setPosition(int $position): void 132 | { 133 | $this->position = $position; 134 | } 135 | 136 | public function getPosition(): ?int 137 | { 138 | return $this->position; 139 | } 140 | 141 | public function setCreatedAt(?\DateTime $createdAt = null): void 142 | { 143 | $this->createdAt = $createdAt; 144 | } 145 | 146 | public function getCreatedAt(): ?\DateTime 147 | { 148 | return $this->createdAt; 149 | } 150 | 151 | public function setUpdatedAt(?\DateTime $updatedAt = null): void 152 | { 153 | $this->updatedAt = $updatedAt; 154 | } 155 | 156 | public function getUpdatedAt(): ?\DateTime 157 | { 158 | return $this->updatedAt; 159 | } 160 | 161 | public function addChild(BlockInterface $child): void 162 | { 163 | $this->children[] = $child; 164 | 165 | $child->setParent($this); 166 | } 167 | 168 | public function removeChild(BlockInterface $child): void 169 | { 170 | if ($this->children->contains($child)) { 171 | $this->children->removeElement($child); 172 | } 173 | } 174 | 175 | public function getChildren(): Collection 176 | { 177 | return $this->children; 178 | } 179 | 180 | public function setParent(?BlockInterface $parent = null): void 181 | { 182 | $this->parent = $parent; 183 | } 184 | 185 | public function getParent(): ?BlockInterface 186 | { 187 | return $this->parent; 188 | } 189 | 190 | public function hasParent(): bool 191 | { 192 | return $this->getParent() instanceof self; 193 | } 194 | 195 | public function hasChild(): bool 196 | { 197 | return \count($this->children) > 0; 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /src/Model/Block.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Model; 15 | 16 | /** 17 | * Block model with concrete implementation of BlockInterface. 18 | */ 19 | class Block extends BaseBlock 20 | { 21 | /** 22 | * @var string|int|null 23 | */ 24 | protected $id; 25 | 26 | public function setId($id): void 27 | { 28 | $this->id = $id; 29 | } 30 | 31 | public function getId() 32 | { 33 | return $this->id; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Model/BlockInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Model; 15 | 16 | use Doctrine\Common\Collections\Collection; 17 | 18 | interface BlockInterface 19 | { 20 | /** 21 | * @param string|int $id 22 | */ 23 | public function setId($id): void; 24 | 25 | /** 26 | * @return string|int|null 27 | */ 28 | public function getId(); 29 | 30 | public function setName(string $name): void; 31 | 32 | public function getName(): ?string; 33 | 34 | public function setType(string $type): void; 35 | 36 | public function getType(): ?string; 37 | 38 | public function setEnabled(bool $enabled): void; 39 | 40 | public function getEnabled(): bool; 41 | 42 | public function setPosition(int $position): void; 43 | 44 | public function getPosition(): ?int; 45 | 46 | public function setCreatedAt(?\DateTime $createdAt = null): void; 47 | 48 | public function getCreatedAt(): ?\DateTime; 49 | 50 | public function setUpdatedAt(?\DateTime $updatedAt = null): void; 51 | 52 | public function getUpdatedAt(): ?\DateTime; 53 | 54 | /** 55 | * @param array $settings An array of key/value 56 | */ 57 | public function setSettings(array $settings = []): void; 58 | 59 | /** 60 | * @return array 61 | */ 62 | public function getSettings(): array; 63 | 64 | public function setSetting(string $name, mixed $value): void; 65 | 66 | public function getSetting(string $name, mixed $default = null): mixed; 67 | 68 | public function addChild(self $child): void; 69 | 70 | public function removeChild(self $child): void; 71 | 72 | /** 73 | * @return Collection 74 | */ 75 | public function getChildren(): Collection; 76 | 77 | public function hasChild(): bool; 78 | 79 | public function setParent(?self $parent = null): void; 80 | 81 | public function getParent(): ?self; 82 | 83 | public function hasParent(): bool; 84 | } 85 | -------------------------------------------------------------------------------- /src/Model/EmptyBlock.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Model; 15 | 16 | /** 17 | * EmptyBlock model to be used to return an empty result if a block is not found or not valid. 18 | */ 19 | final class EmptyBlock extends Block 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/Profiler/DataCollector/BlockDataCollector.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Profiler\DataCollector; 15 | 16 | use Sonata\BlockBundle\Templating\Helper\BlockHelper; 17 | use Symfony\Component\HttpFoundation\Request; 18 | use Symfony\Component\HttpFoundation\Response; 19 | use Symfony\Component\HttpKernel\DataCollector\DataCollector; 20 | 21 | /** 22 | * Block data collector for the symfony web profiling. 23 | * 24 | * @author Olivier Paradis 25 | */ 26 | final class BlockDataCollector extends DataCollector 27 | { 28 | /** 29 | * @param BlockHelper $blocksHelper Block renderer 30 | * @param string[] $containerTypes array of container types 31 | */ 32 | public function __construct( 33 | private BlockHelper $blocksHelper, 34 | private array $containerTypes, 35 | ) { 36 | $this->reset(); 37 | } 38 | 39 | public function collect(Request $request, Response $response, ?\Throwable $exception = null): void 40 | { 41 | $this->data['blocks'] = $this->blocksHelper->getTraces(); 42 | 43 | // split into containers & real blocks 44 | foreach ($this->data['blocks'] as $id => $block) { 45 | if (!\is_array($block)) { 46 | return; // something went wrong while collecting information 47 | } 48 | 49 | if ('_events' === $id) { 50 | foreach ($block as $uniqid => $event) { 51 | $this->data['events'][$uniqid] = $event; 52 | } 53 | 54 | continue; 55 | } 56 | 57 | if (\in_array($block['type'], $this->containerTypes, true)) { 58 | $this->data['containers'][$id] = $block; 59 | } else { 60 | $this->data['realBlocks'][$id] = $block; 61 | } 62 | } 63 | } 64 | 65 | /** 66 | * Returns the number of block used. 67 | */ 68 | public function getTotalBlock(): int 69 | { 70 | return \count($this->data['realBlocks']) + \count($this->data['containers']); 71 | } 72 | 73 | /** 74 | * Return the events used on the current page. 75 | * 76 | * @return array 77 | */ 78 | public function getEvents(): array 79 | { 80 | return $this->data['events']; 81 | } 82 | 83 | /** 84 | * Returns the block rendering history. 85 | * 86 | * @return array 87 | */ 88 | public function getBlocks(): array 89 | { 90 | return $this->data['blocks']; 91 | } 92 | 93 | /** 94 | * Returns the container blocks. 95 | * 96 | * @return array 97 | */ 98 | public function getContainers(): array 99 | { 100 | return $this->data['containers']; 101 | } 102 | 103 | /** 104 | * Returns the real blocks (non-container). 105 | * 106 | * @return array 107 | */ 108 | public function getRealBlocks(): array 109 | { 110 | return $this->data['realBlocks']; 111 | } 112 | 113 | public function getName(): string 114 | { 115 | return 'block'; 116 | } 117 | 118 | public function reset(): void 119 | { 120 | $this->data['blocks'] = []; 121 | $this->data['containers'] = []; 122 | $this->data['realBlocks'] = []; 123 | $this->data['events'] = []; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/Resources/config/block.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Symfony\Component\DependencyInjection\Loader\Configurator; 15 | 16 | use Sonata\BlockBundle\Block\Service\ContainerBlockService; 17 | use Sonata\BlockBundle\Block\Service\EmptyBlockService; 18 | use Sonata\BlockBundle\Block\Service\RssBlockService; 19 | use Sonata\BlockBundle\Block\Service\TemplateBlockService; 20 | use Sonata\BlockBundle\Block\Service\TextBlockService; 21 | 22 | return static function (ContainerConfigurator $containerConfigurator): void { 23 | $services = $containerConfigurator->services(); 24 | 25 | $services->set('sonata.block.service.container', ContainerBlockService::class) 26 | ->tag('sonata.block') 27 | ->args([ 28 | service('twig'), 29 | ]); 30 | 31 | $services->set('sonata.block.service.empty', EmptyBlockService::class) 32 | ->tag('sonata.block') 33 | ->args([ 34 | service('twig'), 35 | ]); 36 | 37 | $services->set('sonata.block.service.text', TextBlockService::class) 38 | ->tag('sonata.block') 39 | ->args([ 40 | service('twig'), 41 | ]); 42 | 43 | $services->set('sonata.block.service.rss', RssBlockService::class) 44 | ->tag('sonata.block') 45 | ->args([ 46 | service('twig'), 47 | ]); 48 | 49 | $services->set('sonata.block.service.template', TemplateBlockService::class) 50 | ->tag('sonata.block') 51 | ->args([ 52 | service('twig'), 53 | ]); 54 | }; 55 | -------------------------------------------------------------------------------- /src/Resources/config/commands.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Symfony\Component\DependencyInjection\Loader\Configurator; 15 | 16 | use Sonata\BlockBundle\Command\DebugBlocksCommand; 17 | 18 | return static function (ContainerConfigurator $containerConfigurator): void { 19 | $services = $containerConfigurator->services(); 20 | 21 | $services->set('sonata.block.command.debug_blocks', DebugBlocksCommand::class) 22 | ->tag('console.command') 23 | ->args([ 24 | service('sonata.block.manager'), 25 | ]); 26 | }; 27 | -------------------------------------------------------------------------------- /src/Resources/config/core.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Symfony\Component\DependencyInjection\Loader\Configurator; 15 | 16 | use Sonata\BlockBundle\Block\BlockContextManager; 17 | use Sonata\BlockBundle\Block\BlockLoaderChain; 18 | use Sonata\BlockBundle\Block\BlockRenderer; 19 | use Sonata\BlockBundle\Block\BlockServiceManager; 20 | use Sonata\BlockBundle\Block\Loader\ServiceLoader; 21 | use Sonata\BlockBundle\Menu\MenuRegistry; 22 | use Sonata\BlockBundle\Templating\Helper\BlockHelper; 23 | use Sonata\BlockBundle\Twig\Extension\BlockExtension; 24 | use Sonata\BlockBundle\Twig\GlobalVariables; 25 | 26 | return static function (ContainerConfigurator $containerConfigurator): void { 27 | $services = $containerConfigurator->services(); 28 | 29 | $services->set('sonata.block.manager', BlockServiceManager::class) 30 | ->public() 31 | ->args([ 32 | abstract_arg('container of block services'), 33 | param('sonata.block.container.types'), 34 | ]); 35 | 36 | $services->set('sonata.block.menu.registry', MenuRegistry::class) 37 | ->public(); 38 | 39 | $services->set('sonata.block.context_manager.default', BlockContextManager::class) 40 | ->public() 41 | ->args([ 42 | service('sonata.block.loader.chain'), 43 | service('sonata.block.manager'), 44 | service('logger')->nullOnInvalid(), 45 | ]); 46 | 47 | $services->set('sonata.block.renderer.default', BlockRenderer::class) 48 | ->public() 49 | ->args([ 50 | service('sonata.block.manager'), 51 | service('sonata.block.exception.strategy.manager'), 52 | service('logger')->nullOnInvalid(), 53 | ]); 54 | 55 | $services->set('sonata.block.twig.extension', BlockExtension::class) 56 | ->tag('twig.extension') 57 | ->args([ 58 | service('sonata.block.templating.helper'), 59 | ]); 60 | 61 | $services->set('sonata.block.templating.helper', BlockHelper::class) 62 | ->tag('twig.runtime') 63 | ->args([ 64 | service('sonata.block.renderer'), 65 | service('sonata.block.context_manager'), 66 | service('event_dispatcher'), 67 | service('debug.stopwatch')->nullOnInvalid(), 68 | ]); 69 | 70 | $services->set('sonata.block.loader.chain', BlockLoaderChain::class) 71 | ->args([ 72 | abstract_arg('loaders array'), 73 | ]); 74 | 75 | $services->set('sonata.block.loader.service', ServiceLoader::class) 76 | ->tag('sonata.block.loader') 77 | ->args([ 78 | abstract_arg('types array'), 79 | ]); 80 | 81 | $services->set('sonata.block.twig.global', GlobalVariables::class) 82 | ->args([ 83 | abstract_arg('templates array'), 84 | ]); 85 | 86 | $services->alias(BlockHelper::class, 'sonata.block.templating.helper'); 87 | }; 88 | -------------------------------------------------------------------------------- /src/Resources/config/exception.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Symfony\Component\DependencyInjection\Loader\Configurator; 15 | 16 | use Sonata\BlockBundle\Exception\BlockExceptionInterface; 17 | use Sonata\BlockBundle\Exception\Filter\DebugOnlyFilter; 18 | use Sonata\BlockBundle\Exception\Filter\IgnoreClassFilter; 19 | use Sonata\BlockBundle\Exception\Filter\KeepAllFilter; 20 | use Sonata\BlockBundle\Exception\Filter\KeepNoneFilter; 21 | use Sonata\BlockBundle\Exception\Renderer\InlineDebugRenderer; 22 | use Sonata\BlockBundle\Exception\Renderer\InlineRenderer; 23 | use Sonata\BlockBundle\Exception\Renderer\MonkeyThrowRenderer; 24 | use Sonata\BlockBundle\Exception\Strategy\StrategyManager; 25 | 26 | return static function (ContainerConfigurator $containerConfigurator): void { 27 | $services = $containerConfigurator->services(); 28 | 29 | $services->set('sonata.block.exception.strategy.manager', StrategyManager::class) 30 | ->args([ 31 | service('service_container'), 32 | abstract_arg('filters array'), 33 | abstract_arg('renderers array'), 34 | abstract_arg('block filters array'), 35 | abstract_arg('block renderers array'), 36 | ]); 37 | 38 | $services->set('sonata.block.exception.filter.keep_none', KeepNoneFilter::class) 39 | ->public(); 40 | 41 | $services->set('sonata.block.exception.filter.keep_all', KeepAllFilter::class) 42 | ->public(); 43 | 44 | $services->set('sonata.block.exception.filter.debug_only', DebugOnlyFilter::class) 45 | ->public() 46 | ->args([ 47 | param('kernel.debug'), 48 | ]); 49 | 50 | $services->set('sonata.block.exception.filter.ignore_block_exception', IgnoreClassFilter::class) 51 | ->public() 52 | ->args([ 53 | BlockExceptionInterface::class, 54 | ]); 55 | 56 | $services->set('sonata.block.exception.renderer.inline', InlineRenderer::class) 57 | ->public() 58 | ->args([ 59 | service('twig'), 60 | '@SonataBlock/Block/block_exception.html.twig', 61 | ]); 62 | 63 | $services->set('sonata.block.exception.renderer.inline_debug', InlineDebugRenderer::class) 64 | ->public() 65 | ->args([ 66 | service('twig'), 67 | '@SonataBlock/Block/block_exception_debug.html.twig', 68 | param('kernel.debug'), 69 | true, 70 | ]); 71 | 72 | $services->set('sonata.block.exception.renderer.throw', MonkeyThrowRenderer::class) 73 | ->public(); 74 | }; 75 | -------------------------------------------------------------------------------- /src/Resources/config/form.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Symfony\Component\DependencyInjection\Loader\Configurator; 15 | 16 | use Sonata\BlockBundle\Form\Type\ContainerTemplateType; 17 | use Sonata\BlockBundle\Form\Type\ServiceListType; 18 | 19 | return static function (ContainerConfigurator $containerConfigurator): void { 20 | $services = $containerConfigurator->services(); 21 | 22 | $services->set('sonata.block.form.type.block', ServiceListType::class) 23 | ->tag('form.type', ['alias' => 'sonata_block_service_choice']) 24 | ->args([ 25 | service('sonata.block.manager'), 26 | ]); 27 | 28 | $services->set('sonata.block.form.type.container_template', ContainerTemplateType::class) 29 | ->tag('form.type', ['alias' => 'sonata_type_container_template_choice']) 30 | ->args([ 31 | abstract_arg('template choices array'), 32 | ]); 33 | }; 34 | -------------------------------------------------------------------------------- /src/Resources/config/knp_block.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Symfony\Component\DependencyInjection\Loader\Configurator; 15 | 16 | use Sonata\BlockBundle\Block\Service\MenuBlockService; 17 | 18 | return static function (ContainerConfigurator $containerConfigurator): void { 19 | $services = $containerConfigurator->services(); 20 | 21 | $services->set('sonata.block.service.menu', MenuBlockService::class) 22 | ->tag('sonata.block') 23 | ->args([ 24 | service('twig'), 25 | service('knp_menu.menu_provider'), 26 | service('sonata.block.menu.registry'), 27 | ]); 28 | }; 29 | -------------------------------------------------------------------------------- /src/Resources/meta/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2010-2013 thomas.rabaix@sonata-project.org 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Resources/translations/SonataBlockBundle.ar.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | sonata.block.service.container 7 | الحاوية 8 | 9 | 10 | sonata.block.service.text 11 | نص عادي 12 | 13 | 14 | sonata.block.service.rss 15 | تغذية RSS 16 | 17 | 18 | sonata.block.service.menu 19 | قائمة 20 | 21 | 22 | sonata.block.service.template 23 | قالب 24 | 25 | 26 | form.label_template 27 | قالب 28 | 29 | 30 | form.label_content 31 | محتوى 32 | 33 | 34 | form.label_url 35 | رابط 36 | 37 | 38 | form.label_title 39 | عنوان 40 | 41 | 42 | form.label_code 43 | نص برمجي 44 | 45 | 46 | form.label_layout 47 | تَخطِيط 48 | 49 | 50 | form.label_class 51 | معرف CSS 52 | 53 | 54 | form.label_cache_policy 55 | سياسة ذاكرة التخزين المؤقت 56 | 57 | 58 | form.label_menu_name 59 | قائمة 60 | 61 | 62 | form.label_safe_labels 63 | تسميات آمنة 64 | 65 | 66 | form.label_current_class 67 | معرف ال CSS الحالي 68 | 69 | 70 | form.label_first_class 71 | أول معرف CSS 72 | 73 | 74 | form.label_last_class 75 | آخر معرف CSS 76 | 77 | 78 | form.label_menu_class 79 | معرف ال CSS للقائمة 80 | 81 | 82 | form.label_children_class 83 | معرف ال CSS للأبناء 84 | 85 | 86 | form.label_menu_template 87 | قالب القائمة 88 | 89 | 90 | form.label_translation_domain 91 | مجال الترجمة 92 | 93 | 94 | form.label_icon 95 | أيقونة 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /src/Resources/translations/SonataBlockBundle.de.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | sonata.block.service.container 7 | Container 8 | 9 | 10 | sonata.block.service.text 11 | Einfacher Text 12 | 13 | 14 | sonata.block.service.rss 15 | RSS Feed 16 | 17 | 18 | sonata.block.service.menu 19 | Menü 20 | 21 | 22 | sonata.block.service.template 23 | Template 24 | 25 | 26 | form.label_template 27 | Template 28 | 29 | 30 | form.label_content 31 | Inhalt 32 | 33 | 34 | form.label_url 35 | URL 36 | 37 | 38 | form.label_title 39 | Titel 40 | 41 | 42 | form.label_code 43 | Code 44 | 45 | 46 | form.label_layout 47 | Layout 48 | 49 | 50 | form.label_class 51 | CSS Klasse 52 | 53 | 54 | form.label_menu_name 55 | Menu 56 | 57 | 58 | form.label_safe_labels 59 | Sichere Bezeichner 60 | 61 | 62 | form.label_current_class 63 | Aktuelle CSS Klasse 64 | 65 | 66 | form.label_first_class 67 | Erste CSS Klasse 68 | 69 | 70 | form.label_last_class 71 | Letzte CSS Klasse 72 | 73 | 74 | form.label_menu_template 75 | Menü Template 76 | 77 | 78 | form.label_translation_domain 79 | Übersetzungsdatei 80 | 81 | 82 | form.label_icon 83 | Icon 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /src/Resources/translations/SonataBlockBundle.en.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | sonata.block.service.container 7 | Container 8 | 9 | 10 | sonata.block.service.text 11 | Simple text 12 | 13 | 14 | sonata.block.service.rss 15 | RSS feed 16 | 17 | 18 | sonata.block.service.menu 19 | Menu 20 | 21 | 22 | sonata.block.service.template 23 | Template 24 | 25 | 26 | form.label_template 27 | Template 28 | 29 | 30 | form.label_content 31 | Content 32 | 33 | 34 | form.label_url 35 | URL 36 | 37 | 38 | form.label_title 39 | Title 40 | 41 | 42 | form.label_code 43 | Code 44 | 45 | 46 | form.label_layout 47 | Layout 48 | 49 | 50 | form.label_class 51 | CSS Class 52 | 53 | 54 | form.label_menu_name 55 | Menu 56 | 57 | 58 | form.label_safe_labels 59 | Safe labels 60 | 61 | 62 | form.label_current_class 63 | Current CSS Class 64 | 65 | 66 | form.label_first_class 67 | First CSS Class 68 | 69 | 70 | form.label_last_class 71 | Last CSS Class 72 | 73 | 74 | form.label_menu_template 75 | Menu Template 76 | 77 | 78 | form.label_translation_domain 79 | Translation domain 80 | 81 | 82 | form.label_icon 83 | Icon 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /src/Resources/translations/SonataBlockBundle.fr.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | sonata.block.service.container 7 | Conteneur 8 | 9 | 10 | sonata.block.service.text 11 | Texte 12 | 13 | 14 | sonata.block.service.rss 15 | Flux RSS 16 | 17 | 18 | sonata.block.service.menu 19 | Menu 20 | 21 | 22 | sonata.block.service.template 23 | Vue Partielle 24 | 25 | 26 | form.label_template 27 | Template 28 | 29 | 30 | form.label_content 31 | Contenu 32 | 33 | 34 | form.label_url 35 | URL 36 | 37 | 38 | form.label_title 39 | Titre 40 | 41 | 42 | form.label_code 43 | Code 44 | 45 | 46 | form.label_layout 47 | Layout 48 | 49 | 50 | form.label_class 51 | Classe CSS 52 | 53 | 54 | form.label_menu_name 55 | Nom du menu 56 | 57 | 58 | form.label_safe_labels 59 | Libellés sûrs 60 | 61 | 62 | form.label_current_class 63 | Classe CSS de l'élément actuel du menu 64 | 65 | 66 | form.label_first_class 67 | Classe CSS du premier élément du menu 68 | 69 | 70 | form.label_last_class 71 | Classe CSS du dernier élément du menu 72 | 73 | 74 | form.label_menu_template 75 | Template du menu 76 | 77 | 78 | form.label_translation_domain 79 | Domaine de traduction 80 | 81 | 82 | form.label_icon 83 | Icône 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /src/Resources/translations/SonataBlockBundle.hu.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | sonata.block.service.container 7 | Konténer 8 | 9 | 10 | sonata.block.service.text 11 | Szöveg 12 | 13 | 14 | sonata.block.service.rss 15 | RSS hírfolyam 16 | 17 | 18 | sonata.block.service.menu 19 | Menü 20 | 21 | 22 | sonata.block.service.template 23 | Sablon 24 | 25 | 26 | form.label_template 27 | Sablon 28 | 29 | 30 | form.label_content 31 | Tartalom 32 | 33 | 34 | form.label_url 35 | URL 36 | 37 | 38 | form.label_title 39 | Cím 40 | 41 | 42 | form.label_code 43 | Kód 44 | 45 | 46 | form.label_layout 47 | Elrendezés 48 | 49 | 50 | form.label_class 51 | CSS osztály 52 | 53 | 54 | form.label_menu_name 55 | Menü 56 | 57 | 58 | form.label_safe_labels 59 | Biztonságos címkék 60 | 61 | 62 | form.label_current_class 63 | Jelenlegi CSS osztály 64 | 65 | 66 | form.label_first_class 67 | Első CSS osztály 68 | 69 | 70 | form.label_last_class 71 | Utolsó CSS osztály 72 | 73 | 74 | form.label_menu_class 75 | Menü CSS osztály 76 | 77 | 78 | form.label_menu_template 79 | Menü sablon 80 | 81 | 82 | form.label_translation_domain 83 | Fordítási domain 84 | 85 | 86 | form.label_icon 87 | Ikon 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /src/Resources/translations/SonataBlockBundle.it.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | sonata.block.service.container 7 | Contenitore 8 | 9 | 10 | sonata.block.service.text 11 | Testo semplice 12 | 13 | 14 | sonata.block.service.rss 15 | Feed RSS 16 | 17 | 18 | sonata.block.service.menu 19 | Menu 20 | 21 | 22 | sonata.block.service.template 23 | Template 24 | 25 | 26 | form.label_template 27 | Template 28 | 29 | 30 | form.label_content 31 | Contenuto 32 | 33 | 34 | form.label_url 35 | URL 36 | 37 | 38 | form.label_title 39 | Titolo 40 | 41 | 42 | form.label_code 43 | Codice 44 | 45 | 46 | form.label_layout 47 | Layout 48 | 49 | 50 | form.label_class 51 | Classe CSS 52 | 53 | 54 | form.label_menu_name 55 | Menù 56 | 57 | 58 | form.label_safe_labels 59 | Label sicure 60 | 61 | 62 | form.label_current_class 63 | Classe CSS corrente 64 | 65 | 66 | form.label_first_class 67 | Classe CSS primo el. 68 | 69 | 70 | form.label_last_class 71 | Classe CSS ultimo el. 72 | 73 | 74 | form.label_menu_template 75 | Template menu 76 | 77 | 78 | form.label_translation_domain 79 | Dominio traduzioni 80 | 81 | 82 | form.label_icon 83 | Icona 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /src/Resources/translations/SonataBlockBundle.nl.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | sonata.block.service.container 7 | Container 8 | 9 | 10 | sonata.block.service.text 11 | Simpele tekst 12 | 13 | 14 | sonata.block.service.rss 15 | RSS feed 16 | 17 | 18 | sonata.block.service.menu 19 | Menu 20 | 21 | 22 | sonata.block.service.template 23 | Template 24 | 25 | 26 | form.label_template 27 | Template 28 | 29 | 30 | form.label_content 31 | Inhoud 32 | 33 | 34 | form.label_url 35 | URL 36 | 37 | 38 | form.label_title 39 | Title 40 | 41 | 42 | form.label_code 43 | Code 44 | 45 | 46 | form.label_layout 47 | Layout 48 | 49 | 50 | form.label_class 51 | CSS Class 52 | 53 | 54 | form.label_menu_name 55 | Menu 56 | 57 | 58 | form.label_safe_labels 59 | Veilige labels 60 | 61 | 62 | form.label_current_class 63 | Huidige CSS Class 64 | 65 | 66 | form.label_first_class 67 | Eerste CSS Class 68 | 69 | 70 | form.label_last_class 71 | Laatste CSS Class 72 | 73 | 74 | form.label_menu_template 75 | Menu Template 76 | 77 | 78 | form.label_translation_domain 79 | Vertaling domein 80 | 81 | 82 | form.label_icon 83 | Icoon 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /src/Resources/translations/SonataBlockBundle.ru.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | sonata.block.service.container 7 | Контейнер 8 | 9 | 10 | sonata.block.service.text 11 | Простой текст 12 | 13 | 14 | sonata.block.service.rss 15 | RSS-лента 16 | 17 | 18 | sonata.block.service.menu 19 | Меню 20 | 21 | 22 | sonata.block.service.template 23 | Шаблон 24 | 25 | 26 | form.label_url 27 | URL 28 | 29 | 30 | form.label_title 31 | Заголовок 32 | 33 | 34 | form.label_class 35 | CSS-класс 36 | 37 | 38 | form.label_menu_name 39 | Меню 40 | 41 | 42 | form.label_safe_labels 43 | Безопасные заголовки 44 | 45 | 46 | form.label_current_class 47 | CSS-класс "текущий" 48 | 49 | 50 | form.label_first_class 51 | CSS-класс "первый" 52 | 53 | 54 | form.label_last_class 55 | CSS-класс "последний" 56 | 57 | 58 | form.label_menu_template 59 | Шаблон меню 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /src/Resources/translations/validators.de.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | sonata.block.menu.not_existing 7 | Menü %name% ist nicht vorhanden. 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Resources/translations/validators.en.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | sonata.block.menu.not_existing 7 | Menu %name% does not exist. 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Resources/translations/validators.fr.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | sonata.block.menu.not_existing 7 | Le menu "%name%" n'existe pas. 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Resources/translations/validators.hu.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | sonata.block.menu.not_existing 7 | A kért menü "%name%" nem létezik. 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Resources/translations/validators.nl.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | sonata.block.menu.not_existing 7 | Menu %name% bestaat niet. 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Resources/views/Block/block_base.html.twig: -------------------------------------------------------------------------------- 1 | {# 2 | 3 | This file is part of the Sonata package. 4 | 5 | (c) Thomas Rabaix 6 | 7 | For the full copyright and license information, please view the LICENSE 8 | file that was distributed with this source code. 9 | 10 | #} 11 | 12 |
13 | {% block block %}EMPTY CONTENT{% endblock %} 14 |
15 | -------------------------------------------------------------------------------- /src/Resources/views/Block/block_container.html.twig: -------------------------------------------------------------------------------- 1 | {# 2 | 3 | This file is part of the Sonata package. 4 | 5 | (c) Thomas Rabaix 6 | 7 | For the full copyright and license information, please view the LICENSE 8 | file that was distributed with this source code. 9 | 10 | #} 11 | 12 | {% extends sonata_block.templates.block_base %} 13 | 14 | {# block classes are prepended with a container class #} 15 | {% block block_class %} cms-container{% if not block.hasParent() %} cms-container-root{%endif%}{% if settings.class %} {{ settings.class }}{% endif %}{% endblock %} 16 | 17 | {# identify a block role used by the page editor #} 18 | {% block block_role %}container{% endblock %} 19 | 20 | {# render container block #} 21 | {% block block %} 22 | {% if decorator %}{{ decorator.pre|raw }}{% endif %} 23 | {% for child in block.children %} 24 | {% block block_child_render %} 25 | {{ sonata_block_render(child) }} 26 | {% endblock %} 27 | {% endfor %} 28 | {% if decorator %}{{ decorator.post|raw }}{% endif %} 29 | {% endblock %} 30 | -------------------------------------------------------------------------------- /src/Resources/views/Block/block_core_action.html.twig: -------------------------------------------------------------------------------- 1 | {# 2 | 3 | This file is part of the Sonata package. 4 | 5 | (c) Thomas Rabaix 6 | 7 | For the full copyright and license information, please view the LICENSE 8 | file that was distributed with this source code. 9 | 10 | #} 11 | 12 | {% extends sonata_block.templates.block_base %} 13 | 14 | {% block block %} 15 | {{ content|raw }} 16 | {% endblock %} 17 | -------------------------------------------------------------------------------- /src/Resources/views/Block/block_core_menu.html.twig: -------------------------------------------------------------------------------- 1 | {# 2 | 3 | This file is part of the Sonata package. 4 | 5 | (c) Thomas Rabaix 6 | 7 | For the full copyright and license information, please view the LICENSE 8 | file that was distributed with this source code. 9 | 10 | #} 11 | 12 | {% extends sonata_block.templates.block_base %} 13 | 14 | {% block block %} 15 | {{ knp_menu_render(menu, menu_options) }} 16 | {% endblock %} 17 | -------------------------------------------------------------------------------- /src/Resources/views/Block/block_core_rss.html.twig: -------------------------------------------------------------------------------- 1 | {# 2 | 3 | This file is part of the Sonata package. 4 | 5 | (c) Thomas Rabaix 6 | 7 | For the full copyright and license information, please view the LICENSE 8 | file that was distributed with this source code. 9 | 10 | #} 11 | 12 | {% extends sonata_block.templates.block_base %} 13 | 14 | {% block block %} 15 |
16 | {% if settings.title is not empty %} 17 |
18 |

19 | {% if settings.icon %} 20 | 21 | {% endif %} 22 | {% if settings.translation_domain %} 23 | {{ settings.title|trans({}, settings.translation_domain) }} 24 | {% else %} 25 | {{ settings.title }} 26 | {% endif %} 27 |

28 |
29 | {% endif %} 30 | 31 |
32 |
33 | {% for feed in feeds %} 34 |
35 |
36 | {{ feed.title }} 37 |
38 | {{ feed.description|raw }} 39 |
40 | {% else %} 41 | No feeds available. 42 | {% endfor %} 43 |
44 |
45 |
46 | {% endblock %} 47 | -------------------------------------------------------------------------------- /src/Resources/views/Block/block_core_text.html.twig: -------------------------------------------------------------------------------- 1 | {# 2 | 3 | This file is part of the Sonata package. 4 | 5 | (c) Thomas Rabaix 6 | 7 | For the full copyright and license information, please view the LICENSE 8 | file that was distributed with this source code. 9 | 10 | #} 11 | 12 | {% extends sonata_block.templates.block_base %} 13 | 14 | {% block block %} 15 | {{ settings.content|raw }} 16 | {% endblock %} 17 | -------------------------------------------------------------------------------- /src/Resources/views/Block/block_exception.html.twig: -------------------------------------------------------------------------------- 1 | {# 2 | 3 | This file is part of the Sonata package. 4 | 5 | (c) Thomas Rabaix 6 | 7 | For the full copyright and license information, please view the LICENSE 8 | file that was distributed with this source code. 9 | 10 | #} 11 | 12 | {% extends sonata_block.templates.block_base %} 13 | 14 | {% block block %} 15 |
16 |

{{ block.name }}

17 |

{{ exception.message }}

18 |
19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /src/Resources/views/Block/block_exception_debug.html.twig: -------------------------------------------------------------------------------- 1 | {# 2 | 3 | This file is part of the Sonata package. 4 | 5 | (c) Thomas Rabaix 6 | 7 | For the full copyright and license information, please view the LICENSE 8 | file that was distributed with this source code. 9 | 10 | #} 11 | 12 | {% extends sonata_block.templates.block_base %} 13 | 14 | {% block block %} 15 |
16 | 17 | {# this is dirty but the alternative would require a new block-optimized exception css #} 18 | {% if forceStyle %} 19 | 20 | 21 | {% endif %} 22 | {{ include('@Twig/Exception/exception.html.twig') }} 23 |
24 | {% endblock %} 25 | -------------------------------------------------------------------------------- /src/Resources/views/Block/block_no_page_available.html.twig: -------------------------------------------------------------------------------- 1 | {# 2 | 3 | This file is part of the Sonata package. 4 | 5 | (c) Thomas Rabaix 6 | 7 | For the full copyright and license information, please view the LICENSE 8 | file that was distributed with this source code. 9 | 10 | #} 11 | -------------------------------------------------------------------------------- /src/Resources/views/Block/block_side_menu_template.html.twig: -------------------------------------------------------------------------------- 1 | {# 2 | 3 | This file is part of the Sonata package. 4 | 5 | (c) Thomas Rabaix 6 | 7 | For the full copyright and license information, please view the LICENSE 8 | file that was distributed with this source code. 9 | 10 | #} 11 | 12 | {% extends 'knp_menu.html.twig' %} 13 | 14 | {% import 'knp_menu.html.twig' as macros %} 15 | 16 | {% block list %} 17 | {% if item.hasChildren and options.depth is not same as(0) and item.displayChildren %} 18 | 19 | {{ block('children') }} 20 | 21 | {% endif %} 22 | {% endblock %} 23 | 24 | {% block item %} 25 | {% if item.displayed %} 26 | {# building the class of the item #} 27 | {%- set classes = item.attribute('class') is not empty ? [item.attribute('class')] : [] %} 28 | {%- if matcher.isCurrent(item) %} 29 | {%- set classes = classes|merge([options.currentClass]) %} 30 | {%- elseif matcher.isAncestor(item, options.depth) %} 31 | {%- set classes = classes|merge([options.ancestorClass]) %} 32 | {%- endif %} 33 | {%- if item.actsLikeFirst %} 34 | {%- set classes = classes|merge([options.firstClass]) %} 35 | {%- endif %} 36 | {%- if item.actsLikeLast %} 37 | {%- set classes = classes|merge([options.lastClass]) %} 38 | {%- endif %} 39 | {%- set attributes = item.attributes %} 40 | {%- if classes is not empty %} 41 | {%- set attributes = attributes|merge({'class': classes|join(' ')}) %} 42 | {%- endif %} 43 | {# displaying the item #} 44 | {%- if item.uri is not empty and (not item.current or options.currentAsLink) %} 45 | {{ block('linkElement') }} 46 | {%- else %} 47 | {{ block('spanElement') }} 48 | {%- endif %} 49 | {# render the list of children#} 50 | {%- set childrenClasses = item.childrenAttribute('class') is not empty ? [item.childrenAttribute('class')] : [] %} 51 | {%- set childrenClasses = childrenClasses|merge(['menu_level_' ~ item.level]) %} 52 | {%- set listAttributes = item.childrenAttributes|merge({'class': childrenClasses|join(' ') }) %} 53 | {{ block('list') }} 54 | {% endif %} 55 | {% endblock %} 56 | 57 | {% block linkElement %}{{ block('label') }}{% endblock %} 58 | 59 | {% block spanElement %}

{{ block('label') }}

{% endblock %} 60 | -------------------------------------------------------------------------------- /src/Resources/views/Block/block_template.html.twig: -------------------------------------------------------------------------------- 1 | {# 2 | 3 | This file is part of the Sonata package. 4 | 5 | (c) Thomas Rabaix 6 | 7 | For the full copyright and license information, please view the LICENSE 8 | file that was distributed with this source code. 9 | 10 | #} 11 | 12 | {% extends sonata_block.templates.block_base %} 13 | 14 | {% block block %} 15 |

Sonata Block Template

16 | If you want to use the sonata.block.template block type, you need to create a template : 17 | 18 |
19 |         {%- verbatim -%}
20 | {# file: '@My/Block/my_block_feature_1.html.twig' #}
21 | {% extends sonata_block.templates.block_base %}
22 | 
23 | {% block block %}
24 |     <h3>The block title</h3>
25 |     <p>
26 |         Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam vel turpis at lacus
27 |         vehicula fringilla at eu lectus. Duis vitae arcu congue, porttitor nisi sit amet,
28 |         mattis metus. Nunc mollis elit ut lectus cursus luctus. Aliquam eu magna sit amet
29 |         massa volutpat auctor.
30 |     </p>
31 | {% endblock %}
32 |         {%- endverbatim -%}
33 |     
34 | 35 | And then call it from a template with the sonata_block_render helper: 36 | 37 |
38 | {%- verbatim -%}
39 | {{ sonata_block_render({ 'type': 'sonata.block.service.template' }, {
40 |     'template': '@My/Block/my_block_feature_1.html.twig',
41 | }) }}
42 | {%- endverbatim -%}
43 |     
44 | {% endblock %} 45 | -------------------------------------------------------------------------------- /src/Resources/views/Profiler/block.html.twig: -------------------------------------------------------------------------------- 1 | {% extends '@WebProfiler/Profiler/layout.html.twig' %} 2 | 3 | {% block toolbar %} 4 |
5 | 6 |
7 | {{ include('@SonataBlock/Profiler/icon.svg') }} 8 | {{ collector.getTotalBlock() }} 9 |
10 |
11 | 12 |
13 |
14 | Real Blocks 15 | {{ collector.realBlocks|length }} 16 |
17 |
18 | Containers 19 | {{ collector.containers|length }} 20 |
21 |
22 | Events 23 | {{ collector.events|length }} 24 |
25 |
26 |
27 | {% endblock %} 28 | 29 | {% block menu %} 30 | 31 | 32 | {{ include('@SonataBlock/Profiler/icon.svg') }} 33 | 34 | Blocks{% if collector.events|length > 0 %}*{% endif %} 35 | 36 | {{ collector.getTotalBlock() }} 37 | 38 | 39 | {% endblock %} 40 | 41 | {% block panel %} 42 |

Events Blocks

43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | {% for event in collector.events %} 52 | 53 | 54 | 55 | 62 | 69 | 70 | {% endfor %} 71 |
code namelistener tagBlock typesListeners
{{ event['template_code'] }}{{ event['event_name'] }} 56 | {% for type in event['blocks'] %} 57 | {{ type.1 }} (id:{{ type.0 }}) 58 | {% else %} 59 | no block returned 60 | {% endfor %} 61 | 63 | {% for listener in event['listeners'] %} 64 | {{ listener }} 65 | {% else %} 66 | no listener registered 67 | {% endfor %} 68 |
72 | 73 |

Real Blocks

74 | {% set blocks = collector.realBlocks %} 75 |
76 | {{ block('table_v2') }} 77 |
78 | 79 |

Containers Blocks

80 | {% set blocks = collector.containers %} 81 |
82 | {{ block('table_v2') }} 83 |
84 | {% endblock %} 85 | 86 | {% block table %} 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | {% for id, block in blocks %} 97 | 98 | {% set rowspan = 1 %} 99 | 100 | {% if block.assets.js|length > 0 or block.assets.css|length > 0 %} 101 | {% set rowspan = rowspan + 1 %} 102 | {% endif %} 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | {% if block.assets.js|length > 0 or block.assets.css|length > 0 %} 113 | 114 | 118 | 119 | {% endif %} 120 | 121 | {% endfor %} 122 |
IdNameTypeMem. (diff)Mem. (peak)Duration
{{ id }}{{ block.name }}{{ block.type }}{{ ((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
115 | Javascripts:
{{ block.assets.js|json_encode() }}

116 | Stylesheets:
{{ block.assets.css|json_encode() }}
117 |
123 | {% endblock %} 124 | 125 | {% block table_v2 %} 126 | {% for id, block in blocks %} 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | {% if block.assets.js|length > 0 or block.assets.css|length > 0 %} 148 | 149 | 150 | 154 | 155 | {% endif %} 156 | 157 |
Block {{ id }}
Name{{ block.name }}
Type{{ block.type }}
Mem. diff / Mem. peak / Duration{{ ((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
Assets 151 | Javascripts:
{{ block.assets.js|json_encode() }}

152 | Stylesheets:
{{ block.assets.css|json_encode() }}
153 |
158 | {% endfor %} 159 | {% endblock %} 160 | -------------------------------------------------------------------------------- /src/Resources/views/Profiler/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/SonataBlockBundle.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle; 15 | 16 | use Sonata\BlockBundle\DependencyInjection\Compiler\GlobalVariablesCompilerPass; 17 | use Sonata\BlockBundle\DependencyInjection\Compiler\TweakCompilerPass; 18 | use Symfony\Component\DependencyInjection\ContainerBuilder; 19 | use Symfony\Component\HttpKernel\Bundle\Bundle; 20 | 21 | final class SonataBlockBundle extends Bundle 22 | { 23 | public function build(ContainerBuilder $container): void 24 | { 25 | $container->addCompilerPass(new TweakCompilerPass()); 26 | $container->addCompilerPass(new GlobalVariablesCompilerPass()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Templating/Helper/BlockHelper.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Templating\Helper; 15 | 16 | use Sonata\BlockBundle\Block\BlockContextManagerInterface; 17 | use Sonata\BlockBundle\Block\BlockRendererInterface; 18 | use Sonata\BlockBundle\Event\BlockEvent; 19 | use Sonata\BlockBundle\Model\BlockInterface; 20 | use Symfony\Component\EventDispatcher\EventDispatcherInterface as EventDispatcherComponentInterface; 21 | use Symfony\Component\Stopwatch\Stopwatch; 22 | use Symfony\Component\Stopwatch\StopwatchEvent; 23 | use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; 24 | 25 | /** 26 | * @phpstan-type Trace = array{ 27 | * name: string, 28 | * type: string, 29 | * duration: int|float|false, 30 | * memory_start: int|false, 31 | * memory_end: int|false, 32 | * memory_peak: int|false, 33 | * assets: array{ 34 | * js: string[], 35 | * css: string[], 36 | * } 37 | * } 38 | * 39 | * NEXT_MAJOR: Change this class to final. 40 | * 41 | * @final since 5.2.0 42 | */ 43 | class BlockHelper 44 | { 45 | /** 46 | * This property is a state variable holdings all assets used by the block for the current PHP request 47 | * It is used to correctly render the javascripts and stylesheets tags on the main layout. 48 | * 49 | * @var array{css: array, js: array} 50 | */ 51 | private array $assets = ['css' => [], 'js' => []]; 52 | 53 | /** 54 | * @var array> 55 | * 56 | * @phpstan-var array 57 | */ 58 | private array $traces = []; 59 | 60 | /** 61 | * @var array 62 | */ 63 | private array $eventTraces = []; 64 | 65 | /** 66 | * @internal 67 | */ 68 | public function __construct( 69 | private BlockRendererInterface $blockRenderer, 70 | private BlockContextManagerInterface $blockContextManager, 71 | private EventDispatcherInterface $eventDispatcher, 72 | private ?Stopwatch $stopwatch = null, 73 | ) { 74 | } 75 | 76 | /** 77 | * @param string $media Unused, only kept to not break existing code 78 | * @param string $basePath Base path to prepend to the stylesheet urls 79 | * 80 | * @return string 81 | */ 82 | public function includeJavascripts($media, $basePath = '') 83 | { 84 | $html = ''; 85 | foreach ($this->assets['js'] as $javascript) { 86 | $html .= "\n".\sprintf('', $basePath, $javascript); 87 | } 88 | 89 | return $html; 90 | } 91 | 92 | /** 93 | * @param string $media The css media type to use: all|screen|... 94 | * @param string $basePath Base path to prepend to the stylesheet urls 95 | * 96 | * @return string 97 | */ 98 | public function includeStylesheets($media, $basePath = '') 99 | { 100 | if (0 === \count($this->assets['css'])) { 101 | return ''; 102 | } 103 | 104 | $html = \sprintf(""; 111 | 112 | return $html; 113 | } 114 | 115 | /** 116 | * @param array $options 117 | */ 118 | public function renderEvent(string $name, array $options = []): string 119 | { 120 | $eventName = \sprintf('sonata.block.event.%s', $name); 121 | 122 | $event = $this->eventDispatcher->dispatch(new BlockEvent($options), $eventName); 123 | 124 | $content = ''; 125 | 126 | foreach ($event->getBlocks() as $block) { 127 | $content .= $this->render($block); 128 | } 129 | 130 | if (null !== $this->stopwatch) { 131 | $this->eventTraces[uniqid('', true)] = [ 132 | 'template_code' => $name, 133 | 'event_name' => $eventName, 134 | 'blocks' => $this->getEventBlocks($event), 135 | 'listeners' => $this->getEventListeners($eventName), 136 | ]; 137 | } 138 | 139 | return $content; 140 | } 141 | 142 | /** 143 | * Check if a given block type exists. 144 | * 145 | * @param string $type Block type to check for 146 | */ 147 | public function exists(string $type): bool 148 | { 149 | return $this->blockContextManager->exists($type); 150 | } 151 | 152 | /** 153 | * @param string|array|BlockInterface $block 154 | * @param array $options 155 | */ 156 | public function render(string|array|BlockInterface $block, array $options = []): string 157 | { 158 | $blockContext = $this->blockContextManager->get($block, $options); 159 | 160 | $stats = null; 161 | 162 | if (null !== $this->stopwatch) { 163 | $stats = $this->startTracing($blockContext->getBlock()); 164 | } 165 | 166 | $response = $this->blockRenderer->render($blockContext); 167 | 168 | if (null !== $stats) { 169 | $this->stopTracing($blockContext->getBlock(), $stats); 170 | } 171 | 172 | return (string) $response->getContent(); 173 | } 174 | 175 | /** 176 | * Returns the rendering traces. 177 | * 178 | * @return array 179 | */ 180 | public function getTraces(): array 181 | { 182 | return ['_events' => $this->eventTraces] + $this->traces; 183 | } 184 | 185 | /** 186 | * @param array $stats 187 | * 188 | * @phpstan-param Trace $stats 189 | */ 190 | private function stopTracing(BlockInterface $block, array $stats): void 191 | { 192 | $event = $this->traces[$block->getId() ?? '']; 193 | if (!$event instanceof StopwatchEvent) { 194 | throw new \InvalidArgumentException( 195 | \sprintf('The block %s has no stopwatch event to stop.', $block->getId() ?? '') 196 | ); 197 | } 198 | 199 | $event->stop(); 200 | 201 | $this->traces[$block->getId() ?? ''] = [ 202 | 'duration' => $event->getDuration(), 203 | 'memory_end' => memory_get_usage(true), 204 | 'memory_peak' => memory_get_peak_usage(true), 205 | ] + $stats; 206 | } 207 | 208 | /** 209 | * @return array 210 | */ 211 | private function getEventBlocks(BlockEvent $event): array 212 | { 213 | $results = []; 214 | 215 | foreach ($event->getBlocks() as $block) { 216 | $results[] = [$block->getId() ?? '', $block->getType() ?? '']; 217 | } 218 | 219 | return $results; 220 | } 221 | 222 | /** 223 | * @return string[] 224 | */ 225 | private function getEventListeners(string $eventName): array 226 | { 227 | $results = []; 228 | 229 | if (!$this->eventDispatcher instanceof EventDispatcherComponentInterface) { 230 | return $results; 231 | } 232 | 233 | foreach ($this->eventDispatcher->getListeners($eventName) as $listener) { 234 | if ($listener instanceof \Closure) { 235 | $results[] = '{closure}()'; 236 | } elseif (\is_array($listener) && \is_object($listener[0])) { 237 | $results[] = $listener[0]::class; 238 | } elseif (\is_array($listener) && \is_string($listener[0])) { 239 | $results[] = $listener[0]; 240 | } else { 241 | $results[] = 'Unknown type!'; 242 | } 243 | } 244 | 245 | return $results; 246 | } 247 | 248 | /** 249 | * @return array 250 | * 251 | * @phpstan-return Trace 252 | */ 253 | private function startTracing(BlockInterface $block): array 254 | { 255 | if (null !== $this->stopwatch) { 256 | $this->traces[$block->getId() ?? ''] = $this->stopwatch->start( 257 | \sprintf( 258 | '%s (id: %s, type: %s)', 259 | $block->getName() ?? '', 260 | $block->getId() ?? '', 261 | $block->getType() ?? '' 262 | ) 263 | ); 264 | } 265 | 266 | return [ 267 | 'name' => $block->getName() ?? '', 268 | 'type' => $block->getType() ?? '', 269 | 'duration' => false, 270 | 'memory_start' => memory_get_usage(true), 271 | 'memory_end' => false, 272 | 'memory_peak' => false, 273 | 'assets' => [ 274 | 'js' => [], 275 | 'css' => [], 276 | ], 277 | ]; 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /src/Test/BlockServiceTestCase.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Test; 15 | 16 | use PHPUnit\Framework\MockObject\MockObject; 17 | use PHPUnit\Framework\TestCase; 18 | use Sonata\BlockBundle\Block\BlockContextInterface; 19 | use Sonata\BlockBundle\Block\BlockContextManager; 20 | use Sonata\BlockBundle\Block\BlockContextManagerInterface; 21 | use Sonata\BlockBundle\Block\BlockLoaderInterface; 22 | use Sonata\BlockBundle\Block\BlockServiceManagerInterface; 23 | use Sonata\BlockBundle\Block\Service\BlockServiceInterface; 24 | use Sonata\BlockBundle\Model\BlockInterface; 25 | use Twig\Environment; 26 | 27 | /** 28 | * Abstract test class for block service tests. 29 | * 30 | * @author Sullivan Senechal 31 | */ 32 | abstract class BlockServiceTestCase extends TestCase 33 | { 34 | /** 35 | * @var MockObject&BlockServiceManagerInterface 36 | */ 37 | protected $blockServiceManager; 38 | 39 | /** 40 | * @var BlockContextManagerInterface 41 | */ 42 | protected $blockContextManager; 43 | 44 | /** 45 | * @var MockObject&Environment 46 | */ 47 | protected $twig; 48 | 49 | /** 50 | * @var MockObject&BlockInterface 51 | */ 52 | protected $block; 53 | 54 | protected function setUp(): void 55 | { 56 | $blockLoader = $this->createMock(BlockLoaderInterface::class); 57 | $this->blockServiceManager = $this->createMock(BlockServiceManagerInterface::class); 58 | $this->blockContextManager = new BlockContextManager($blockLoader, $this->blockServiceManager); 59 | $this->twig = $this->createMock(Environment::class); 60 | $this->block = $this->createMock(BlockInterface::class); 61 | } 62 | 63 | /** 64 | * Create a mocked block service. 65 | */ 66 | protected function getBlockContext(BlockServiceInterface $blockService): BlockContextInterface 67 | { 68 | $this->blockServiceManager->expects(static::once())->method('get')->willReturn($blockService); 69 | $this->block->expects(static::once())->method('getSettings')->willReturn([]); 70 | 71 | return $this->blockContextManager->get($this->block); 72 | } 73 | 74 | /** 75 | * Asserts that the block settings have the expected values. 76 | * 77 | * @param array $expected Expected settings 78 | */ 79 | protected function assertSettings(array $expected, BlockContextInterface $blockContext): void 80 | { 81 | $completeExpectedOptions = $expected + [ 82 | 'attr' => [], 83 | ]; 84 | 85 | ksort($completeExpectedOptions); 86 | $blockSettings = $blockContext->getSettings(); 87 | ksort($blockSettings); 88 | 89 | static::assertSame($completeExpectedOptions, $blockSettings); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Twig/Extension/BlockExtension.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Twig\Extension; 15 | 16 | use Sonata\BlockBundle\Templating\Helper\BlockHelper; 17 | use Twig\Extension\AbstractExtension; 18 | use Twig\TwigFunction; 19 | 20 | final class BlockExtension extends AbstractExtension 21 | { 22 | public function getFunctions(): array 23 | { 24 | return [ 25 | new TwigFunction( 26 | 'sonata_block_exists', 27 | [BlockHelper::class, 'exists'] 28 | ), 29 | new TwigFunction( 30 | 'sonata_block_render', 31 | [BlockHelper::class, 'render'], 32 | ['is_safe' => ['html']] 33 | ), 34 | new TwigFunction( 35 | 'sonata_block_render_event', 36 | [BlockHelper::class, 'renderEvent'], 37 | ['is_safe' => ['html']] 38 | ), 39 | new TwigFunction( 40 | 'sonata_block_include_javascripts', 41 | [BlockHelper::class, 'includeJavascripts'], 42 | ['is_safe' => ['html']] 43 | ), 44 | new TwigFunction( 45 | 'sonata_block_include_stylesheets', 46 | [BlockHelper::class, 'includeStylesheets'], 47 | ['is_safe' => ['html']] 48 | ), 49 | ]; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Twig/GlobalVariables.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Twig; 15 | 16 | /** 17 | * GlobalVariables. 18 | * 19 | * @author Thomas Rabaix 20 | */ 21 | final class GlobalVariables 22 | { 23 | /** 24 | * @param string[] $templates 25 | */ 26 | public function __construct(private array $templates) 27 | { 28 | } 29 | 30 | /** 31 | * @return string[] 32 | */ 33 | public function getTemplates(): array 34 | { 35 | return $this->templates; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Util/RecursiveBlockIterator.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Util; 15 | 16 | use Doctrine\Common\Collections\ReadableCollection; 17 | use Sonata\BlockBundle\Model\BlockInterface; 18 | 19 | /** 20 | * @author Thomas Rabaix 21 | * 22 | * @phpstan-extends \RecursiveArrayIterator 23 | */ 24 | final class RecursiveBlockIterator extends \RecursiveArrayIterator 25 | { 26 | /** 27 | * @param ReadableCollection|array $array 28 | */ 29 | public function __construct($array) 30 | { 31 | if ($array instanceof ReadableCollection) { 32 | $array = $array->toArray(); 33 | } 34 | 35 | parent::__construct($array); 36 | } 37 | 38 | /** 39 | * @phpstan-return RecursiveBlockIterator 40 | */ 41 | public function getChildren(): self 42 | { 43 | $current = $this->current(); 44 | \assert(null !== $current); 45 | 46 | return new self($current->getChildren()); 47 | } 48 | 49 | public function hasChildren(): bool 50 | { 51 | $current = $this->current(); 52 | \assert(null !== $current); 53 | 54 | return $current->hasChild(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Util/RecursiveBlockIteratorIterator.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\BlockBundle\Util; 15 | 16 | use Doctrine\Common\Collections\ReadableCollection; 17 | 18 | /** 19 | * @author Thomas Rabaix 20 | * 21 | * @phpstan-extends \RecursiveIteratorIterator 22 | */ 23 | final class RecursiveBlockIteratorIterator extends \RecursiveIteratorIterator 24 | { 25 | /** 26 | * @param array|ReadableCollection $array 27 | */ 28 | public function __construct($array) 29 | { 30 | parent::__construct(new RecursiveBlockIterator($array), \RecursiveIteratorIterator::SELF_FIRST); 31 | } 32 | } 33 | --------------------------------------------------------------------------------